Skip to content

Commit 7d22caa

Browse files
authored
Synchronize contents in multiple editors for same nb file (#9886)
For #9340 Note This PR adds the infrastructure necessary to sync edits & the like across editors for the same file Code has been commented out to ensure some remaining bits work properly Waiting on some of the new changes from @rchiodo from his undo PR. Remaining changes Ensuring we do not end up in a crazy loop that will cause one editor to propagate changes to the other, then that one back to this & so on Sync cell execution (currently creation of new cells isn't sync'ed across) Find a bullet proof solution for syncing focused states (this messes up everything - we don't need this in syncing editors, but if required for live share, then we need to ensure focus states work correctly - depending on whether liveshare even needs such a feature?) * Address code review comments * Fix test * Fix linter * Remove unused property * More changes
1 parent 05bf2ea commit 7d22caa

File tree

23 files changed

+348
-103
lines changed

23 files changed

+348
-103
lines changed

src/client/datascience/interactive-common/interactiveBase.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { CancellationToken, ConfigurationTarget, Event, EventEmitter, Memento, P
1111
import { Disposable } from 'vscode-jsonrpc';
1212

1313
import { ServerStatus } from '../../../datascience-ui/interactive-common/mainState';
14+
import { CommonActionType } from '../../../datascience-ui/interactive-common/redux/reducers/types';
1415
import { IApplicationShell, ICommandManager, IDocumentManager, ILiveShareApi, IWebPanelProvider, IWorkspaceService } from '../../common/application/types';
1516
import { CancellationError } from '../../common/cancellation';
1617
import { EXTENSION_ROOT_DIR, PYTHON_LANGUAGE } from '../../common/constants';
@@ -43,7 +44,7 @@ import { JupyterInstallError } from '../jupyter/jupyterInstallError';
4344
import { JupyterSelfCertsError } from '../jupyter/jupyterSelfCertsError';
4445
import { JupyterKernelPromiseFailedError } from '../jupyter/kernels/jupyterKernelPromiseFailedError';
4546
import { LiveKernelModel } from '../jupyter/kernels/types';
46-
import { CssMessages } from '../messages';
47+
import { CssMessages, SharedMessages } from '../messages';
4748
import { ProgressReporter } from '../progress/progressReporter';
4849
import {
4950
CellState,
@@ -72,6 +73,7 @@ import {
7273
} from '../types';
7374
import { WebViewHost } from '../webViewHost';
7475
import { InteractiveWindowMessageListener } from './interactiveWindowMessageListener';
76+
import { BaseReduxActionPayload } from './types';
7577

7678
@injectable()
7779
export abstract class InteractiveBase extends WebViewHost<IInteractiveWindowMapping> implements IInteractiveBase {
@@ -174,6 +176,11 @@ export abstract class InteractiveBase extends WebViewHost<IInteractiveWindowMapp
174176
// tslint:disable-next-line: no-any no-empty cyclomatic-complexity max-func-body-length
175177
public onMessage(message: string, payload: any) {
176178
switch (message) {
179+
case InteractiveWindowMessages.Sync:
180+
// tslint:disable-next-line: no-any
181+
const syncPayload = payload as { type: InteractiveWindowMessages | SharedMessages | CommonActionType; payload: BaseReduxActionPayload<any> };
182+
this.postMessageInternal(syncPayload.type, syncPayload.payload).ignoreErrors();
183+
break;
177184
case InteractiveWindowMessages.GotoCodeCell:
178185
this.handleMessage(message, payload, this.gotoCode);
179186
break;

src/client/datascience/interactive-common/interactiveWindowMessageListener.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { InteractiveWindowMessages, InteractiveWindowRemoteMessages } from './in
1515

1616
// This class listens to messages that come from the local Python Interactive window
1717
export class InteractiveWindowMessageListener implements IWebPanelMessageListener {
18+
private static handlers = new Map<InteractiveWindowMessageListener, (message: string, payload: any) => void>();
1819
private postOffice: PostOffice;
1920
private disposedCallback: () => void;
2021
private callback: (message: string, payload: any) => void;
@@ -40,6 +41,7 @@ export class InteractiveWindowMessageListener implements IWebPanelMessageListene
4041
this.interactiveWindowMessages.forEach(m => {
4142
this.postOffice.registerCallback(m, a => callback(m, a)).ignoreErrors();
4243
});
44+
InteractiveWindowMessageListener.handlers.set(this, callback);
4345
}
4446

4547
public async dispose() {
@@ -48,6 +50,20 @@ export class InteractiveWindowMessageListener implements IWebPanelMessageListene
4850
}
4951

5052
public onMessage(message: string, payload: any) {
53+
if (message === InteractiveWindowMessages.Sync) {
54+
// const syncPayload = payload as BaseReduxActionPayload;
55+
Array.from(InteractiveWindowMessageListener.handlers.keys()).forEach(item => {
56+
if (item === this) {
57+
return;
58+
}
59+
// Temporarily disabled.
60+
// const cb = InteractiveWindowMessageListener.handlers.get(item);
61+
// if (cb) {
62+
// cb(InteractiveWindowMessages.Sync, { type: message, payload: syncPayload });
63+
// }
64+
});
65+
return;
66+
}
5167
// We received a message from the local webview. Broadcast it to everybody if it's a remote message
5268
if (InteractiveWindowRemoteMessages.indexOf(message) >= 0) {
5369
this.postOffice.postCommand(message, payload).ignoreErrors();

src/client/datascience/interactive-common/interactiveWindowTypes.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
'use strict';
44
import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api';
55
import { IServerState } from '../../../datascience-ui/interactive-common/mainState';
6-
import { IAddCellAction, ICellAction } from '../../../datascience-ui/interactive-common/redux/reducers/types';
6+
import { CommonActionType, IAddCellAction, ICellAction } from '../../../datascience-ui/interactive-common/redux/reducers/types';
77
import { CssMessages, IGetCssRequest, IGetCssResponse, IGetMonacoThemeRequest, SharedMessages } from '../messages';
88
import { IGetMonacoThemeResponse } from '../monacoMessages';
99
import { ICell, IInteractiveWindowInfo, IJupyterVariable, IJupyterVariablesRequest, IJupyterVariablesResponse } from '../types';
10+
import { BaseReduxActionPayload } from './types';
1011

1112
export enum InteractiveWindowMessages {
1213
StartCell = 'start_cell',
@@ -58,6 +59,7 @@ export enum InteractiveWindowMessages {
5859
EditCell = 'edit_cell',
5960
RemoveCell = 'remove_cell',
6061
SwapCells = 'swap_cells',
62+
Sync = 'sync_message_used_to_broadcast_and_sync_editors',
6163
InsertCell = 'insert_cell',
6264
LoadOnigasmAssemblyRequest = 'load_onigasm_assembly_request',
6365
LoadOnigasmAssemblyResponse = 'load_onigasm_assembly_response',
@@ -368,6 +370,8 @@ export class IInteractiveWindowMapping {
368370
public [InteractiveWindowMessages.NotebookDirty]: never | undefined;
369371
public [InteractiveWindowMessages.NotebookClean]: never | undefined;
370372
public [InteractiveWindowMessages.SaveAll]: ISaveAll;
373+
// tslint:disable-next-line: no-any
374+
public [InteractiveWindowMessages.Sync]: { type: InteractiveWindowMessages | SharedMessages | CommonActionType; payload: BaseReduxActionPayload<any> };
371375
public [InteractiveWindowMessages.NativeCommand]: INativeCommand;
372376
public [InteractiveWindowMessages.VariablesComplete]: never | undefined;
373377
public [InteractiveWindowMessages.NotebookRunAllCells]: never | undefined;
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import { CommonActionType, CommonActionTypeMapping } from '../../../datascience-ui/interactive-common/redux/reducers/types';
2+
import { CssMessages, SharedMessages } from '../messages';
3+
import { IInteractiveWindowMapping, InteractiveWindowMessages } from './interactiveWindowTypes';
4+
import { BaseReduxActionPayload } from './types';
5+
6+
// Copyright (c) Microsoft Corporation. All rights reserved.
7+
// Licensed under the MIT License.
8+
9+
export enum MessageType {
10+
other = 0,
11+
/**
12+
* Messages must be re-broadcasted across other editors of the same file in the same session.
13+
*/
14+
syncAcrossSameNotebooks = 1 << 0,
15+
/**
16+
* Messages must be re-broadcasted across all sessions.
17+
*/
18+
syncWithLiveShare = 1 << 1,
19+
noIdea = 1 << 2
20+
}
21+
22+
type MessageMapping<T> = {
23+
[P in keyof T]: MessageType;
24+
};
25+
26+
export type IInteractiveActionMapping = MessageMapping<IInteractiveWindowMapping>;
27+
28+
// Do not change to a dictionary or a record.
29+
// The current structure ensures all new enums added will be categorized.
30+
// This way, if a new message is added, we'll make the decision early on whether it needs to be synchronized and how.
31+
// Rather than waiting for users to report issues related to new messages.
32+
const messageWithMessageTypes: MessageMapping<IInteractiveWindowMapping> & MessageMapping<CommonActionTypeMapping> = {
33+
[CommonActionType.ADD_NEW_CELL]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare,
34+
[CommonActionType.ARROW_DOWN]: MessageType.syncWithLiveShare,
35+
[CommonActionType.ARROW_UP]: MessageType.syncWithLiveShare,
36+
[CommonActionType.CHANGE_CELL_TYPE]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare,
37+
[CommonActionType.CLICK_CELL]: MessageType.syncWithLiveShare,
38+
[CommonActionType.CODE_CREATED]: MessageType.noIdea,
39+
[CommonActionType.COPY_CELL_CODE]: MessageType.other,
40+
[CommonActionType.EDITOR_LOADED]: MessageType.other,
41+
[CommonActionType.EDIT_CELL]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare,
42+
[CommonActionType.EXECUTE_ABOVE]: MessageType.other,
43+
[CommonActionType.EXECUTE_ALL_CELLS]: MessageType.other,
44+
[CommonActionType.EXECUTE_CELL]: MessageType.other,
45+
[CommonActionType.EXECUTE_CELL_AND_BELOW]: MessageType.other,
46+
[CommonActionType.EXPORT]: MessageType.other,
47+
[CommonActionType.FOCUS_CELL]: MessageType.syncWithLiveShare,
48+
[CommonActionType.GATHER_CELL]: MessageType.other,
49+
[CommonActionType.GET_VARIABLE_DATA]: MessageType.other,
50+
[CommonActionType.GOTO_CELL]: MessageType.syncWithLiveShare,
51+
[CommonActionType.INSERT_ABOVE]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare,
52+
[CommonActionType.INSERT_ABOVE_FIRST]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare,
53+
[CommonActionType.INSERT_BELOW]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare,
54+
[CommonActionType.INTERRUPT_KERNEL]: MessageType.other,
55+
[CommonActionType.LOADED_ALL_CELLS]: MessageType.other,
56+
[CommonActionType.LINK_CLICK]: MessageType.other,
57+
[CommonActionType.MOVE_CELL_DOWN]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare,
58+
[CommonActionType.MOVE_CELL_UP]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare,
59+
[CommonActionType.RESTART_KERNEL]: MessageType.other,
60+
[CommonActionType.SAVE]: MessageType.other,
61+
[CommonActionType.SCROLL]: MessageType.syncWithLiveShare,
62+
[CommonActionType.SELECT_CELL]: MessageType.syncWithLiveShare,
63+
[CommonActionType.SELECT_SERVER]: MessageType.other,
64+
[CommonActionType.SEND_COMMAND]: MessageType.other,
65+
[CommonActionType.SHOW_DATA_VIEWER]: MessageType.other,
66+
[CommonActionType.SUBMIT_INPUT]: MessageType.other,
67+
[CommonActionType.TOGGLE_INPUT_BLOCK]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare,
68+
[CommonActionType.TOGGLE_LINE_NUMBERS]: MessageType.syncWithLiveShare,
69+
[CommonActionType.TOGGLE_OUTPUT]: MessageType.syncWithLiveShare,
70+
[CommonActionType.TOGGLE_VARIABLE_EXPLORER]: MessageType.syncWithLiveShare,
71+
[CommonActionType.UNFOCUS_CELL]: MessageType.syncWithLiveShare,
72+
[CommonActionType.UNMOUNT]: MessageType.other,
73+
74+
// Types from InteractiveWindowMessages
75+
[InteractiveWindowMessages.Activate]: MessageType.other,
76+
[InteractiveWindowMessages.AddCell]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare,
77+
[InteractiveWindowMessages.AddedSysInfo]: MessageType.other,
78+
[InteractiveWindowMessages.CancelCompletionItemsRequest]: MessageType.other,
79+
[InteractiveWindowMessages.CancelHoverRequest]: MessageType.other,
80+
[InteractiveWindowMessages.CancelResolveCompletionItemRequest]: MessageType.other,
81+
[InteractiveWindowMessages.CancelSignatureHelpRequest]: MessageType.other,
82+
[InteractiveWindowMessages.ClearAllOutputs]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare,
83+
[InteractiveWindowMessages.CollapseAll]: MessageType.syncWithLiveShare,
84+
[InteractiveWindowMessages.CopyCodeCell]: MessageType.other,
85+
[InteractiveWindowMessages.DeleteAllCells]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare,
86+
[InteractiveWindowMessages.DeleteCell]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare,
87+
[InteractiveWindowMessages.DoSave]: MessageType.other,
88+
[InteractiveWindowMessages.EditCell]: MessageType.other,
89+
[InteractiveWindowMessages.ExecutionRendered]: MessageType.other,
90+
[InteractiveWindowMessages.ExpandAll]: MessageType.syncWithLiveShare,
91+
[InteractiveWindowMessages.Export]: MessageType.other,
92+
[InteractiveWindowMessages.FinishCell]: MessageType.other,
93+
[InteractiveWindowMessages.FocusedCellEditor]: MessageType.syncWithLiveShare,
94+
[InteractiveWindowMessages.GatherCodeRequest]: MessageType.other,
95+
[InteractiveWindowMessages.GetAllCells]: MessageType.other,
96+
[InteractiveWindowMessages.GetVariablesRequest]: MessageType.other,
97+
[InteractiveWindowMessages.GetVariablesResponse]: MessageType.other,
98+
[InteractiveWindowMessages.GotoCodeCell]: MessageType.syncWithLiveShare,
99+
[InteractiveWindowMessages.GotoCodeCell]: MessageType.syncWithLiveShare,
100+
[InteractiveWindowMessages.InsertCell]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare,
101+
[InteractiveWindowMessages.Interrupt]: MessageType.other,
102+
[InteractiveWindowMessages.LoadAllCells]: MessageType.other,
103+
[InteractiveWindowMessages.LoadAllCellsComplete]: MessageType.other,
104+
[InteractiveWindowMessages.LoadOnigasmAssemblyRequest]: MessageType.other,
105+
[InteractiveWindowMessages.LoadOnigasmAssemblyResponse]: MessageType.other,
106+
[InteractiveWindowMessages.LoadTmLanguageRequest]: MessageType.other,
107+
[InteractiveWindowMessages.LoadTmLanguageResponse]: MessageType.other,
108+
[InteractiveWindowMessages.MonacoReady]: MessageType.other,
109+
[InteractiveWindowMessages.NativeCommand]: MessageType.other,
110+
[InteractiveWindowMessages.NotebookAddCellBelow]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare,
111+
[InteractiveWindowMessages.NotebookClean]: MessageType.other,
112+
[InteractiveWindowMessages.NotebookDirty]: MessageType.other,
113+
[InteractiveWindowMessages.NotebookExecutionActivated]: MessageType.other,
114+
[InteractiveWindowMessages.NotebookIdentity]: MessageType.other,
115+
[InteractiveWindowMessages.NotebookRunAllCells]: MessageType.other,
116+
[InteractiveWindowMessages.NotebookRunSelectedCell]: MessageType.other,
117+
[InteractiveWindowMessages.OpenLink]: MessageType.other,
118+
[InteractiveWindowMessages.ProvideCompletionItemsRequest]: MessageType.other,
119+
[InteractiveWindowMessages.ProvideCompletionItemsResponse]: MessageType.other,
120+
[InteractiveWindowMessages.ProvideHoverRequest]: MessageType.other,
121+
[InteractiveWindowMessages.ProvideHoverResponse]: MessageType.other,
122+
[InteractiveWindowMessages.ProvideSignatureHelpRequest]: MessageType.other,
123+
[InteractiveWindowMessages.ProvideSignatureHelpResponse]: MessageType.other,
124+
[InteractiveWindowMessages.ReExecuteCell]: MessageType.other,
125+
[InteractiveWindowMessages.Redo]: MessageType.other,
126+
[InteractiveWindowMessages.RemoteAddCode]: MessageType.other,
127+
[InteractiveWindowMessages.RemoteReexecuteCode]: MessageType.other,
128+
[InteractiveWindowMessages.RemoveCell]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare,
129+
[InteractiveWindowMessages.ResolveCompletionItemRequest]: MessageType.other,
130+
[InteractiveWindowMessages.ResolveCompletionItemResponse]: MessageType.other,
131+
[InteractiveWindowMessages.RestartKernel]: MessageType.other,
132+
[InteractiveWindowMessages.ReturnAllCells]: MessageType.other,
133+
[InteractiveWindowMessages.SaveAll]: MessageType.other,
134+
[InteractiveWindowMessages.SavePng]: MessageType.other,
135+
[InteractiveWindowMessages.ScrollToCell]: MessageType.syncWithLiveShare,
136+
[InteractiveWindowMessages.SelectJupyterServer]: MessageType.other,
137+
[InteractiveWindowMessages.SelectKernel]: MessageType.other,
138+
[InteractiveWindowMessages.SendInfo]: MessageType.other,
139+
[InteractiveWindowMessages.SettingsUpdated]: MessageType.other,
140+
[InteractiveWindowMessages.ShowDataViewer]: MessageType.other,
141+
[InteractiveWindowMessages.ShowPlot]: MessageType.other,
142+
[InteractiveWindowMessages.StartCell]: MessageType.other,
143+
[InteractiveWindowMessages.StartDebugging]: MessageType.other,
144+
[InteractiveWindowMessages.StartProgress]: MessageType.other,
145+
[InteractiveWindowMessages.Started]: MessageType.other,
146+
[InteractiveWindowMessages.StopDebugging]: MessageType.other,
147+
[InteractiveWindowMessages.StopProgress]: MessageType.other,
148+
[InteractiveWindowMessages.SubmitNewCell]: MessageType.other,
149+
[InteractiveWindowMessages.SwapCells]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare,
150+
[InteractiveWindowMessages.Sync]: MessageType.other,
151+
[InteractiveWindowMessages.Undo]: MessageType.other,
152+
[InteractiveWindowMessages.UnfocusedCellEditor]: MessageType.syncWithLiveShare,
153+
[InteractiveWindowMessages.UpdateCell]: MessageType.other,
154+
[InteractiveWindowMessages.UpdateKernel]: MessageType.other,
155+
[InteractiveWindowMessages.VariableExplorerToggle]: MessageType.other,
156+
[InteractiveWindowMessages.VariablesComplete]: MessageType.other,
157+
// Types from CssMessages
158+
[CssMessages.GetCssRequest]: MessageType.other,
159+
[CssMessages.GetCssResponse]: MessageType.other,
160+
[CssMessages.GetMonacoThemeRequest]: MessageType.other,
161+
[CssMessages.GetMonacoThemeResponse]: MessageType.other,
162+
// Types from Shared Messages
163+
[SharedMessages.LocInit]: MessageType.other,
164+
[SharedMessages.Started]: MessageType.other,
165+
[SharedMessages.UpdateSettings]: MessageType.other
166+
};
167+
168+
export function isActionPerformedByUser(action: BaseReduxActionPayload<{}> | BaseReduxActionPayload<never>) {
169+
return action.messageType === undefined;
170+
}
171+
172+
export function shouldRebroadcast(message: keyof IInteractiveWindowMapping): [boolean, MessageType] {
173+
const messageType: MessageType | undefined = messageWithMessageTypes[message];
174+
// Support for liveshare is turned off for now, we can enable that later.
175+
// I.e. we only support synchronizing across editors in the same session.
176+
if (messageType === undefined || (messageType & MessageType.syncAcrossSameNotebooks) !== MessageType.syncAcrossSameNotebooks) {
177+
return [false, MessageType.other];
178+
}
179+
180+
return [(messageType & MessageType.syncAcrossSameNotebooks) > 0 || (messageType & MessageType.syncWithLiveShare) > 0, messageType];
181+
}

src/client/datascience/interactive-common/types.ts

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,12 @@
33

44
'use strict';
55

6+
import { MessageType } from './synchronization';
7+
68
// Stuff common to React and Extensions.
79

810
type BaseData = {
9-
/**
10-
* If this property exists, then this is an action that has been dispatched for the solve purpose of:
11-
* 1. Synchronizing states across different editors (pointing to the same file).
12-
* 2. Synchronizing states across different editors (pointing to the same file) in different sessions.
13-
*
14-
* @type {('syncEditors' | 'syncSessions')}
15-
*/
16-
broadcastReason?: 'syncEditors' | 'syncSessions';
11+
messageType?: MessageType;
1712
/**
1813
* Tells us whether this message is incoming for reducer use or
1914
* whether this is a message that needs to be sent out to extension (from reducer).
@@ -22,14 +17,7 @@ type BaseData = {
2217
};
2318

2419
type BaseDataWithPayload<T> = {
25-
/**
26-
* If this property exists, then this is an action that has been dispatched for the solve purpose of:
27-
* 1. Synchronizing states across different editors (pointing to the same file).
28-
* 2. Synchronizing states across different editors (pointing to the same file) in different sessions.
29-
*
30-
* @type {('syncEditors' | 'syncSessions')}
31-
*/
32-
broadcastReason?: 'syncEditors' | 'syncSessions';
20+
messageType?: MessageType;
3321
/**
3422
* Tells us whether this message is incoming for reducer use or
3523
* whether this is a message that needs to be sent out to extension (from reducer).

0 commit comments

Comments
 (0)