/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { Registry } from 'vs/platform/registry/common/platform'; import { EditorDescriptor, IEditorRegistry } from 'vs/workbench/browser/editor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { localize } from 'vs/nls'; import { IEditorInputFactoryRegistry, ActiveEditorContext, IEditorInput, EditorExtensions } from 'vs/workbench/common/editor'; import { ILanguageAssociationRegistry, Extensions as LanguageAssociationExtensions } from 'sql/workbench/services/languageAssociation/common/languageAssociation'; import { UntitledNotebookInput } from 'sql/workbench/contrib/notebook/browser/models/untitledNotebookInput'; import { FileNotebookInput } from 'sql/workbench/contrib/notebook/browser/models/fileNotebookInput'; import { FileNoteBookEditorInputSerializer, NotebookEditorInputAssociation, UntitledNotebookEditorInputSerializer } from 'sql/workbench/contrib/notebook/browser/models/notebookInputFactory'; import { IWorkbenchActionRegistry, Extensions as WorkbenchActionsExtensions } from 'vs/workbench/common/actions'; import { SyncActionDescriptor, registerAction2, MenuRegistry, MenuId, Action2 } from 'vs/platform/actions/common/actions'; import { NotebookEditor } from 'sql/workbench/contrib/notebook/browser/notebookEditor'; import { NewNotebookAction } from 'sql/workbench/contrib/notebook/browser/notebookActions'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { IConfigurationRegistry, Extensions as ConfigExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { GridOutputComponent } from 'sql/workbench/contrib/notebook/browser/outputs/gridOutput.component'; import { PlotlyOutputComponent } from 'sql/workbench/contrib/notebook/browser/outputs/plotlyOutput.component'; import { registerComponentType } from 'sql/workbench/contrib/notebook/browser/outputs/mimeRegistry'; import { MimeRendererComponent } from 'sql/workbench/contrib/notebook/browser/outputs/mimeRenderer.component'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { URI } from 'vs/base/common/uri'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { NodeContextKey } from 'sql/workbench/contrib/views/browser/nodeContext'; import { MssqlNodeContext } from 'sql/workbench/services/objectExplorer/browser/mssqlNodeContext'; import { mssqlProviderName } from 'sql/platform/connection/common/constants'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { TreeViewItemHandleArg } from 'sql/workbench/common/views'; import { ConnectedContext, nb } from 'azdata'; import { TreeNodeContextKey } from 'sql/workbench/services/objectExplorer/common/treeNodeContextKey'; import { ObjectExplorerActionsContext } from 'sql/workbench/services/objectExplorer/browser/objectExplorerActions'; import { ItemContextKey } from 'sql/workbench/contrib/dashboard/browser/widgets/explorer/explorerContext'; import { ManageActionContext } from 'sql/workbench/browser/actions'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { MarkdownOutputComponent } from 'sql/workbench/contrib/notebook/browser/outputs/markdownOutput.component'; import { registerCellComponent } from 'sql/platform/notebooks/common/outputRegistry'; import { TextCellComponent } from 'sql/workbench/contrib/notebook/browser/cellViews/textCell.component'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { NotebookThemingContribution } from 'sql/workbench/contrib/notebook/browser/notebookThemingContribution'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { ToggleTabFocusModeAction } from 'vs/editor/contrib/toggleTabFocusMode/toggleTabFocusMode'; import 'vs/css!./media/notebook.contribution'; import { isMacintosh } from 'vs/base/common/platform'; import { SearchSortOrder } from 'vs/workbench/services/search/common/search'; import { ImageMimeTypes, TextCellEditModes } from 'sql/workbench/services/notebook/common/contracts'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { NotebookInput } from 'sql/workbench/contrib/notebook/browser/models/notebookInput'; import { INotebookModel } from 'sql/workbench/services/notebook/browser/models/modelInterfaces'; import { IExecuteManager } from 'sql/workbench/services/notebook/browser/notebookService'; import { NotebookExplorerViewletViewsContribution } from 'sql/workbench/contrib/notebook/browser/notebookExplorer/notebookExplorerViewlet'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { ContributedEditorPriority, IEditorOverrideService } from 'vs/workbench/services/editor/common/editorOverrideService'; import { FileEditorInput } from 'vs/workbench/contrib/files/browser/editors/fileEditorInput'; import { IModeService } from 'vs/editor/common/services/modeService'; import { ILogService } from 'vs/platform/log/common/log'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { useNewMarkdownRendererKey } from 'sql/workbench/contrib/notebook/common/notebookCommon'; import { JUPYTER_PROVIDER_ID } from 'sql/workbench/common/constants'; Registry.as(EditorExtensions.EditorInputFactories) .registerEditorInputSerializer(FileNotebookInput.ID, FileNoteBookEditorInputSerializer); Registry.as(EditorExtensions.EditorInputFactories) .registerEditorInputSerializer(UntitledNotebookInput.ID, UntitledNotebookEditorInputSerializer); Registry.as(LanguageAssociationExtensions.LanguageAssociations) .registerLanguageAssociation(NotebookEditorInputAssociation.languages, NotebookEditorInputAssociation); Registry.as(EditorExtensions.Editors) .registerEditor(EditorDescriptor.create(NotebookEditor, NotebookEditor.ID, NotebookEditor.LABEL), [new SyncDescriptor(UntitledNotebookInput), new SyncDescriptor(FileNotebookInput)]); Registry.as(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(NotebookThemingContribution, LifecyclePhase.Restored); // Global Actions const actionRegistry = Registry.as(WorkbenchActionsExtensions.WorkbenchActions); actionRegistry.registerWorkbenchAction( SyncActionDescriptor.create( NewNotebookAction, NewNotebookAction.ID, NewNotebookAction.LABEL, { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.KEY_N }, ), NewNotebookAction.LABEL ); const DE_NEW_NOTEBOOK_COMMAND_ID = 'dataExplorer.newNotebook'; // New Notebook CommandsRegistry.registerCommand({ id: DE_NEW_NOTEBOOK_COMMAND_ID, handler: (accessor, args: TreeViewItemHandleArg) => { const instantiationService = accessor.get(IInstantiationService); const connectedContext: ConnectedContext = { connectionProfile: args.$treeItem.payload }; return instantiationService.createInstance(NewNotebookAction, NewNotebookAction.ID, NewNotebookAction.LABEL).run({ connectionProfile: connectedContext.connectionProfile, isConnectionNode: false, nodeInfo: undefined }); } }); // New Notebook MenuRegistry.appendMenuItem(MenuId.DataExplorerContext, { group: '0_query', order: 3, command: { id: DE_NEW_NOTEBOOK_COMMAND_ID, title: localize('newNotebook', "New Notebook") }, when: ContextKeyExpr.and(NodeContextKey.IsConnectable, MssqlNodeContext.IsDatabaseOrServer, MssqlNodeContext.NodeProvider.isEqualTo(mssqlProviderName)) }); const OE_NEW_NOTEBOOK_COMMAND_ID = 'objectExplorer.newNotebook'; // New Notebook CommandsRegistry.registerCommand({ id: OE_NEW_NOTEBOOK_COMMAND_ID, handler: (accessor, actionContext: ObjectExplorerActionsContext) => { const instantiationService = accessor.get(IInstantiationService); return instantiationService.createInstance(NewNotebookAction, NewNotebookAction.ID, NewNotebookAction.LABEL).run(actionContext); } }); MenuRegistry.appendMenuItem(MenuId.ObjectExplorerItemContext, { group: '0_query', order: 3, command: { id: OE_NEW_NOTEBOOK_COMMAND_ID, title: localize('newQuery', "New Notebook") }, when: ContextKeyExpr.and(TreeNodeContextKey.Status.notEqualsTo('Unavailable'), TreeNodeContextKey.IsQueryProvider.isEqualTo(true), ContextKeyExpr.or(TreeNodeContextKey.NodeType.isEqualTo('Server'), TreeNodeContextKey.NodeType.isEqualTo('Database'))) }); const ExplorerNotebookActionID = 'explorer.notebook'; CommandsRegistry.registerCommand(ExplorerNotebookActionID, (accessor, context: ManageActionContext) => { const instantiationService = accessor.get(IInstantiationService); const connectedContext: ConnectedContext = { connectionProfile: context.profile }; instantiationService.createInstance(NewNotebookAction, NewNotebookAction.ID, NewNotebookAction.LABEL).run({ connectionProfile: connectedContext.connectionProfile, isConnectionNode: false, nodeInfo: undefined }); }); MenuRegistry.appendMenuItem(MenuId.ExplorerWidgetContext, { command: { id: ExplorerNotebookActionID, title: NewNotebookAction.LABEL }, when: ItemContextKey.ItemType.isEqualTo('database'), order: 1 }); const TOGGLE_TAB_FOCUS_COMMAND_ID = 'notebook.action.toggleTabFocusMode'; const toggleTabFocusAction = new ToggleTabFocusModeAction(); CommandsRegistry.registerCommand({ id: TOGGLE_TAB_FOCUS_COMMAND_ID, handler: (accessor) => { toggleTabFocusAction.run(accessor, undefined); } }); const LAUNCH_FIND_IN_NOTEBOOK = 'notebook.action.launchFindInNotebook'; CommandsRegistry.registerCommand({ id: LAUNCH_FIND_IN_NOTEBOOK, handler: async (accessor: ServicesAccessor, searchTerm: string) => { const notebookEditor = accessor.get(IEditorService).activeEditorPane; if (notebookEditor instanceof NotebookEditor) { if (notebookEditor) { await notebookEditor.setNotebookModel(); await notebookEditor.launchFind(searchTerm); } } } }); const RESTART_JUPYTER_NOTEBOOK_SESSIONS = 'notebook.action.restartJupyterNotebookSessions'; CommandsRegistry.registerCommand({ id: RESTART_JUPYTER_NOTEBOOK_SESSIONS, handler: async (accessor: ServicesAccessor, restartJupyterServer: boolean = true) => { const editorService: IEditorService = accessor.get(IEditorService); const editors: readonly IEditorInput[] = editorService.editors; let jupyterServerRestarted: boolean = false; for (let editor of editors) { if (editor instanceof NotebookInput) { let model: INotebookModel = editor.notebookModel; if (model.providerId === JUPYTER_PROVIDER_ID && model.clientSession.isReady) { // Jupyter server needs to be restarted so that the correct Python installation is used if (!jupyterServerRestarted && restartJupyterServer) { let jupyterNotebookManager: IExecuteManager = model.executeManagers.find(x => x.providerId === JUPYTER_PROVIDER_ID); // Shutdown all current Jupyter sessions before stopping the server await jupyterNotebookManager.sessionManager.shutdownAll(); // Jupyter session manager needs to be disposed so that a new one is created with the new server info jupyterNotebookManager.sessionManager.dispose(); await jupyterNotebookManager.serverManager.stopServer(); let spec: nb.IKernelSpec = model.defaultKernel; await jupyterNotebookManager.serverManager.startServer(spec); jupyterServerRestarted = true; } // Start a new session for each Jupyter notebook await model.restartSession(); } } } } }); const STOP_JUPYTER_NOTEBOOK_SESSIONS = 'notebook.action.stopJupyterNotebookSessions'; CommandsRegistry.registerCommand({ id: STOP_JUPYTER_NOTEBOOK_SESSIONS, handler: async (accessor: ServicesAccessor) => { const editorService: IEditorService = accessor.get(IEditorService); const editors: readonly IEditorInput[] = editorService.editors; for (let editor of editors) { if (editor instanceof NotebookInput) { let model: INotebookModel = editor.notebookModel; if (model?.providerId === JUPYTER_PROVIDER_ID) { let jupyterNotebookManager: IExecuteManager = model.executeManagers.find(x => x.providerId === JUPYTER_PROVIDER_ID); await jupyterNotebookManager.sessionManager.shutdownAll(); jupyterNotebookManager.sessionManager.dispose(); await jupyterNotebookManager.serverManager.stopServer(); return; } } } } }); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: TOGGLE_TAB_FOCUS_COMMAND_ID, title: toggleTabFocusAction.label, }, when: ContextKeyExpr.and(ActiveEditorContext.isEqualTo(NotebookEditor.ID)) }); registerAction2(class extends Action2 { constructor() { super({ id: 'workbench.action.setWorkspaceAndOpen', title: localize('workbench.action.setWorkspaceAndOpen', "Set Workspace And Open") }); } run = async (accessor, options: { forceNewWindow: boolean, folderPath: URI }) => { const viewletService = accessor.get(IViewletService); const workspaceEditingService = accessor.get(IWorkspaceEditingService); const hostService = accessor.get(IHostService); let folders = []; if (!options.folderPath) { return; } folders.push(options.folderPath); await workspaceEditingService.addFolders(folders.map(folder => ({ uri: folder }))); await viewletService.openViewlet(viewletService.getDefaultViewletId(), true); if (options.forceNewWindow) { return hostService.openWindow([{ folderUri: folders[0] }], { forceNewWindow: options.forceNewWindow }); } else { return hostService.reload(); } }; }); const configurationRegistry = Registry.as(ConfigExtensions.Configuration); configurationRegistry.registerConfiguration({ 'id': 'notebook', 'title': 'Notebook', 'type': 'object', 'properties': { 'notebook.sqlStopOnError': { 'type': 'boolean', 'default': true, 'description': localize('notebook.sqlStopOnError', "SQL kernel: stop Notebook execution when error occurs in a cell.") }, 'notebook.showAllKernels': { 'type': 'boolean', 'default': false, 'description': localize('notebook.showAllKernels', "(Preview) show all kernels for the current notebook provider.") }, 'notebook.allowAzureDataStudioCommands': { 'type': 'boolean', 'default': false, 'description': localize('notebook.allowADSCommands', "Allow notebooks to run Azure Data Studio commands.") }, 'notebook.enableDoubleClickEdit': { 'type': 'boolean', 'default': true, 'description': localize('notebook.enableDoubleClickEdit', "Enable double click to edit for text cells in notebooks") }, 'notebook.defaultTextEditMode': { 'type': 'string', 'enum': [TextCellEditModes.RichText, TextCellEditModes.SplitView, TextCellEditModes.Markdown], 'enumDescriptions': [ localize('notebook.richTextModeDescription', 'Text is displayed as Rich Text (also known as WYSIWYG).'), localize('notebook.splitViewModeDescription', 'Markdown is displayed on the left, with a preview of the rendered text on the right.'), localize('notebook.markdownModeDescription', 'Text is displayed as Markdown.') ], 'default': TextCellEditModes.RichText, 'description': localize('notebook.defaultTextEditMode', "The default editing mode used for text cells") }, 'notebook.saveConnectionName': { 'type': 'boolean', 'default': false, 'description': localize('notebook.saveConnectionName', "(Preview) Save connection name in notebook metadata.") }, 'notebook.markdownPreviewLineHeight': { 'type': 'number', 'default': 1.5, 'minimum': 1, 'description': localize('notebook.markdownPreviewLineHeight', "Controls the line height used in the notebook markdown preview. This number is relative to the font size.") }, 'notebook.showRenderedNotebookInDiffEditor': { 'type': 'boolean', 'default': false, 'description': localize('notebook.showRenderedNotebookinDiffEditor', "(Preview) Show rendered notebook in diff editor.") }, 'notebook.maxRichTextUndoHistory': { 'type': 'number', 'default': 200, 'minimum': 10, 'description': localize('notebook.maxRichTextUndoHistory', "The maximum number of changes stored in the undo history for the notebook Rich Text editor.") }, 'notebook.useAbsoluteFilePaths': { 'type': 'boolean', 'default': false, 'description': localize('notebook.useAbsoluteFilePaths', "Use absolute file paths when linking to other notebooks.") }, 'notebook.enableIncrementalGridRendering': { 'type': 'boolean', 'default': false, 'description': localize('notebook.enableIncrementalGridRendering', "Enable incremental grid rendering for notebooks. This will improve the initial rendering time for large notebooks. There may be performance issues when interacting with the notebook while the rest of the grids are rendering.") }, [useNewMarkdownRendererKey]: { 'type': 'boolean', default: true, 'description': localize('notebook.useNewMarkdownRenderer', "Whether to use the newer version of the markdown renderer for Notebooks. This may result in markdown being rendered differently than previous versions.") } } }); configurationRegistry.registerConfiguration({ 'id': 'notebookViews', 'title': localize('notebookViews', 'Notebook Views'), 'type': 'object', 'properties': { 'notebookViews.enabled': { 'type': 'boolean', 'default': false, 'description': localize('notebookViews.enabled', "(Preview) Enable Notebook Views") } } }); /* *************** Output components *************** */ // Note: most existing types use the same component to render. In order to // preserve correct rank order, we register it once for each different rank of // MIME types. /** * A mime renderer component for raw html. */ registerComponentType({ mimeTypes: ['text/html'], rank: 50, safe: true, ctor: MimeRendererComponent, selector: MimeRendererComponent.SELECTOR }); /** * A mime renderer component for images. */ registerComponentType({ mimeTypes: ImageMimeTypes, rank: 90, safe: true, ctor: MimeRendererComponent, selector: MimeRendererComponent.SELECTOR }); /** * A mime renderer component for svg. */ registerComponentType({ mimeTypes: ['image/svg+xml'], rank: 80, safe: false, ctor: MimeRendererComponent, selector: MimeRendererComponent.SELECTOR }); /** * A mime renderer component for plain and jupyter console text data. */ registerComponentType({ mimeTypes: [ 'text/plain', 'application/vnd.jupyter.stdout', 'application/vnd.jupyter.stderr' ], rank: 120, safe: true, ctor: MimeRendererComponent, selector: MimeRendererComponent.SELECTOR }); /** * A placeholder component for deprecated rendered JavaScript. */ registerComponentType({ mimeTypes: ['text/javascript', 'application/javascript'], rank: 110, safe: false, ctor: MimeRendererComponent, selector: MimeRendererComponent.SELECTOR }); /** * A mime renderer component for grid data. * This will be replaced by a dedicated component in the future */ registerComponentType({ mimeTypes: [ 'application/vnd.dataresource+json', 'application/vnd.dataresource' ], rank: 40, safe: true, ctor: GridOutputComponent, selector: GridOutputComponent.SELECTOR }); /** * A mime renderer component for LaTeX. */ registerComponentType({ mimeTypes: ['text/latex'], rank: 70, safe: true, ctor: MimeRendererComponent, selector: MimeRendererComponent.SELECTOR }); /** * A mime renderer component for Plotly graphs. */ registerComponentType({ mimeTypes: ['application/vnd.plotly.v1+json'], rank: 45, safe: true, ctor: PlotlyOutputComponent, selector: PlotlyOutputComponent.SELECTOR }); /** * A mime renderer component for Plotly HTML output * that will ensure this gets ignored if possible since it's only output * on offline init and adds a