diff --git a/src/client/datascience/interactive-common/interactiveBase.ts b/src/client/datascience/interactive-common/interactiveBase.ts index f365aec71c06..a4522a560d25 100644 --- a/src/client/datascience/interactive-common/interactiveBase.ts +++ b/src/client/datascience/interactive-common/interactiveBase.ts @@ -11,6 +11,7 @@ import { CancellationToken, ConfigurationTarget, Event, EventEmitter, Memento, P import { Disposable } from 'vscode-jsonrpc'; import { ServerStatus } from '../../../datascience-ui/interactive-common/mainState'; +import { CommonActionType } from '../../../datascience-ui/interactive-common/redux/reducers/types'; import { IApplicationShell, ICommandManager, IDocumentManager, ILiveShareApi, IWebPanelProvider, IWorkspaceService } from '../../common/application/types'; import { CancellationError } from '../../common/cancellation'; import { EXTENSION_ROOT_DIR, PYTHON_LANGUAGE } from '../../common/constants'; @@ -43,7 +44,7 @@ import { JupyterInstallError } from '../jupyter/jupyterInstallError'; import { JupyterSelfCertsError } from '../jupyter/jupyterSelfCertsError'; import { JupyterKernelPromiseFailedError } from '../jupyter/kernels/jupyterKernelPromiseFailedError'; import { LiveKernelModel } from '../jupyter/kernels/types'; -import { CssMessages } from '../messages'; +import { CssMessages, SharedMessages } from '../messages'; import { ProgressReporter } from '../progress/progressReporter'; import { CellState, @@ -72,6 +73,7 @@ import { } from '../types'; import { WebViewHost } from '../webViewHost'; import { InteractiveWindowMessageListener } from './interactiveWindowMessageListener'; +import { BaseReduxActionPayload } from './types'; @injectable() export abstract class InteractiveBase extends WebViewHost implements IInteractiveBase { @@ -174,6 +176,11 @@ export abstract class InteractiveBase extends WebViewHost }; + this.postMessageInternal(syncPayload.type, syncPayload.payload).ignoreErrors(); + break; case InteractiveWindowMessages.GotoCodeCell: this.handleMessage(message, payload, this.gotoCode); break; diff --git a/src/client/datascience/interactive-common/interactiveWindowMessageListener.ts b/src/client/datascience/interactive-common/interactiveWindowMessageListener.ts index 7234c41169ba..2dfe92e76597 100644 --- a/src/client/datascience/interactive-common/interactiveWindowMessageListener.ts +++ b/src/client/datascience/interactive-common/interactiveWindowMessageListener.ts @@ -15,6 +15,7 @@ import { InteractiveWindowMessages, InteractiveWindowRemoteMessages } from './in // This class listens to messages that come from the local Python Interactive window export class InteractiveWindowMessageListener implements IWebPanelMessageListener { + private static handlers = new Map void>(); private postOffice: PostOffice; private disposedCallback: () => void; private callback: (message: string, payload: any) => void; @@ -40,6 +41,7 @@ export class InteractiveWindowMessageListener implements IWebPanelMessageListene this.interactiveWindowMessages.forEach(m => { this.postOffice.registerCallback(m, a => callback(m, a)).ignoreErrors(); }); + InteractiveWindowMessageListener.handlers.set(this, callback); } public async dispose() { @@ -48,6 +50,20 @@ export class InteractiveWindowMessageListener implements IWebPanelMessageListene } public onMessage(message: string, payload: any) { + if (message === InteractiveWindowMessages.Sync) { + // const syncPayload = payload as BaseReduxActionPayload; + Array.from(InteractiveWindowMessageListener.handlers.keys()).forEach(item => { + if (item === this) { + return; + } + // Temporarily disabled. + // const cb = InteractiveWindowMessageListener.handlers.get(item); + // if (cb) { + // cb(InteractiveWindowMessages.Sync, { type: message, payload: syncPayload }); + // } + }); + return; + } // We received a message from the local webview. Broadcast it to everybody if it's a remote message if (InteractiveWindowRemoteMessages.indexOf(message) >= 0) { this.postOffice.postCommand(message, payload).ignoreErrors(); diff --git a/src/client/datascience/interactive-common/interactiveWindowTypes.ts b/src/client/datascience/interactive-common/interactiveWindowTypes.ts index fe4641254bf9..d5772c5af964 100644 --- a/src/client/datascience/interactive-common/interactiveWindowTypes.ts +++ b/src/client/datascience/interactive-common/interactiveWindowTypes.ts @@ -3,10 +3,11 @@ 'use strict'; import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; import { IServerState } from '../../../datascience-ui/interactive-common/mainState'; -import { IAddCellAction, ICellAction } from '../../../datascience-ui/interactive-common/redux/reducers/types'; +import { CommonActionType, IAddCellAction, ICellAction } from '../../../datascience-ui/interactive-common/redux/reducers/types'; import { CssMessages, IGetCssRequest, IGetCssResponse, IGetMonacoThemeRequest, SharedMessages } from '../messages'; import { IGetMonacoThemeResponse } from '../monacoMessages'; import { ICell, IInteractiveWindowInfo, IJupyterVariable, IJupyterVariablesRequest, IJupyterVariablesResponse } from '../types'; +import { BaseReduxActionPayload } from './types'; export enum InteractiveWindowMessages { StartCell = 'start_cell', @@ -58,6 +59,7 @@ export enum InteractiveWindowMessages { EditCell = 'edit_cell', RemoveCell = 'remove_cell', SwapCells = 'swap_cells', + Sync = 'sync_message_used_to_broadcast_and_sync_editors', InsertCell = 'insert_cell', LoadOnigasmAssemblyRequest = 'load_onigasm_assembly_request', LoadOnigasmAssemblyResponse = 'load_onigasm_assembly_response', @@ -368,6 +370,8 @@ export class IInteractiveWindowMapping { public [InteractiveWindowMessages.NotebookDirty]: never | undefined; public [InteractiveWindowMessages.NotebookClean]: never | undefined; public [InteractiveWindowMessages.SaveAll]: ISaveAll; + // tslint:disable-next-line: no-any + public [InteractiveWindowMessages.Sync]: { type: InteractiveWindowMessages | SharedMessages | CommonActionType; payload: BaseReduxActionPayload }; public [InteractiveWindowMessages.NativeCommand]: INativeCommand; public [InteractiveWindowMessages.VariablesComplete]: never | undefined; public [InteractiveWindowMessages.NotebookRunAllCells]: never | undefined; diff --git a/src/client/datascience/interactive-common/synchronization.ts b/src/client/datascience/interactive-common/synchronization.ts new file mode 100644 index 000000000000..54c0decb7007 --- /dev/null +++ b/src/client/datascience/interactive-common/synchronization.ts @@ -0,0 +1,181 @@ +import { CommonActionType, CommonActionTypeMapping } from '../../../datascience-ui/interactive-common/redux/reducers/types'; +import { CssMessages, SharedMessages } from '../messages'; +import { IInteractiveWindowMapping, InteractiveWindowMessages } from './interactiveWindowTypes'; +import { BaseReduxActionPayload } from './types'; + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +export enum MessageType { + other = 0, + /** + * Messages must be re-broadcasted across other editors of the same file in the same session. + */ + syncAcrossSameNotebooks = 1 << 0, + /** + * Messages must be re-broadcasted across all sessions. + */ + syncWithLiveShare = 1 << 1, + noIdea = 1 << 2 +} + +type MessageMapping = { + [P in keyof T]: MessageType; +}; + +export type IInteractiveActionMapping = MessageMapping; + +// Do not change to a dictionary or a record. +// The current structure ensures all new enums added will be categorized. +// This way, if a new message is added, we'll make the decision early on whether it needs to be synchronized and how. +// Rather than waiting for users to report issues related to new messages. +const messageWithMessageTypes: MessageMapping & MessageMapping = { + [CommonActionType.ADD_NEW_CELL]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare, + [CommonActionType.ARROW_DOWN]: MessageType.syncWithLiveShare, + [CommonActionType.ARROW_UP]: MessageType.syncWithLiveShare, + [CommonActionType.CHANGE_CELL_TYPE]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare, + [CommonActionType.CLICK_CELL]: MessageType.syncWithLiveShare, + [CommonActionType.CODE_CREATED]: MessageType.noIdea, + [CommonActionType.COPY_CELL_CODE]: MessageType.other, + [CommonActionType.EDITOR_LOADED]: MessageType.other, + [CommonActionType.EDIT_CELL]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare, + [CommonActionType.EXECUTE_ABOVE]: MessageType.other, + [CommonActionType.EXECUTE_ALL_CELLS]: MessageType.other, + [CommonActionType.EXECUTE_CELL]: MessageType.other, + [CommonActionType.EXECUTE_CELL_AND_BELOW]: MessageType.other, + [CommonActionType.EXPORT]: MessageType.other, + [CommonActionType.FOCUS_CELL]: MessageType.syncWithLiveShare, + [CommonActionType.GATHER_CELL]: MessageType.other, + [CommonActionType.GET_VARIABLE_DATA]: MessageType.other, + [CommonActionType.GOTO_CELL]: MessageType.syncWithLiveShare, + [CommonActionType.INSERT_ABOVE]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare, + [CommonActionType.INSERT_ABOVE_FIRST]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare, + [CommonActionType.INSERT_BELOW]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare, + [CommonActionType.INTERRUPT_KERNEL]: MessageType.other, + [CommonActionType.LOADED_ALL_CELLS]: MessageType.other, + [CommonActionType.LINK_CLICK]: MessageType.other, + [CommonActionType.MOVE_CELL_DOWN]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare, + [CommonActionType.MOVE_CELL_UP]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare, + [CommonActionType.RESTART_KERNEL]: MessageType.other, + [CommonActionType.SAVE]: MessageType.other, + [CommonActionType.SCROLL]: MessageType.syncWithLiveShare, + [CommonActionType.SELECT_CELL]: MessageType.syncWithLiveShare, + [CommonActionType.SELECT_SERVER]: MessageType.other, + [CommonActionType.SEND_COMMAND]: MessageType.other, + [CommonActionType.SHOW_DATA_VIEWER]: MessageType.other, + [CommonActionType.SUBMIT_INPUT]: MessageType.other, + [CommonActionType.TOGGLE_INPUT_BLOCK]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare, + [CommonActionType.TOGGLE_LINE_NUMBERS]: MessageType.syncWithLiveShare, + [CommonActionType.TOGGLE_OUTPUT]: MessageType.syncWithLiveShare, + [CommonActionType.TOGGLE_VARIABLE_EXPLORER]: MessageType.syncWithLiveShare, + [CommonActionType.UNFOCUS_CELL]: MessageType.syncWithLiveShare, + [CommonActionType.UNMOUNT]: MessageType.other, + + // Types from InteractiveWindowMessages + [InteractiveWindowMessages.Activate]: MessageType.other, + [InteractiveWindowMessages.AddCell]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare, + [InteractiveWindowMessages.AddedSysInfo]: MessageType.other, + [InteractiveWindowMessages.CancelCompletionItemsRequest]: MessageType.other, + [InteractiveWindowMessages.CancelHoverRequest]: MessageType.other, + [InteractiveWindowMessages.CancelResolveCompletionItemRequest]: MessageType.other, + [InteractiveWindowMessages.CancelSignatureHelpRequest]: MessageType.other, + [InteractiveWindowMessages.ClearAllOutputs]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare, + [InteractiveWindowMessages.CollapseAll]: MessageType.syncWithLiveShare, + [InteractiveWindowMessages.CopyCodeCell]: MessageType.other, + [InteractiveWindowMessages.DeleteAllCells]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare, + [InteractiveWindowMessages.DeleteCell]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare, + [InteractiveWindowMessages.DoSave]: MessageType.other, + [InteractiveWindowMessages.EditCell]: MessageType.other, + [InteractiveWindowMessages.ExecutionRendered]: MessageType.other, + [InteractiveWindowMessages.ExpandAll]: MessageType.syncWithLiveShare, + [InteractiveWindowMessages.Export]: MessageType.other, + [InteractiveWindowMessages.FinishCell]: MessageType.other, + [InteractiveWindowMessages.FocusedCellEditor]: MessageType.syncWithLiveShare, + [InteractiveWindowMessages.GatherCodeRequest]: MessageType.other, + [InteractiveWindowMessages.GetAllCells]: MessageType.other, + [InteractiveWindowMessages.GetVariablesRequest]: MessageType.other, + [InteractiveWindowMessages.GetVariablesResponse]: MessageType.other, + [InteractiveWindowMessages.GotoCodeCell]: MessageType.syncWithLiveShare, + [InteractiveWindowMessages.GotoCodeCell]: MessageType.syncWithLiveShare, + [InteractiveWindowMessages.InsertCell]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare, + [InteractiveWindowMessages.Interrupt]: MessageType.other, + [InteractiveWindowMessages.LoadAllCells]: MessageType.other, + [InteractiveWindowMessages.LoadAllCellsComplete]: MessageType.other, + [InteractiveWindowMessages.LoadOnigasmAssemblyRequest]: MessageType.other, + [InteractiveWindowMessages.LoadOnigasmAssemblyResponse]: MessageType.other, + [InteractiveWindowMessages.LoadTmLanguageRequest]: MessageType.other, + [InteractiveWindowMessages.LoadTmLanguageResponse]: MessageType.other, + [InteractiveWindowMessages.MonacoReady]: MessageType.other, + [InteractiveWindowMessages.NativeCommand]: MessageType.other, + [InteractiveWindowMessages.NotebookAddCellBelow]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare, + [InteractiveWindowMessages.NotebookClean]: MessageType.other, + [InteractiveWindowMessages.NotebookDirty]: MessageType.other, + [InteractiveWindowMessages.NotebookExecutionActivated]: MessageType.other, + [InteractiveWindowMessages.NotebookIdentity]: MessageType.other, + [InteractiveWindowMessages.NotebookRunAllCells]: MessageType.other, + [InteractiveWindowMessages.NotebookRunSelectedCell]: MessageType.other, + [InteractiveWindowMessages.OpenLink]: MessageType.other, + [InteractiveWindowMessages.ProvideCompletionItemsRequest]: MessageType.other, + [InteractiveWindowMessages.ProvideCompletionItemsResponse]: MessageType.other, + [InteractiveWindowMessages.ProvideHoverRequest]: MessageType.other, + [InteractiveWindowMessages.ProvideHoverResponse]: MessageType.other, + [InteractiveWindowMessages.ProvideSignatureHelpRequest]: MessageType.other, + [InteractiveWindowMessages.ProvideSignatureHelpResponse]: MessageType.other, + [InteractiveWindowMessages.ReExecuteCell]: MessageType.other, + [InteractiveWindowMessages.Redo]: MessageType.other, + [InteractiveWindowMessages.RemoteAddCode]: MessageType.other, + [InteractiveWindowMessages.RemoteReexecuteCode]: MessageType.other, + [InteractiveWindowMessages.RemoveCell]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare, + [InteractiveWindowMessages.ResolveCompletionItemRequest]: MessageType.other, + [InteractiveWindowMessages.ResolveCompletionItemResponse]: MessageType.other, + [InteractiveWindowMessages.RestartKernel]: MessageType.other, + [InteractiveWindowMessages.ReturnAllCells]: MessageType.other, + [InteractiveWindowMessages.SaveAll]: MessageType.other, + [InteractiveWindowMessages.SavePng]: MessageType.other, + [InteractiveWindowMessages.ScrollToCell]: MessageType.syncWithLiveShare, + [InteractiveWindowMessages.SelectJupyterServer]: MessageType.other, + [InteractiveWindowMessages.SelectKernel]: MessageType.other, + [InteractiveWindowMessages.SendInfo]: MessageType.other, + [InteractiveWindowMessages.SettingsUpdated]: MessageType.other, + [InteractiveWindowMessages.ShowDataViewer]: MessageType.other, + [InteractiveWindowMessages.ShowPlot]: MessageType.other, + [InteractiveWindowMessages.StartCell]: MessageType.other, + [InteractiveWindowMessages.StartDebugging]: MessageType.other, + [InteractiveWindowMessages.StartProgress]: MessageType.other, + [InteractiveWindowMessages.Started]: MessageType.other, + [InteractiveWindowMessages.StopDebugging]: MessageType.other, + [InteractiveWindowMessages.StopProgress]: MessageType.other, + [InteractiveWindowMessages.SubmitNewCell]: MessageType.other, + [InteractiveWindowMessages.SwapCells]: MessageType.syncAcrossSameNotebooks | MessageType.syncWithLiveShare, + [InteractiveWindowMessages.Sync]: MessageType.other, + [InteractiveWindowMessages.Undo]: MessageType.other, + [InteractiveWindowMessages.UnfocusedCellEditor]: MessageType.syncWithLiveShare, + [InteractiveWindowMessages.UpdateCell]: MessageType.other, + [InteractiveWindowMessages.UpdateKernel]: MessageType.other, + [InteractiveWindowMessages.VariableExplorerToggle]: MessageType.other, + [InteractiveWindowMessages.VariablesComplete]: MessageType.other, + // Types from CssMessages + [CssMessages.GetCssRequest]: MessageType.other, + [CssMessages.GetCssResponse]: MessageType.other, + [CssMessages.GetMonacoThemeRequest]: MessageType.other, + [CssMessages.GetMonacoThemeResponse]: MessageType.other, + // Types from Shared Messages + [SharedMessages.LocInit]: MessageType.other, + [SharedMessages.Started]: MessageType.other, + [SharedMessages.UpdateSettings]: MessageType.other +}; + +export function isActionPerformedByUser(action: BaseReduxActionPayload<{}> | BaseReduxActionPayload) { + return action.messageType === undefined; +} + +export function shouldRebroadcast(message: keyof IInteractiveWindowMapping): [boolean, MessageType] { + const messageType: MessageType | undefined = messageWithMessageTypes[message]; + // Support for liveshare is turned off for now, we can enable that later. + // I.e. we only support synchronizing across editors in the same session. + if (messageType === undefined || (messageType & MessageType.syncAcrossSameNotebooks) !== MessageType.syncAcrossSameNotebooks) { + return [false, MessageType.other]; + } + + return [(messageType & MessageType.syncAcrossSameNotebooks) > 0 || (messageType & MessageType.syncWithLiveShare) > 0, messageType]; +} diff --git a/src/client/datascience/interactive-common/types.ts b/src/client/datascience/interactive-common/types.ts index 590bd60fb178..38b8a48e2cf7 100644 --- a/src/client/datascience/interactive-common/types.ts +++ b/src/client/datascience/interactive-common/types.ts @@ -3,17 +3,12 @@ 'use strict'; +import { MessageType } from './synchronization'; + // Stuff common to React and Extensions. type BaseData = { - /** - * If this property exists, then this is an action that has been dispatched for the solve purpose of: - * 1. Synchronizing states across different editors (pointing to the same file). - * 2. Synchronizing states across different editors (pointing to the same file) in different sessions. - * - * @type {('syncEditors' | 'syncSessions')} - */ - broadcastReason?: 'syncEditors' | 'syncSessions'; + messageType?: MessageType; /** * Tells us whether this message is incoming for reducer use or * whether this is a message that needs to be sent out to extension (from reducer). @@ -22,14 +17,7 @@ type BaseData = { }; type BaseDataWithPayload = { - /** - * If this property exists, then this is an action that has been dispatched for the solve purpose of: - * 1. Synchronizing states across different editors (pointing to the same file). - * 2. Synchronizing states across different editors (pointing to the same file) in different sessions. - * - * @type {('syncEditors' | 'syncSessions')} - */ - broadcastReason?: 'syncEditors' | 'syncSessions'; + messageType?: MessageType; /** * Tells us whether this message is incoming for reducer use or * whether this is a message that needs to be sent out to extension (from reducer). diff --git a/src/datascience-ui/history-react/interactiveCell.tsx b/src/datascience-ui/history-react/interactiveCell.tsx index 8329fcf7867a..ac429959f6b5 100644 --- a/src/datascience-ui/history-react/interactiveCell.tsx +++ b/src/datascience-ui/history-react/interactiveCell.tsx @@ -132,15 +132,7 @@ export class InteractiveCell extends React.Component { // Only render if we are allowed to. if (shouldRender) { return ( -
+
{this.renderControls()}
@@ -209,14 +201,6 @@ export class InteractiveCell extends React.Component { } }; - private onMouseDoubleClick = (ev: React.MouseEvent) => { - // When we receive double click, propagate upwards. Might change our state - if (this.props.doubleClickCell) { - ev.stopPropagation(); - this.props.doubleClickCell(this.props.cellVM.cell.id); - } - }; - private renderControls = () => { const busy = this.props.cellVM.cell.state === CellState.init || this.props.cellVM.cell.state === CellState.executing; const collapseVisible = this.props.cellVM.inputBlockCollapseNeeded && this.props.cellVM.inputBlockShow && !this.props.cellVM.editable && this.isCodeCell(); diff --git a/src/datascience-ui/history-react/redux/actions.ts b/src/datascience-ui/history-react/redux/actions.ts index b6f55eaec82d..905c40e733a2 100644 --- a/src/datascience-ui/history-react/redux/actions.ts +++ b/src/datascience-ui/history-react/redux/actions.ts @@ -3,13 +3,12 @@ 'use strict'; import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; -import { InteractiveWindowMessages, IRefreshVariablesRequest } from '../../../client/datascience/interactive-common/interactiveWindowTypes'; +import { InteractiveWindowMessages } from '../../../client/datascience/interactive-common/interactiveWindowTypes'; import { IJupyterVariable, IJupyterVariablesRequest } from '../../../client/datascience/types'; +import { createIncomingAction, createIncomingActionWithPayload } from '../../interactive-common/redux/helpers'; import { CommonAction, CommonActionType, - createIncomingAction, - createIncomingActionWithPayload, ICellAction, ICodeAction, ICodeCreatedAction, @@ -21,8 +20,6 @@ import { // See https://react-redux.js.org/using-react-redux/connect-mapdispatch#defining-mapdispatchtoprops-as-an-object export const actionCreators = { - refreshVariables: (newExecutionCount?: number): CommonAction => - createIncomingActionWithPayload(CommonActionType.REFRESH_VARIABLES, { newExecutionCount }), restartKernel: (): CommonAction => createIncomingAction(CommonActionType.RESTART_KERNEL), interruptKernel: (): CommonAction => createIncomingAction(CommonActionType.INTERRUPT_KERNEL), deleteAllCells: (): CommonAction => createIncomingAction(InteractiveWindowMessages.DeleteAllCells), @@ -36,7 +33,6 @@ export const actionCreators = { copyCellCode: (cellId: string): CommonAction => createIncomingActionWithPayload(CommonActionType.COPY_CELL_CODE, { cellId }), gatherCell: (cellId: string): CommonAction => createIncomingActionWithPayload(CommonActionType.GATHER_CELL, { cellId }), clickCell: (cellId: string): CommonAction => createIncomingActionWithPayload(CommonActionType.CLICK_CELL, { cellId }), - doubleClickCell: (cellId: string): CommonAction => createIncomingActionWithPayload(CommonActionType.DOUBLE_CLICK_CELL, { cellId }), editCell: (cellId: string, changes: monacoEditor.editor.IModelContentChange[], modelId: string, code: string): CommonAction => createIncomingActionWithPayload(CommonActionType.EDIT_CELL, { cellId, changes, modelId, code }), submitInput: (code: string, cellId: string): CommonAction => createIncomingActionWithPayload(CommonActionType.SUBMIT_INPUT, { code, cellId }), diff --git a/src/datascience-ui/history-react/redux/reducers/creation.ts b/src/datascience-ui/history-react/redux/reducers/creation.ts index 607349a9fbdf..e0684a2dbd68 100644 --- a/src/datascience-ui/history-react/redux/reducers/creation.ts +++ b/src/datascience-ui/history-react/redux/reducers/creation.ts @@ -5,7 +5,7 @@ import { Identifiers } from '../../../../client/datascience/constants'; import { InteractiveWindowMessages } from '../../../../client/datascience/interactive-common/interactiveWindowTypes'; import { ICell, IDataScienceExtraSettings } from '../../../../client/datascience/types'; import { createCellVM, extractInputText, ICellViewModel, IMainState } from '../../../interactive-common/mainState'; -import { createPostableAction } from '../../../interactive-common/redux/postOffice'; +import { createPostableAction } from '../../../interactive-common/redux/helpers'; import { Helpers } from '../../../interactive-common/redux/reducers/helpers'; import { IAddCellAction, ICellAction } from '../../../interactive-common/redux/reducers/types'; import { InteractiveReducerArg } from '../mapping'; diff --git a/src/datascience-ui/history-react/redux/reducers/effects.ts b/src/datascience-ui/history-react/redux/reducers/effects.ts index 9d6148d53de3..5cb22db9ae94 100644 --- a/src/datascience-ui/history-react/redux/reducers/effects.ts +++ b/src/datascience-ui/history-react/redux/reducers/effects.ts @@ -6,7 +6,7 @@ import { IScrollToCell } from '../../../../client/datascience/interactive-common import { CssMessages } from '../../../../client/datascience/messages'; import { IDataScienceExtraSettings } from '../../../../client/datascience/types'; import { IMainState } from '../../../interactive-common/mainState'; -import { createPostableAction } from '../../../interactive-common/redux/postOffice'; +import { createPostableAction } from '../../../interactive-common/redux/helpers'; import { Helpers } from '../../../interactive-common/redux/reducers/helpers'; import { ICellAction, IScrollAction } from '../../../interactive-common/redux/reducers/types'; import { computeEditorOptions } from '../../../react-common/settingsReactSide'; diff --git a/src/datascience-ui/history-react/redux/reducers/execution.ts b/src/datascience-ui/history-react/redux/reducers/execution.ts index 140db015aab9..407911183a67 100644 --- a/src/datascience-ui/history-react/redux/reducers/execution.ts +++ b/src/datascience-ui/history-react/redux/reducers/execution.ts @@ -11,7 +11,7 @@ import { CellState } from '../../../../client/datascience/types'; import { generateMarkdownFromCodeLines } from '../../../common'; import { createCellFrom } from '../../../common/cellFactory'; import { createCellVM, IMainState } from '../../../interactive-common/mainState'; -import { createPostableAction } from '../../../interactive-common/redux/postOffice'; +import { createPostableAction } from '../../../interactive-common/redux/helpers'; import { Helpers } from '../../../interactive-common/redux/reducers/helpers'; import { ICodeAction } from '../../../interactive-common/redux/reducers/types'; import { InteractiveReducerArg } from '../mapping'; diff --git a/src/datascience-ui/interactive-common/redux/helpers.ts b/src/datascience-ui/interactive-common/redux/helpers.ts new file mode 100644 index 000000000000..685b3c251d96 --- /dev/null +++ b/src/datascience-ui/interactive-common/redux/helpers.ts @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import * as Redux from 'redux'; +import { IInteractiveWindowMapping, InteractiveWindowMessages } from '../../../client/datascience/interactive-common/interactiveWindowTypes'; +import { BaseReduxActionPayload } from '../../../client/datascience/interactive-common/types'; +import { CssMessages, SharedMessages } from '../../../client/datascience/messages'; +import { CommonAction, CommonActionType } from './reducers/types'; + +const AllowedMessages = [...Object.values(InteractiveWindowMessages), ...Object.values(CssMessages), ...Object.values(SharedMessages), ...Object.values(CommonActionType)]; +export function isAllowedMessage(message: string) { + // tslint:disable-next-line: no-any + return AllowedMessages.includes(message as any); +} +export function isAllowedAction(action: Redux.AnyAction) { + return isAllowedMessage(action.type); +} + +export function createIncomingActionWithPayload(type: CommonActionType | InteractiveWindowMessages, data: T): CommonAction { + // tslint:disable-next-line: no-any + return { type, payload: ({ data, messageDirection: 'incoming' } as any) as BaseReduxActionPayload }; +} +export function createIncomingAction(type: CommonActionType | InteractiveWindowMessages): CommonAction { + return { type, payload: { messageDirection: 'incoming', data: undefined } }; +} + +// Actions created from messages +export function createPostableAction(message: T, payload?: M[T]): Redux.AnyAction { + const newPayload: BaseReduxActionPayload = ({ + data: payload, + messageDirection: 'outgoing' + // tslint:disable-next-line: no-any + } as any) as BaseReduxActionPayload; + return { type: CommonActionType.PostOutgoingMessage, payload: { payload: newPayload, type: message } }; +} +export function unwrapPostableAction(action: Redux.AnyAction): { type: keyof IInteractiveWindowMapping; payload?: BaseReduxActionPayload<{}> } { + // Unwrap the payload that was created in `createPostableAction`. + const type = action.type; + const payload: BaseReduxActionPayload<{}> | undefined = action.payload; + return { type, payload }; +} + +export function reBroadcastMessageIfRequired( + _dispatcher: Function, + message: InteractiveWindowMessages | SharedMessages | CommonActionType | CssMessages, + payload?: BaseReduxActionPayload<{}> +) { + if (typeof payload?.messageType === 'number' || message === InteractiveWindowMessages.Sync) { + return; + } + if (payload?.messageDirection === 'outgoing') { + return; + } + // Temporarily disabled. + // // Check if we need to re-broadcast this message to other editors/sessions. + // // tslint:disable-next-line: no-any + // const result = shouldRebroadcast(message as any); + // if (result[0]) { + // // Mark message as incoming, to indicate this will be sent into the other webviews. + // // tslint:disable-next-line: no-any + // const syncPayloadData: BaseReduxActionPayload = { data: payload?.data, messageType: result[1], messageDirection: 'incoming' }; + // // tslint:disable-next-line: no-any + // const syncPayload = { type: message, payload: syncPayloadData } as any; + // // Send this out. + // dispatcher(InteractiveWindowMessages.Sync, syncPayload); + // } +} diff --git a/src/datascience-ui/interactive-common/redux/postOffice.ts b/src/datascience-ui/interactive-common/redux/postOffice.ts index 12cee1739faf..a7831b4eeb8e 100644 --- a/src/datascience-ui/interactive-common/redux/postOffice.ts +++ b/src/datascience-ui/interactive-common/redux/postOffice.ts @@ -3,36 +3,32 @@ 'use strict'; import * as Redux from 'redux'; -import { IInteractiveWindowMapping, InteractiveWindowMessages } from '../../../client/datascience/interactive-common/interactiveWindowTypes'; +import { IInteractiveWindowMapping } from '../../../client/datascience/interactive-common/interactiveWindowTypes'; import { BaseReduxActionPayload } from '../../../client/datascience/interactive-common/types'; -import { CssMessages, SharedMessages } from '../../../client/datascience/messages'; import { PostOffice } from '../../react-common/postOffice'; +import { isAllowedAction, reBroadcastMessageIfRequired, unwrapPostableAction } from './helpers'; import { CommonActionType } from './reducers/types'; -export const AllowedMessages = [...Object.values(InteractiveWindowMessages), ...Object.values(CssMessages), ...Object.values(SharedMessages)]; - -// Actions created from messages -export function createPostableAction(message: T, payload?: M[T]): Redux.AnyAction { - const newPayload: BaseReduxActionPayload = ({ - data: payload, - messageDirection: 'outgoing' - // tslint:disable-next-line: no-any - } as any) as BaseReduxActionPayload; - return { type: CommonActionType.PostOutgoingMessage, payload: { payload: newPayload, type: message } }; -} - export function generatePostOfficeSendReducer(postOffice: PostOffice): Redux.Reducer<{}, Redux.AnyAction> { // tslint:disable-next-line: no-function-expression return function(_state: {} | undefined, action: Redux.AnyAction): {} { - // Make sure a valid message - if (action.type === CommonActionType.PostOutgoingMessage) { - // Unwrap the payload that was created in `createPostableAction`. - const type = action.payload.type; - const payload: BaseReduxActionPayload<{}> | undefined = action.payload.payload; - if (AllowedMessages.find(k => k === type) && payload?.messageDirection === 'outgoing') { + if (isAllowedAction(action)) { + // Make sure a valid message + if (action.type === CommonActionType.PostOutgoingMessage) { + const { type, payload } = unwrapPostableAction(action.payload); // Just post this to the post office. // tslint:disable-next-line: no-any - postOffice.sendMessage(type, payload.data as any); + postOffice.sendMessage(type, payload?.data as any); + } else { + const payload: BaseReduxActionPayload<{}> | undefined = action.payload; + // Do not rebroadcast messages that have been sent through as part of a synchronization packet. + // If `messageType` is a number, then its some part of a synchronization packet. + if (payload?.messageDirection === 'incoming' && typeof payload?.messageType !== 'number') { + // We can delay this, first focus on UX perf. + setTimeout(() => { + reBroadcastMessageIfRequired(postOffice.sendMessage.bind(postOffice), action.type, action?.payload); + }, 1); + } } } diff --git a/src/datascience-ui/interactive-common/redux/reducers/kernel.ts b/src/datascience-ui/interactive-common/redux/reducers/kernel.ts index 7acface3c37a..924a2489450b 100644 --- a/src/datascience-ui/interactive-common/redux/reducers/kernel.ts +++ b/src/datascience-ui/interactive-common/redux/reducers/kernel.ts @@ -4,7 +4,7 @@ import { InteractiveWindowMessages } from '../../../../client/datascience/interactive-common/interactiveWindowTypes'; import { CellState } from '../../../../client/datascience/types'; import { IMainState, IServerState } from '../../mainState'; -import { createPostableAction } from '../postOffice'; +import { createPostableAction } from '../helpers'; import { CommonActionType, CommonReducerArg } from './types'; export namespace Kernel { diff --git a/src/datascience-ui/interactive-common/redux/reducers/monaco.ts b/src/datascience-ui/interactive-common/redux/reducers/monaco.ts index 08d1558ed628..fc559bcaacfb 100644 --- a/src/datascience-ui/interactive-common/redux/reducers/monaco.ts +++ b/src/datascience-ui/interactive-common/redux/reducers/monaco.ts @@ -20,7 +20,8 @@ import { PostOffice } from '../../../react-common/postOffice'; import { combineReducers, QueuableAction, ReducerArg, ReducerFunc } from '../../../react-common/reduxUtils'; import { IntellisenseProvider } from '../../intellisenseProvider'; import { initializeTokenizer, registerMonacoLanguage } from '../../tokenizer'; -import { CommonActionType, createIncomingAction, ICodeCreatedAction, IEditCellAction } from './types'; +import { createIncomingAction } from '../helpers'; +import { CommonActionType, ICodeCreatedAction, IEditCellAction } from './types'; export interface IMonacoState { onigasmData: ArrayBuffer | undefined; diff --git a/src/datascience-ui/interactive-common/redux/reducers/transfer.ts b/src/datascience-ui/interactive-common/redux/reducers/transfer.ts index ce9852cb33e6..5aa4215e610e 100644 --- a/src/datascience-ui/interactive-common/redux/reducers/transfer.ts +++ b/src/datascience-ui/interactive-common/redux/reducers/transfer.ts @@ -4,7 +4,7 @@ import { InteractiveWindowMessages } from '../../../../client/datascience/interactive-common/interactiveWindowTypes'; import { CssMessages } from '../../../../client/datascience/messages'; import { extractInputText, getSelectedAndFocusedInfo, IMainState } from '../../mainState'; -import { createPostableAction } from '../postOffice'; +import { createPostableAction } from '../helpers'; import { Helpers } from './helpers'; import { CommonActionType, CommonReducerArg, ICellAction, IEditCellAction, ILinkClickAction, ISendCommandAction, IShowDataViewerAction } from './types'; diff --git a/src/datascience-ui/interactive-common/redux/reducers/types.ts b/src/datascience-ui/interactive-common/redux/reducers/types.ts index e30888cbd7a7..58bfdf71ed3f 100644 --- a/src/datascience-ui/interactive-common/redux/reducers/types.ts +++ b/src/datascience-ui/interactive-common/redux/reducers/types.ts @@ -29,8 +29,6 @@ export enum CommonActionType { CLICK_CELL = 'action.click_cell', CODE_CREATED = 'action.code_created', COPY_CELL_CODE = 'action.copy_cell_code', - DESELECT_CELL = 'action.deselect_cell', - DOUBLE_CLICK_CELL = 'action.double_click_cell', EDITOR_LOADED = 'action.editor_loaded', EDIT_CELL = 'action.edit_cell', EXECUTE_ABOVE = 'action.execute_above', @@ -180,11 +178,3 @@ export interface IChangeCellTypeAction { currentCode: string; } export type CommonAction = ActionWithPayload; - -export function createIncomingActionWithPayload(type: CommonActionType | InteractiveWindowMessages, data: T): CommonAction { - // tslint:disable-next-line: no-any - return { type, payload: ({ data, messageDirection: 'incoming' } as any) as BaseReduxActionPayload }; -} -export function createIncomingAction(type: CommonActionType | InteractiveWindowMessages): CommonAction { - return { type, payload: { messageDirection: 'incoming', data: undefined } }; -} diff --git a/src/datascience-ui/interactive-common/redux/reducers/variables.ts b/src/datascience-ui/interactive-common/redux/reducers/variables.ts index 6f51205a2d85..ad4120e6705d 100644 --- a/src/datascience-ui/interactive-common/redux/reducers/variables.ts +++ b/src/datascience-ui/interactive-common/redux/reducers/variables.ts @@ -6,7 +6,7 @@ import { IInteractiveWindowMapping, InteractiveWindowMessages } from '../../../. import { BaseReduxActionPayload } from '../../../../client/datascience/interactive-common/types'; import { ICell, IJupyterVariable, IJupyterVariablesRequest, IJupyterVariablesResponse } from '../../../../client/datascience/types'; import { combineReducers, QueuableAction, ReducerArg, ReducerFunc } from '../../../react-common/reduxUtils'; -import { createPostableAction } from '../postOffice'; +import { createPostableAction } from '../helpers'; import { CommonActionType, CommonActionTypeMapping } from './types'; export type IVariableState = { diff --git a/src/datascience-ui/interactive-common/redux/store.ts b/src/datascience-ui/interactive-common/redux/store.ts index b05e6e88fc95..e00aaefde666 100644 --- a/src/datascience-ui/interactive-common/redux/store.ts +++ b/src/datascience-ui/interactive-common/redux/store.ts @@ -16,7 +16,8 @@ import { combineReducers, createQueueableActionMiddleware, QueuableAction } from import { computeEditorOptions, getDefaultSettings } from '../../react-common/settingsReactSide'; import { createEditableCellVM, generateTestState } from '../mainState'; import { forceLoad } from '../transforms'; -import { AllowedMessages, createPostableAction, generatePostOfficeSendReducer } from './postOffice'; +import { createPostableAction, isAllowedAction, isAllowedMessage } from './helpers'; +import { generatePostOfficeSendReducer } from './postOffice'; import { generateMonacoReducer, IMonacoState } from './reducers/monaco'; import { generateVariableReducer, IVariableState } from './reducers/variables'; @@ -72,6 +73,11 @@ function createSendInfoMiddleware(): Redux.Middleware<{}, IStore> { const res = next(action); const afterState = store.getState(); + // If the action is part of a sync message, then do not send it to the extension. + if (action.payload && typeof (action.payload as BaseReduxActionPayload).messageType === 'number') { + return res; + } + // If cell vm count changed or selected cell changed, send the message const currentSelection = getSelectedAndFocusedInfo(afterState.main); if ( @@ -232,6 +238,21 @@ export interface IMainWithVariables extends IMainState { variableState: IVariableState; } +/** + * Middleware that will ensure all actions have `messageDirection` property. + */ +const addMessageDirectionMiddleware: Redux.Middleware = _store => next => (action: Redux.AnyAction) => { + if (isAllowedAction(action)) { + // Ensure all dispatched messages have been flagged as `incoming`. + const payload: BaseReduxActionPayload<{}> = action.payload || {}; + if (!payload.messageDirection) { + action.payload = { ...payload, messageDirection: 'incoming' }; + } + } + + return next(action); +}; + export function createStore(skipDefault: boolean, baseTheme: string, testMode: boolean, editable: boolean, reducerMap: M) { // Create a post office to listen to store dispatches and allow reducers to // send messages @@ -258,7 +279,7 @@ export function createStore(skipDefault: boolean, baseTheme: string, testMode }); // Create our middleware - const middleware = createMiddleWare(testMode); + const middleware = createMiddleWare(testMode).concat([addMessageDirectionMiddleware]); // Use this reducer and middle ware to create a store const store = Redux.createStore(rootReducer, Redux.applyMiddleware(...middleware)); @@ -269,21 +290,17 @@ export function createStore(skipDefault: boolean, baseTheme: string, testMode // tslint:disable-next-line: no-any handleMessage(message: string, payload?: any): boolean { // Double check this is one of our messages. React will actually post messages here too during development - if (!AllowedMessages.find(k => k === message)) { - return true; - } - // Add `isIncomingMessage` property so we can differentiate between messages in reducers. - // This way we: - // - Have one reducer for incoming - // - Have another reducer for outgoing - let basePayload = payload as BaseReduxActionPayload<{}> | undefined; - if (!basePayload?.broadcastReason && !basePayload?.messageDirection) { - // Re-wrap to indicate this is an incoming message. - basePayload = { data: payload, messageDirection: 'incoming' }; - } - if (AllowedMessages.find(k => k === message) && basePayload.messageDirection !== 'outgoing') { + if (isAllowedMessage(message)) { + const basePayload: BaseReduxActionPayload = { data: payload }; + if (message === InteractiveWindowMessages.Sync) { + // Unwrap the message. + message = payload.type; + basePayload.messageType = payload.payload.messageType; + basePayload.data = payload.payload.data; + } store.dispatch({ type: message, payload: basePayload }); } + return true; } }); diff --git a/src/datascience-ui/native-editor/redux/actions.ts b/src/datascience-ui/native-editor/redux/actions.ts index 16a38da4af97..8619a0db3e50 100644 --- a/src/datascience-ui/native-editor/redux/actions.ts +++ b/src/datascience-ui/native-editor/redux/actions.ts @@ -6,11 +6,10 @@ import * as uuid from 'uuid/v4'; import { InteractiveWindowMessages, NativeCommandType } from '../../../client/datascience/interactive-common/interactiveWindowTypes'; import { IJupyterVariable, IJupyterVariablesRequest } from '../../../client/datascience/types'; import { CursorPos } from '../../interactive-common/mainState'; +import { createIncomingAction, createIncomingActionWithPayload } from '../../interactive-common/redux/helpers'; import { CommonAction, CommonActionType, - createIncomingAction, - createIncomingActionWithPayload, IAddCellAction, ICellAction, ICellAndCursorAction, @@ -20,7 +19,6 @@ import { IEditCellAction, IExecuteAction, ILinkClickAction, - IRefreshVariablesAction, ISendCommandAction, IShowDataViewerAction } from '../../interactive-common/redux/reducers/types'; @@ -49,8 +47,6 @@ export const actionCreators = { executeAbove: (cellId: string): CommonAction => createIncomingActionWithPayload(CommonActionType.EXECUTE_ABOVE, { cellId }), executeCellAndBelow: (cellId: string, code: string): CommonAction => createIncomingActionWithPayload(CommonActionType.EXECUTE_CELL_AND_BELOW, { cellId, code }), toggleVariableExplorer: (): CommonAction => createIncomingAction(CommonActionType.TOGGLE_VARIABLE_EXPLORER), - refreshVariables: (newExecutionCount?: number): CommonAction => - createIncomingActionWithPayload(CommonActionType.REFRESH_VARIABLES, { newExecutionCount }), restartKernel: (): CommonAction => createIncomingAction(CommonActionType.RESTART_KERNEL), interruptKernel: (): CommonAction => createIncomingAction(CommonActionType.INTERRUPT_KERNEL), clearAllOutputs: (): CommonAction => createIncomingAction(InteractiveWindowMessages.ClearAllOutputs), diff --git a/src/datascience-ui/native-editor/redux/reducers/creation.ts b/src/datascience-ui/native-editor/redux/reducers/creation.ts index bb1bda4e125d..2aa41b26a987 100644 --- a/src/datascience-ui/native-editor/redux/reducers/creation.ts +++ b/src/datascience-ui/native-editor/redux/reducers/creation.ts @@ -5,7 +5,7 @@ import { ILoadAllCells, InteractiveWindowMessages } from '../../../../client/datascience/interactive-common/interactiveWindowTypes'; import { ICell, IDataScienceExtraSettings } from '../../../../client/datascience/types'; import { createCellVM, createEmptyCell, CursorPos, extractInputText, getSelectedAndFocusedInfo, ICellViewModel, IMainState } from '../../../interactive-common/mainState'; -import { createPostableAction } from '../../../interactive-common/redux/postOffice'; +import { createPostableAction } from '../../../interactive-common/redux/helpers'; import { Helpers } from '../../../interactive-common/redux/reducers/helpers'; import { IAddCellAction, ICellAction } from '../../../interactive-common/redux/reducers/types'; import { actionCreators } from '../actions'; diff --git a/src/datascience-ui/native-editor/redux/reducers/effects.ts b/src/datascience-ui/native-editor/redux/reducers/effects.ts index 117945eb3deb..6bfa022ef4c8 100644 --- a/src/datascience-ui/native-editor/redux/reducers/effects.ts +++ b/src/datascience-ui/native-editor/redux/reducers/effects.ts @@ -4,7 +4,7 @@ import { CssMessages } from '../../../../client/datascience/messages'; import { IDataScienceExtraSettings } from '../../../../client/datascience/types'; import { getSelectedAndFocusedInfo, IMainState } from '../../../interactive-common/mainState'; -import { createPostableAction } from '../../../interactive-common/redux/postOffice'; +import { createPostableAction } from '../../../interactive-common/redux/helpers'; import { Helpers } from '../../../interactive-common/redux/reducers/helpers'; import { ICellAction, ICellAndCursorAction, ICodeAction } from '../../../interactive-common/redux/reducers/types'; import { computeEditorOptions } from '../../../react-common/settingsReactSide'; diff --git a/src/datascience-ui/native-editor/redux/reducers/execution.ts b/src/datascience-ui/native-editor/redux/reducers/execution.ts index 129378e289f0..bfeb0ebbd176 100644 --- a/src/datascience-ui/native-editor/redux/reducers/execution.ts +++ b/src/datascience-ui/native-editor/redux/reducers/execution.ts @@ -9,7 +9,7 @@ import { CellState } from '../../../../client/datascience/types'; import { concatMultilineStringInput } from '../../../common'; import { createCellFrom } from '../../../common/cellFactory'; import { CursorPos, getSelectedAndFocusedInfo, ICellViewModel, IMainState } from '../../../interactive-common/mainState'; -import { createPostableAction } from '../../../interactive-common/redux/postOffice'; +import { createPostableAction } from '../../../interactive-common/redux/helpers'; import { Helpers } from '../../../interactive-common/redux/reducers/helpers'; import { CommonActionType, ICellAction, IChangeCellTypeAction, ICodeAction, IExecuteAction } from '../../../interactive-common/redux/reducers/types'; import { QueueAnotherFunc } from '../../../react-common/reduxUtils'; diff --git a/src/datascience-ui/native-editor/redux/reducers/movement.ts b/src/datascience-ui/native-editor/redux/reducers/movement.ts index 8f0b19ec8ee9..b21e8a8ab1f9 100644 --- a/src/datascience-ui/native-editor/redux/reducers/movement.ts +++ b/src/datascience-ui/native-editor/redux/reducers/movement.ts @@ -3,7 +3,7 @@ 'use strict'; import { InteractiveWindowMessages } from '../../../../client/datascience/interactive-common/interactiveWindowTypes'; import { CursorPos, IMainState } from '../../../interactive-common/mainState'; -import { createPostableAction } from '../../../interactive-common/redux/postOffice'; +import { createPostableAction } from '../../../interactive-common/redux/helpers'; import { Helpers } from '../../../interactive-common/redux/reducers/helpers'; import { ICellAction, ICodeAction } from '../../../interactive-common/redux/reducers/types'; import { NativeEditorReducerArg } from '../mapping';