diff options
author | Johannes Rieken <johannes.rieken@gmail.com> | 2022-05-04 09:42:43 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-05-04 09:42:43 +0300 |
commit | 12c535e2bd7f261a94a5aa1267fa940055706f8c (patch) | |
tree | 63456f645a98a08889b97032c5312237f95f5d4d /src/vs/workbench/contrib/notebook | |
parent | 4ef3ed3ce8d7ab1857d41454449d32f946d3ac8c (diff) | |
parent | 9556854c8fc9199b4ffd06b4e17140e8fb78d0f4 (diff) |
Merge branch 'main' into joh/cellUri
Diffstat (limited to 'src/vs/workbench/contrib/notebook')
39 files changed, 672 insertions, 286 deletions
diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/statusBarProviders.ts b/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/statusBarProviders.ts index f1834e4ee96..750af12fdf4 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/statusBarProviders.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/statusBarProviders.ts @@ -5,16 +5,21 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Disposable } from 'vs/base/common/lifecycle'; +import { ResourceMap } from 'vs/base/common/map'; import { URI } from 'vs/base/common/uri'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { localize } from 'vs/nls'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; -import { CHANGE_CELL_LANGUAGE } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CHANGE_CELL_LANGUAGE, DETECT_CELL_LANGUAGE } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService'; import { CellKind, CellStatusbarAlignment, INotebookCellStatusBarItem, INotebookCellStatusBarItemList, INotebookCellStatusBarItemProvider } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; +import { ILanguageDetectionService } from 'vs/workbench/services/languageDetection/common/languageDetectionWorkerService'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; class CellStatusBarLanguagePickerProvider implements INotebookCellStatusBarItemProvider { @@ -50,6 +55,80 @@ class CellStatusBarLanguagePickerProvider implements INotebookCellStatusBarItemP } } +class CellStatusBarLanguageDetectionProvider implements INotebookCellStatusBarItemProvider { + + readonly viewType = '*'; + + private cache = new ResourceMap<{ + lastUpdate: number; + lastCellLang: string; + lastGuess?: string; + }>(); + + constructor( + @INotebookService private readonly _notebookService: INotebookService, + @INotebookKernelService private readonly _notebookKernelService: INotebookKernelService, + @ILanguageService private readonly _languageService: ILanguageService, + @IConfigurationService private readonly _configurationService: IConfigurationService, + @ILanguageDetectionService private readonly _languageDetectionService: ILanguageDetectionService, + @IKeybindingService private readonly _keybindingService: IKeybindingService, + ) { } + + async provideCellStatusBarItems(uri: URI, index: number, token: CancellationToken): Promise<INotebookCellStatusBarItemList | undefined> { + const doc = this._notebookService.getNotebookTextModel(uri); + const cell = doc?.cells[index]; + if (!cell) { return; } + + const enablementConfig = this._configurationService.getValue('workbench.editor.languageDetectionHints'); + const enabled = enablementConfig === 'always' || enablementConfig === 'notebookEditors'; + if (!enabled) { + return; + } + + const currentLanguageId = cell.cellKind === CellKind.Markup ? + 'markdown' : + (this._languageService.getLanguageIdByLanguageName(cell.language) || cell.language); + + if (!this.cache.has(uri)) { + this.cache.set(uri, { lastCellLang: currentLanguageId, lastUpdate: 0 }); + } + + const cached = this.cache.get(uri)!; + if (cached.lastUpdate < Date.now() - 1000 || cached.lastCellLang !== currentLanguageId) { + cached.lastUpdate = Date.now(); + cached.lastCellLang = currentLanguageId; + + const kernel = this._notebookKernelService.getSelectedOrSuggestedKernel(doc); + + if (kernel) { + const availableLangs = []; + availableLangs.push(...kernel.supportedLanguages, 'markdown'); + cached.lastGuess = await this._languageDetectionService.detectLanguage(cell.uri, availableLangs); + } + } + + const items: INotebookCellStatusBarItem[] = []; + if (cached.lastGuess && currentLanguageId !== cached.lastGuess) { + const detectedName = this._languageService.getLanguageName(cached.lastGuess) || cached.lastGuess; + let tooltip = localize('notebook.cell.status.autoDetectLanguage', "Accept Detected Language: {0}", detectedName); + const keybinding = this._keybindingService.lookupKeybinding(DETECT_CELL_LANGUAGE); + const label = keybinding?.getLabel(); + if (label) { + tooltip += ` (${label})`; + } + items.push({ + text: '$(lightbulb-autofix)', + command: DETECT_CELL_LANGUAGE, + tooltip, + alignment: CellStatusbarAlignment.Right, + priority: -Number.MAX_SAFE_INTEGER + 1 + }); + } + + return { items }; + } +} + class BuiltinCellStatusBarProviders extends Disposable { constructor( @IInstantiationService instantiationService: IInstantiationService, @@ -58,6 +137,7 @@ class BuiltinCellStatusBarProviders extends Disposable { const builtinProviders = [ CellStatusBarLanguagePickerProvider, + CellStatusBarLanguageDetectionProvider, ]; builtinProviders.forEach(p => { this._register(notebookCellStatusBarService.registerCellStatusBarItemProvider(instantiationService.createInstance(p))); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts b/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts index 10037c96943..8fbc0999419 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts @@ -27,7 +27,7 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { RedoCommand, UndoCommand } from 'vs/editor/browser/editorExtensions'; import { IWebview } from 'vs/workbench/contrib/webview/browser/webview'; import { CATEGORIES } from 'vs/workbench/common/actions'; -import { IOutputService } from 'vs/workbench/contrib/output/common/output'; +import { IOutputService } from 'vs/workbench/services/output/common/output'; import { rendererLogChannelId } from 'vs/workbench/contrib/logs/common/logConstants'; import { ILogService } from 'vs/platform/log/common/log'; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts index cb7ab0f8586..920669194f6 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts @@ -28,7 +28,7 @@ import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/note import { configureKernelIcon, selectKernelIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { INotebookKernel, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernel, INotebookKernelService, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; @@ -38,7 +38,7 @@ import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeat registerAction2(class extends Action2 { constructor() { super({ - id: '_notebook.selectKernel', + id: SELECT_KERNEL_ID, category: NOTEBOOK_ACTIONS_CATEGORY, title: { value: nls.localize('notebookActions.selectKernel', "Select Notebook Kernel"), original: 'Select Notebook Kernel' }, // precondition: NOTEBOOK_IS_ACTIVE_EDITOR, @@ -182,12 +182,7 @@ registerAction2(class extends Action2 { return res; } const quickPickItems: QuickPickInput<IQuickPickItem | KernelPick>[] = []; - if (!all.length) { - quickPickItems.push({ - id: 'install', - label: nls.localize('installKernels', "Install kernels from the marketplace"), - }); - } else { + if (all.length) { // Always display suggested kernels on the top. if (suggestions.length) { quickPickItems.push({ @@ -210,6 +205,14 @@ registerAction2(class extends Action2 { }); } + if (!all.find(item => item.type === NotebookKernelType.Resolved)) { + // there is no resolved kernel, show the install from marketplace + quickPickItems.push({ + id: 'install', + label: nls.localize('installKernels', "Install kernels from the marketplace"), + }); + } + const pick = await quickInputService.pick(quickPickItems, { placeHolder: selected ? nls.localize('prompt.placeholder.change', "Change kernel for '{0}'", labelService.getUriLabel(notebook.uri, { relative: true })) @@ -381,7 +384,7 @@ export class KernelStatus extends Disposable implements IWorkbenchContribution { tooltip: isSuggested ? nls.localize('tooltop', "{0} (suggestion)", tooltip) : tooltip, command: SELECT_KERNEL_ID, }, - '_notebook.selectKernel', + SELECT_KERNEL_ID, StatusbarAlignment.RIGHT, 10 )); @@ -399,7 +402,7 @@ export class KernelStatus extends Disposable implements IWorkbenchContribution { command: SELECT_KERNEL_ID, backgroundColor: { id: 'statusBarItem.prominentBackground' } }, - '_notebook.selectKernel', + SELECT_KERNEL_ID, StatusbarAlignment.RIGHT, 10 )); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts index f1b0150286c..2f5d35ba93f 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts @@ -88,7 +88,7 @@ export class FindModel extends Disposable { }; } - find(previous: boolean) { + find(option: { previous: boolean } | { index: number }) { if (!this.findMatches.length) { return; } @@ -96,14 +96,20 @@ export class FindModel extends Disposable { // let currCell; if (!this._findMatchesStarts) { this.set(this._findMatches, true); + if ('index' in option) { + this._currentMatch = option.index; + } } else { // const currIndex = this._findMatchesStarts!.getIndexOf(this._currentMatch); // currCell = this._findMatches[currIndex.index].cell; const totalVal = this._findMatchesStarts.getTotalSum(); - if (this._currentMatch === -1) { - this._currentMatch = previous ? totalVal - 1 : 0; + if ('index' in option) { + this._currentMatch = option.index; + } + else if (this._currentMatch === -1) { + this._currentMatch = option.previous ? totalVal - 1 : 0; } else { - const nextVal = (this._currentMatch + (previous ? -1 : 1) + totalVal) % totalVal; + const nextVal = (this._currentMatch + (option.previous ? -1 : 1) + totalVal) % totalVal; this._currentMatch = nextVal; } } @@ -157,8 +163,8 @@ export class FindModel extends Disposable { } async research() { - this._throttledDelayer.trigger(() => { - this._research(); + return this._throttledDelayer.trigger(() => { + return this._research(); }); } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFind.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFind.ts new file mode 100644 index 00000000000..f1ff2386aa7 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFind.ts @@ -0,0 +1,135 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./media/notebookFind'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { Schemas } from 'vs/base/common/network'; +import { isEqual } from 'vs/base/common/resources'; +import { URI } from 'vs/base/common/uri'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { ITextModel } from 'vs/editor/common/model'; +import { StartFindAction, StartFindReplaceAction } from 'vs/editor/contrib/find/browser/findController'; +import { localize } from 'vs/nls'; +import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { NotebookFindWidget } from 'vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget'; +import { getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { registerNotebookContribution } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; +import { CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_IS_ACTIVE_EDITOR } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; + +registerNotebookContribution(NotebookFindWidget.id, NotebookFindWidget); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'notebook.hideFind', + title: { value: localize('notebookActions.hideFind', "Hide Find in Notebook"), original: 'Hide Find in Notebook' }, + keybinding: { + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED), + primary: KeyCode.Escape, + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + + async run(accessor: ServicesAccessor): Promise<void> { + const editorService = accessor.get(IEditorService); + const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); + + if (!editor) { + return; + } + + const controller = editor.getContribution<NotebookFindWidget>(NotebookFindWidget.id); + controller.hide(); + editor.focus(); + } +}); + +registerAction2(class extends Action2 { + constructor() { + super({ + id: 'notebook.find', + title: { value: localize('notebookActions.findInNotebook', "Find in Notebook"), original: 'Find in Notebook' }, + keybinding: { + when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_IS_ACTIVE_EDITOR, EditorContextKeys.focus.toNegated()), + primary: KeyCode.KeyF | KeyMod.CtrlCmd, + weight: KeybindingWeight.WorkbenchContrib + } + }); + } + + async run(accessor: ServicesAccessor): Promise<void> { + const editorService = accessor.get(IEditorService); + const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); + + if (!editor) { + return; + } + + const controller = editor.getContribution<NotebookFindWidget>(NotebookFindWidget.id); + controller.show(); + } +}); + +function notebookContainsTextModel(uri: URI, textModel: ITextModel) { + if (textModel.uri.scheme === Schemas.vscodeNotebookCell) { + const cellUri = CellUri.parse(textModel.uri); + if (cellUri && isEqual(cellUri.notebook, uri)) { + return true; + } + } + + return false; +} + + +StartFindAction.addImplementation(100, (accessor: ServicesAccessor, codeEditor: ICodeEditor, args: any) => { + const editorService = accessor.get(IEditorService); + const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); + + if (!editor) { + return false; + } + + if (!editor.hasEditorFocus() && !editor.hasWebviewFocus()) { + const codeEditorService = accessor.get(ICodeEditorService); + // check if the active pane contains the active text editor + const textEditor = codeEditorService.getFocusedCodeEditor() || codeEditorService.getActiveCodeEditor(); + if (editor.hasModel() && textEditor && textEditor.hasModel() && notebookContainsTextModel(editor.textModel.uri, textEditor.getModel())) { + // the active text editor is in notebook editor + } else { + return false; + } + } + + const controller = editor.getContribution<NotebookFindWidget>(NotebookFindWidget.id); + controller.show(); + return true; +}); + +StartFindReplaceAction.addImplementation(100, (accessor: ServicesAccessor, codeEditor: ICodeEditor, args: any) => { + const editorService = accessor.get(IEditorService); + const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); + + if (!editor) { + return false; + } + + const controller = editor.getContribution<NotebookFindWidget>(NotebookFindWidget.id); + if (controller) { + controller.replace(); + return true; + } + + return false; +}); + diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts index 9e7e660b6b0..329490a8431 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts @@ -614,7 +614,7 @@ export abstract class SimpleFindReplaceWidget extends Widget { this._findInput.focus(); } - public show(initialInput?: string): void { + public show(initialInput?: string, options?: { focus?: boolean }): void { if (initialInput && !this._isVisible) { this._findInput.setValue(initialInput); } @@ -625,7 +625,9 @@ export abstract class SimpleFindReplaceWidget extends Widget { this._domNode.classList.add('visible', 'visible-transition'); this._domNode.setAttribute('aria-hidden', 'false'); - this.focus(); + if (options?.focus ?? true) { + this.focus(); + } }, 0); } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts index 662b1929a2e..c027d8137d4 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts @@ -3,46 +3,42 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/notebookFind'; +import * as DOM from 'vs/base/browser/dom'; +import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { alert as alertFn } from 'vs/base/browser/ui/aria/aria'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import * as strings from 'vs/base/common/strings'; -import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_IS_ACTIVE_EDITOR } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; -import { INotebookEditor, CellEditState, INotebookEditorContribution, getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { Range } from 'vs/editor/common/core/range'; +import { FindMatch } from 'vs/editor/common/model'; import { MATCHES_LIMIT } from 'vs/editor/contrib/find/browser/findModel'; -import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import * as DOM from 'vs/base/browser/dom'; -import { registerNotebookContribution } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; -import { registerAction2, Action2, IMenuService } from 'vs/platform/actions/common/actions'; -import { localize } from 'vs/nls'; -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { FindReplaceState } from 'vs/editor/contrib/find/browser/findState'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { StartFindAction, StartFindReplaceAction } from 'vs/editor/contrib/find/browser/findController'; -import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { NLS_MATCHES_LOCATION, NLS_NO_RESULTS } from 'vs/editor/contrib/find/browser/findWidget'; +import { localize } from 'vs/nls'; +import { IMenuService } from 'vs/platform/actions/common/actions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { NotebookFindFilters } from 'vs/workbench/contrib/notebook/browser/contrib/find/findFilters'; import { FindModel } from 'vs/workbench/contrib/notebook/browser/contrib/find/findModel'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { FindMatch, ITextModel } from 'vs/editor/common/model'; import { SimpleFindReplaceWidget } from 'vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget'; -import { NotebookFindFilters } from 'vs/workbench/contrib/notebook/browser/contrib/find/findFilters'; -import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { Schemas } from 'vs/base/common/network'; -import { CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { URI } from 'vs/base/common/uri'; -import { isEqual } from 'vs/base/common/resources'; +import { CellEditState, INotebookEditor, INotebookEditorContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; const FIND_HIDE_TRANSITION = 'find-hide-transition'; const FIND_SHOW_TRANSITION = 'find-show-transition'; let MAX_MATCHES_COUNT_WIDTH = 69; const PROGRESS_BAR_DELAY = 200; // show progress for at least 200ms +export interface IShowNotebookFindWidgetOptions { + isRegex?: boolean; + wholeWord?: boolean; + matchCase?: boolean; + matchIndex?: number; + focus?: boolean; +} + export class NotebookFindWidget extends SimpleFindReplaceWidget implements INotebookEditorContribution { static id: string = 'workbench.notebook.find'; protected _findWidgetFocused: IContextKey<boolean>; @@ -104,7 +100,7 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote private _onFindInputKeyDown(e: IKeyboardEvent): void { if (e.equals(KeyCode.Enter)) { - this._findModel.find(false); + this.find(false); e.preventDefault(); return; } else if (e.equals(KeyMod.Shift | KeyCode.Enter)) { @@ -125,8 +121,12 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote return false; } + private findIndex(index: number): void { + this._findModel.find({ index }); + } + protected find(previous: boolean): void { - this._findModel.find(previous); + this._findModel.find({ previous }); } protected replaceOne() { @@ -141,7 +141,7 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote this._findModel.ensureFindMatches(); if (this._findModel.currentMatch < 0) { - this._findModel.find(false); + this._findModel.find({ previous: false }); } const currentMatch = this._findModel.getCurrentMatch(); @@ -213,10 +213,18 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote protected onFindInputFocusTrackerFocus(): void { } protected onFindInputFocusTrackerBlur(): void { } - override show(initialInput?: string): void { - super.show(initialInput); + override async show(initialInput?: string, options?: IShowNotebookFindWidgetOptions): Promise<void> { + super.show(initialInput, options); this._state.change({ searchString: initialInput ?? '', isRevealed: true }, false); - this._findInput.select(); + + if (typeof options?.matchIndex === 'number') { + if (!this._findModel.findMatches.length) { + await this._findModel.research(); + } + this.findIndex(options.matchIndex); + } else { + this._findInput.select(); + } if (this._showTimeout === null) { if (this._hideTimeout !== null) { @@ -343,110 +351,3 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote super.dispose(); } } - -registerNotebookContribution(NotebookFindWidget.id, NotebookFindWidget); - -registerAction2(class extends Action2 { - constructor() { - super({ - id: 'notebook.hideFind', - title: { value: localize('notebookActions.hideFind', "Hide Find in Notebook"), original: 'Hide Find in Notebook' }, - keybinding: { - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED), - primary: KeyCode.Escape, - weight: KeybindingWeight.WorkbenchContrib - } - }); - } - - async run(accessor: ServicesAccessor): Promise<void> { - const editorService = accessor.get(IEditorService); - const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); - - if (!editor) { - return; - } - - const controller = editor.getContribution<NotebookFindWidget>(NotebookFindWidget.id); - controller.hide(); - editor.focus(); - } -}); - -registerAction2(class extends Action2 { - constructor() { - super({ - id: 'notebook.find', - title: { value: localize('notebookActions.findInNotebook', "Find in Notebook"), original: 'Find in Notebook' }, - keybinding: { - when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_IS_ACTIVE_EDITOR, EditorContextKeys.focus.toNegated()), - primary: KeyCode.KeyF | KeyMod.CtrlCmd, - weight: KeybindingWeight.WorkbenchContrib - } - }); - } - - async run(accessor: ServicesAccessor): Promise<void> { - const editorService = accessor.get(IEditorService); - const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); - - if (!editor) { - return; - } - - const controller = editor.getContribution<NotebookFindWidget>(NotebookFindWidget.id); - controller.show(); - } -}); - -function notebookContainsTextModel(uri: URI, textModel: ITextModel) { - if (textModel.uri.scheme === Schemas.vscodeNotebookCell) { - const cellUri = CellUri.parse(textModel.uri); - if (cellUri && isEqual(cellUri.notebook, uri)) { - return true; - } - } - - return false; -} - -StartFindAction.addImplementation(100, (accessor: ServicesAccessor, codeEditor: ICodeEditor, args: any) => { - const editorService = accessor.get(IEditorService); - const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); - - if (!editor) { - return false; - } - - if (!editor.hasEditorFocus() && !editor.hasWebviewFocus()) { - const codeEditorService = accessor.get(ICodeEditorService); - // check if the active pane contains the active text editor - const textEditor = codeEditorService.getFocusedCodeEditor() || codeEditorService.getActiveCodeEditor(); - if (editor.hasModel() && textEditor && textEditor.hasModel() && notebookContainsTextModel(editor.textModel.uri, textEditor.getModel())) { - // the active text editor is in notebook editor - } else { - return false; - } - } - - const controller = editor.getContribution<NotebookFindWidget>(NotebookFindWidget.id); - controller.show(); - return true; -}); - -StartFindReplaceAction.addImplementation(100, (accessor: ServicesAccessor, codeEditor: ICodeEditor, args: any) => { - const editorService = accessor.get(IEditorService); - const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); - - if (!editor) { - return false; - } - - const controller = editor.getContribution<NotebookFindWidget>(NotebookFindWidget.id); - if (controller) { - controller.replace(); - return true; - } - - return false; -}); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/apiActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/apiActions.ts index d0c6379a54b..68d9d0f8dcc 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/apiActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/apiActions.ts @@ -7,7 +7,7 @@ import * as glob from 'vs/base/common/glob'; import { URI, UriComponents } from 'vs/base/common/uri'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { isDocumentExcludePattern, TransientCellMetadata, TransientDocumentMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernelService, IResolvedNotebookKernel, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; CommandsRegistry.registerCommand('_resolveNotebookContentProvider', (accessor): { @@ -66,13 +66,13 @@ CommandsRegistry.registerCommand('_resolveNotebookKernels', async (accessor, arg const uri = URI.revive(args.uri as UriComponents); const kernels = notebookKernelService.getMatchingKernel({ uri, viewType: args.viewType }); - return kernels.all.map(provider => ({ + return kernels.all.filter(kernel => kernel.type === NotebookKernelType.Resolved).map((provider) => ({ id: provider.id, label: provider.label, kind: provider.kind, description: provider.description, detail: provider.detail, isPreferred: false, // todo@jrieken,@rebornix - preloads: provider.preloadUris, + preloads: (provider as IResolvedNotebookKernel).preloadUris, })); }); diff --git a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts index 588675f6403..8d253828d2e 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts @@ -21,12 +21,14 @@ import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/ import { changeCellToKind, runDeleteAction } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; import { CellToolbarOrder, CELL_TITLE_CELL_GROUP_ID, CELL_TITLE_OUTPUT_GROUP_ID, executeNotebookCondition, INotebookActionContext, INotebookCellActionContext, NotebookAction, NotebookCellAction, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; import { NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_TYPE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_HAS_OUTPUTS, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_USE_CONSOLIDATED_OUTPUT_BUTTON } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; -import { CellEditState, CHANGE_CELL_LANGUAGE, QUIT_EDIT_CELL_COMMAND_ID } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellEditState, CHANGE_CELL_LANGUAGE, DETECT_CELL_LANGUAGE, QUIT_EDIT_CELL_COMMAND_ID } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons'; import { CellEditType, CellKind, ICellEditOperation, NotebookCellExecutionState } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { ILanguageDetectionService } from 'vs/workbench/services/languageDetection/common/languageDetectionWorkerService'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; const CLEAR_ALL_CELLS_OUTPUTS_COMMAND_ID = 'notebook.clearAllCellsOutputs'; const EDIT_CELL_COMMAND_ID = 'notebook.cell.edit'; @@ -437,23 +439,7 @@ registerAction2(class ChangeCellLanguageAction extends NotebookCellAction<ICellR } private async setLanguage(context: IChangeCellContext, languageId: string) { - if (languageId === 'markdown' && context.cell?.language !== 'markdown') { - const idx = context.notebookEditor.getCellIndex(context.cell); - await changeCellToKind(CellKind.Markup, { cell: context.cell, notebookEditor: context.notebookEditor, ui: true }, 'markdown', Mimes.markdown); - const newCell = context.notebookEditor.cellAt(idx); - - if (newCell) { - context.notebookEditor.focusNotebookCell(newCell, 'editor'); - } - } else if (languageId !== 'markdown' && context.cell?.cellKind === CellKind.Markup) { - await changeCellToKind(CellKind.Code, { cell: context.cell, notebookEditor: context.notebookEditor, ui: true }, languageId); - } else { - const index = context.notebookEditor.textModel.cells.indexOf(context.cell.model); - context.notebookEditor.textModel.applyEdits( - [{ editType: CellEditType.CellLanguage, index, language: languageId }], - true, undefined, () => undefined, undefined, true - ); - } + await setCellToLanguage(languageId, context); } /** @@ -478,3 +464,50 @@ registerAction2(class ChangeCellLanguageAction extends NotebookCellAction<ICellR return fakeResource; } }); + +registerAction2(class DetectCellLanguageAction extends NotebookCellAction { + constructor() { + super({ + id: DETECT_CELL_LANGUAGE, + title: localize('detectLanguage', 'Accept Detected Language for Cell'), + f1: true, + precondition: ContextKeyExpr.and(NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE), + keybinding: { primary: KeyCode.KeyD | KeyMod.Alt | KeyMod.Shift, weight: KeybindingWeight.WorkbenchContrib } + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise<void> { + const languageDetectionService = accessor.get(ILanguageDetectionService); + const notificationService = accessor.get(INotificationService); + const kernelService = accessor.get(INotebookKernelService); + const kernel = kernelService.getSelectedOrSuggestedKernel(context.notebookEditor.textModel); + const providerLanguages = [...kernel?.supportedLanguages ?? []]; + providerLanguages.push('markdown'); + const detection = await languageDetectionService.detectLanguage(context.cell.uri, providerLanguages); + if (detection) { + setCellToLanguage(detection, context); + } else { + notificationService.warn(localize('noDetection', "Unable to detect cell language")); + } + } +}); + +async function setCellToLanguage(languageId: string, context: IChangeCellContext) { + if (languageId === 'markdown' && context.cell?.language !== 'markdown') { + const idx = context.notebookEditor.getCellIndex(context.cell); + await changeCellToKind(CellKind.Markup, { cell: context.cell, notebookEditor: context.notebookEditor, ui: true }, 'markdown', Mimes.markdown); + const newCell = context.notebookEditor.cellAt(idx); + + if (newCell) { + context.notebookEditor.focusNotebookCell(newCell, 'editor'); + } + } else if (languageId !== 'markdown' && context.cell?.cellKind === CellKind.Markup) { + await changeCellToKind(CellKind.Code, { cell: context.cell, notebookEditor: context.notebookEditor, ui: true }, languageId); + } else { + const index = context.notebookEditor.textModel.cells.indexOf(context.cell.model); + context.notebookEditor.textModel.applyEdits( + [{ editType: CellEditType.CellLanguage, index, language: languageId }], + true, undefined, () => undefined, undefined, true + ); + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts index 27ce6ca5434..4f6a3e489c5 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts @@ -173,15 +173,6 @@ registerAction2(class ToggleBreadcrumbFromEditorTitle extends Action2 { } }); -MenuRegistry.appendMenuItem(MenuId.NotebookToolbar, { - command: { - id: 'breadcrumbs.toggle', - title: { value: localize('cmd.toggle', "Toggle Breadcrumbs"), original: 'Toggle Breadcrumbs' }, - }, - group: 'notebookLayout', - order: 2 -}); - registerAction2(class SaveMimeTypeDisplayOrder extends Action2 { constructor() { super({ diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebook.css b/src/vs/workbench/contrib/notebook/browser/media/notebook.css index a660892ff1f..e4220b57b14 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebook.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebook.css @@ -429,7 +429,7 @@ outline: none !important; } -.monaco-workbench .notebookOverlay.notebook-editor-editable > .cell-list-container > .monaco-list > .monaco-scrollable-element > .scrollbar.visible { +.monaco-workbench .notebookOverlay.notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .scrollbar.visible { z-index: var(--z-index-notebook-scrollbar); cursor: default; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 2b793c40961..466fd1bf565 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -70,7 +70,7 @@ import 'vs/workbench/contrib/notebook/browser/controller/foldingController'; // Editor Contribution import 'vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard'; -import 'vs/workbench/contrib/notebook/browser/contrib/find/findController'; +import 'vs/workbench/contrib/notebook/browser/contrib/find/notebookFind'; import 'vs/workbench/contrib/notebook/browser/contrib/format/formatting'; import 'vs/workbench/contrib/notebook/browser/contrib/gettingStarted/notebookGettingStarted'; import 'vs/workbench/contrib/notebook/browser/contrib/layout/layoutActions'; @@ -892,5 +892,22 @@ configurationRegistry.registerConfiguration({ enum: ['always', 'never', 'fromEditor'], default: 'fromEditor' }, + [NotebookSetting.outputLineHeight]: { + markdownDescription: nls.localize('notebook.outputLineHeight', "Line height of the output text for notebook cells.\n - Values between 0 and 8 will be used as a multiplier with the font size.\n - Values greater than or equal to 8 will be used as effective values."), + type: 'number', + default: 22, + tags: ['notebookLayout'] + }, + [NotebookSetting.outputFontSize]: { + markdownDescription: nls.localize('notebook.outputFontSize', "Font size for the output text for notebook cells. When set to 0 `#editor.fontSize#` is used."), + type: 'number', + default: 0, + tags: ['notebookLayout'] + }, + [NotebookSetting.outputFontFamily]: { + markdownDescription: nls.localize('notebook.outputFontFamily', "The font family for the output text for notebook cells. When set to empty, the `#editor.fontFamily#` is used."), + type: 'string', + tags: ['notebookLayout'] + }, } }); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 7153bcfe26c..77a50b8c655 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -11,7 +11,7 @@ import { IEditorContributionDescription } from 'vs/editor/browser/editorExtensio import * as editorCommon from 'vs/editor/common/editorCommon'; import { FontInfo } from 'vs/editor/common/config/fontInfo'; import { IPosition } from 'vs/editor/common/core/position'; -import { Range } from 'vs/editor/common/core/range'; +import { IRange, Range } from 'vs/editor/common/core/range'; import { FindMatch, IModelDeltaDecoration, IReadonlyTextBuffer, ITextModel, TrackedRangeStickiness } from 'vs/editor/common/model'; import { MenuId } from 'vs/platform/actions/common/actions'; import { ITextEditorOptions, ITextResourceEditorInput } from 'vs/platform/editor/common/editor'; @@ -31,6 +31,7 @@ import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; //#region Shared commands export const EXPAND_CELL_INPUT_COMMAND_ID = 'notebook.cell.expandCellInput'; export const EXECUTE_CELL_COMMAND_ID = 'notebook.cell.execute'; +export const DETECT_CELL_LANGUAGE = 'notebook.cell.detectLanguage'; export const CHANGE_CELL_LANGUAGE = 'notebook.cell.changeLanguage'; export const QUIT_EDIT_CELL_COMMAND_ID = 'notebook.cell.quitEdit'; export const EXPAND_CELL_OUTPUT_COMMAND_ID = 'notebook.cell.expandCellOutput'; @@ -280,6 +281,7 @@ export interface INotebookEditorOptions extends ITextEditorOptions { readonly cellSelections?: ICellRange[]; readonly isReadOnly?: boolean; readonly viewState?: INotebookEditorViewState; + readonly indexedCellOptions?: { index: number; selection?: IRange }; } export type INotebookEditorContributionCtor = IConstructorSignature<INotebookEditorContribution, [INotebookEditor]>; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index 623686523fa..58edfc13e6b 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -7,7 +7,7 @@ import * as DOM from 'vs/base/browser/dom'; import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IAction, toAction } from 'vs/base/common/actions'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IErrorWithActions } from 'vs/base/common/errorMessage'; +import { createErrorWithActions } from 'vs/base/common/errorMessage'; import { Emitter, Event } from 'vs/base/common/event'; import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { extname, isEqual } from 'vs/base/common/resources'; @@ -289,8 +289,7 @@ export class NotebookEditor extends EditorPane implements IEditorPaneWithSelecti } } } catch (e) { - const error: Error & IErrorWithActions = e instanceof Error ? e : new Error(e.message); - error.actions = [ + const error = createErrorWithActions(e instanceof Error ? e : new Error(e.message), [ toAction({ id: 'workbench.notebook.action.openInTextEditor', label: localize('notebookOpenInTextEditor', "Open in Text Editor"), run: async () => { const activeEditorPane = this._editorService.activeEditorPane; @@ -317,7 +316,7 @@ export class NotebookEditor extends EditorPane implements IEditorPaneWithSelecti return; } }) - ]; + ]); throw error; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index aa1bcc7853f..20ea78a5058 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -101,7 +101,6 @@ export class BaseCellEditorOptions extends Disposable implements IBaseCellEditor }, renderLineHighlightOnlyWhenFocus: true, overviewRulerLanes: 0, - selectOnLineNumbers: false, lineNumbers: 'off', lineDecorationsWidth: 0, folding: true, @@ -397,7 +396,11 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this._updateForNotebookConfiguration(); } - if (e.compactView || e.focusIndicator || e.insertToolbarPosition || e.cellToolbarLocation || e.dragAndDropEnabled || e.fontSize || e.markupFontSize || e.insertToolbarAlignment) { + if (e.fontFamily) { + this._generateFontInfo(); + } + + if (e.compactView || e.focusIndicator || e.insertToolbarPosition || e.cellToolbarLocation || e.dragAndDropEnabled || e.fontSize || e.outputFontSize || e.markupFontSize || e.fontFamily || e.outputFontFamily || e.insertToolbarAlignment || e.outputLineHeight) { this._styleElement?.remove(); this._createLayoutStyles(); this._webview?.updateOptions({ @@ -1216,8 +1219,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this.viewModel.updateOptions({ isReadOnly: this._readOnly }); // reveal cell if editor options tell to do so - if (options?.cellOptions) { - const cellOptions = options.cellOptions; + const cellOptions = options?.cellOptions ?? this._parseIndexedCellOptions(options); + if (cellOptions) { const cell = this.viewModel.viewCells.find(cell => cell.uri.toString() === cellOptions.resource.toString()); if (cell) { this.focusElement(cell); @@ -1270,6 +1273,24 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this._onDidChangeOptions.fire(); } + private _parseIndexedCellOptions(options: INotebookEditorOptions | undefined) { + if (options?.indexedCellOptions) { + // convert index based selections + const cell = this.cellAt(options.indexedCellOptions.index); + if (cell) { + return { + resource: cell.uri, + options: { + selection: options.indexedCellOptions.selection, + preserveFocus: false + } + }; + } + } + + return undefined; + } + private _detachModel() { this._localStore.clear(); dispose(this._localCellStateListeners); @@ -1802,6 +1823,11 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD const focusRange = this.viewModel.getFocus(); const element = this.viewModel.cellAt(focusRange.start); + // The notebook editor doesn't have focus yet + if (!this.hasEditorFocus()) { + this.focusContainer(); + } + if (element && element.focusMode === CellFocusMode.Editor) { element.updateEditState(CellEditState.Editing, 'editorWidget.focus'); element.focusMode = CellFocusMode.Editor; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts index 000c7b3b72c..2d4fe4cebe0 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts @@ -4,6 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { IDisposable } from 'vs/base/common/lifecycle'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { ILogService } from 'vs/platform/log/common/log'; import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust'; @@ -12,10 +14,11 @@ import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/mode import { CellKind, INotebookTextModel, NotebookCellExecutionState } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookExecutionService } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; -import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernelService, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; -export class NotebookExecutionService implements INotebookExecutionService { +export class NotebookExecutionService implements INotebookExecutionService, IDisposable { declare _serviceBrand: undefined; + private _activeProxyKernelExecutionToken: CancellationTokenSource | undefined; constructor( @ICommandService private readonly _commandService: ICommandService, @@ -45,6 +48,29 @@ export class NotebookExecutionService implements INotebookExecutionService { return; } + if (kernel.type === NotebookKernelType.Proxy) { + this._activeProxyKernelExecutionToken?.dispose(true); + const tokenSource = this._activeProxyKernelExecutionToken = new CancellationTokenSource(); + const resolved = await kernel.resolveKernel(notebook.uri); + const kernels = this._notebookKernelService.getMatchingKernel(notebook); + const newlyMatchedKernel = kernels.all.find(k => k.id === resolved); + + if (!newlyMatchedKernel) { + return; + } + + kernel = newlyMatchedKernel; + if (tokenSource.token.isCancellationRequested) { + // execution was cancelled but we still need to update the active kernel + this._notebookKernelService.selectKernelForNotebook(kernel, notebook); + return; + } + } + + if (kernel.type === NotebookKernelType.Proxy) { + return; + } + const executeCells: NotebookCellTextModel[] = []; for (const cell of cellsArr) { const cellExe = this._notebookExecutionStateService.getCellExecution(cell.uri); @@ -75,11 +101,20 @@ export class NotebookExecutionService implements INotebookExecutionService { this._logService.debug(`NotebookExecutionService#cancelNotebookCellHandles ${JSON.stringify(cellsArr)}`); const kernel = this._notebookKernelService.getSelectedOrSuggestedKernel(notebook); if (kernel) { - await kernel.cancelNotebookCellExecution(notebook.uri, cellsArr); + if (kernel.type === NotebookKernelType.Proxy) { + this._activeProxyKernelExecutionToken?.dispose(true); + } else { + await kernel.cancelNotebookCellExecution(notebook.uri, cellsArr); + } + } } async cancelNotebookCells(notebook: INotebookTextModel, cells: Iterable<NotebookCellTextModel>): Promise<void> { this.cancelNotebookCellHandles(notebook, Array.from(cells, cell => cell.handle)); } + + dispose() { + this._activeProxyKernelExecutionToken?.dispose(true); + } } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts index 1f2e2e72032..3a14f27d0ff 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts @@ -196,7 +196,16 @@ export class NotebookKernelService extends Disposable implements INotebookKernel getSelectedOrSuggestedKernel(notebook: INotebookTextModel): INotebookKernel | undefined { const info = this.getMatchingKernel(notebook); - return info.selected ?? (info.all.length === 1 ? info.all[0] : undefined); + if (info.selected) { + return info.selected; + } + + const preferred = info.all.filter(kernel => this._kernels.get(kernel.id)?.notebookPriorities.get(notebook.uri) === 2 /* vscode.NotebookControllerPriority.Preferred */); + if (preferred.length === 1) { + return preferred[0]; + } + + return info.all.length === 1 ? info.all[0] : undefined; } // default kernel for notebookType diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellContextKeys.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellContextKeys.ts index e1e2a982be5..364939e3456 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellContextKeys.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellContextKeys.ts @@ -5,15 +5,15 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { CellEditState, CellFocusMode, ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { NotebookCellExecutionStateContext, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_CELL_EXECUTING, NOTEBOOK_CELL_EXECUTION_STATE, NOTEBOOK_CELL_FOCUSED, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_INPUT_COLLAPSED, NOTEBOOK_CELL_LINE_NUMBERS, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_OUTPUT_COLLAPSED, NOTEBOOK_CELL_TYPE } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; +import { CellViewModelStateChangeEvent } from 'vs/workbench/contrib/notebook/browser/notebookViewEvents'; +import { CellPart } from 'vs/workbench/contrib/notebook/browser/view/cellPart'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel'; import { NotebookCellExecutionState } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { NotebookCellExecutionStateContext, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_CELL_EXECUTING, NOTEBOOK_CELL_EXECUTION_STATE, NOTEBOOK_CELL_FOCUSED, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_INPUT_COLLAPSED, NOTEBOOK_CELL_LINE_NUMBERS, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_OUTPUT_COLLAPSED, NOTEBOOK_CELL_RESOURCE, NOTEBOOK_CELL_TYPE } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; -import { CellViewModelStateChangeEvent } from 'vs/workbench/contrib/notebook/browser/notebookViewEvents'; -import { CellPart } from 'vs/workbench/contrib/notebook/browser/view/cellPart'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; export class CellContextKeyPart extends CellPart { private cellContextKeyManager: CellContextKeyManager; @@ -44,6 +44,7 @@ export class CellContextKeyManager extends Disposable { private cellContentCollapsed!: IContextKey<boolean>; private cellOutputCollapsed!: IContextKey<boolean>; private cellLineNumbers!: IContextKey<'on' | 'off' | 'inherit'>; + private cellResource!: IContextKey<string>; private markdownEditMode!: IContextKey<boolean>; @@ -69,6 +70,7 @@ export class CellContextKeyManager extends Disposable { this.cellContentCollapsed = NOTEBOOK_CELL_INPUT_COLLAPSED.bindTo(this._contextKeyService); this.cellOutputCollapsed = NOTEBOOK_CELL_OUTPUT_COLLAPSED.bindTo(this._contextKeyService); this.cellLineNumbers = NOTEBOOK_CELL_LINE_NUMBERS.bindTo(this._contextKeyService); + this.cellResource = NOTEBOOK_CELL_RESOURCE.bindTo(this._contextKeyService); if (element) { this.updateForElement(element); @@ -112,6 +114,7 @@ export class CellContextKeyManager extends Disposable { this.updateForOutputs(); this.cellLineNumbers.set(this.element!.lineNumbers); + this.cellResource.set(this.element!.uri.toString()); }); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDnd.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDnd.ts index 8c0b8a99748..86e0a69074a 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDnd.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDnd.ts @@ -78,7 +78,7 @@ export class CellDragAndDropController extends Disposable { this._register(DOM.addDisposableListener(document.body, DOM.EventType.DRAG_START, this.onGlobalDragStart.bind(this), true)); this._register(DOM.addDisposableListener(document.body, DOM.EventType.DRAG_END, this.onGlobalDragEnd.bind(this), true)); - const addCellDragListener = (eventType: string, handler: (e: CellDragEvent) => void) => { + const addCellDragListener = (eventType: string, handler: (e: CellDragEvent) => void, useCapture = false) => { this._register(DOM.addDisposableListener( notebookEditor.getDomNode(), eventType, @@ -87,14 +87,21 @@ export class CellDragAndDropController extends Disposable { if (cellDragEvent) { handler(cellDragEvent); } - })); + }, useCapture)); }; addCellDragListener(DOM.EventType.DRAG_OVER, event => { + if (!this.currentDraggedCell) { + return; + } event.browserEvent.preventDefault(); + event.browserEvent.stopImmediatePropagation(); this.onCellDragover(event); - }); + }, true); addCellDragListener(DOM.EventType.DROP, event => { + if (!this.currentDraggedCell) { + return; + } event.browserEvent.preventDefault(); this.onCellDrop(event); }); diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDragRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDragRenderer.ts index 46388b581b4..43c5852753e 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDragRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDragRenderer.ts @@ -65,7 +65,7 @@ class EditorTextRenderer { let result = ''; for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) { - const lineTokens = model.getLineTokens(lineNumber); + const lineTokens = model.tokenization.getLineTokens(lineNumber); const lineContent = lineTokens.getLineContent(); const startOffset = (lineNumber === startLineNumber ? startColumn - 1 : 0); const endOffset = (lineNumber === endLineNumber ? endColumn - 1 : lineContent.length); diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellExecution.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellExecution.ts index d4745c02a43..469e7cbef3d 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellExecution.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellExecution.ts @@ -9,6 +9,7 @@ import { ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/no import { CellViewModelStateChangeEvent } from 'vs/workbench/contrib/notebook/browser/notebookViewEvents'; import { CellPart } from 'vs/workbench/contrib/notebook/browser/view/cellPart'; import { NotebookCellInternalMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; export class CellExecutionPart extends CellPart { private kernelDisposables = this._register(new DisposableStore()); @@ -41,7 +42,7 @@ export class CellExecutionPart extends CellPart { } private updateExecutionOrder(internalMetadata: NotebookCellInternalMetadata): void { - if (this._notebookEditor.activeKernel?.implementsExecutionOrder) { + if (this._notebookEditor.activeKernel?.type === NotebookKernelType.Resolved && this._notebookEditor.activeKernel?.implementsExecutionOrder) { const executionOrderLabel = typeof internalMetadata.executionOrder === 'number' ? `[${internalMetadata.executionOrder}]` : '[ ]'; @@ -62,7 +63,8 @@ export class CellExecutionPart extends CellPart { DOM.hide(this._executionOrderLabel); } else { DOM.show(this._executionOrderLabel); - this._executionOrderLabel.style.top = `${element.layoutInfo.editorHeight}px`; + const top = element.layoutInfo.editorHeight - 22 + element.layoutInfo.statusBarHeight; + this._executionOrderLabel.style.top = `${top}px`; } } } diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts index 3f2df96e305..6cd5d2c242d 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts @@ -316,8 +316,17 @@ export class CodeCell extends Disposable { })); } + private shouldUpdateDOMFocus() { + // The DOM focus needs to be adjusted: + // when a cell editor should be focused + // the document active element is inside the notebook editor or the document body (cell editor being disposed previously) + return this.notebookEditor.getActiveCell() === this.viewCell + && this.viewCell.focusMode === CellFocusMode.Editor + && (this.notebookEditor.hasEditorFocus() || document.activeElement === document.body); + } + private updateEditorForFocusModeChange() { - if (this.viewCell.focusMode === CellFocusMode.Editor && this.notebookEditor.getActiveCell() === this.viewCell) { + if (this.shouldUpdateDOMFocus()) { this.templateData.editor?.focus(); } @@ -479,7 +488,7 @@ export class CodeCell extends Disposable { this._isDisposed = true; // move focus back to the cell list otherwise the focus goes to body - if (this.notebookEditor.getActiveCell() === this.viewCell && this.viewCell.focusMode === CellFocusMode.Editor) { + if (this.shouldUpdateDOMFocus()) { this.notebookEditor.focusContainer(); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/markdownCell.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/markdownCell.ts index bf119feb15c..82e710ed494 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/markdownCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/markdownCell.ts @@ -221,7 +221,7 @@ export class StatefulMarkdownCell extends Disposable { override dispose() { // move focus back to the cell list otherwise the focus goes to body - if (this.notebookEditor.getActiveCell() === this.viewCell && this.viewCell.focusMode === CellFocusMode.Editor) { + if (this.notebookEditor.getActiveCell() === this.viewCell && this.viewCell.focusMode === CellFocusMode.Editor && (this.notebookEditor.hasEditorFocus() || document.activeElement === document.body)) { this.notebookEditor.focusContainer(); } @@ -327,6 +327,7 @@ export class StatefulMarkdownCell extends Disposable { width: width, height: editorHeight }, + enableDropIntoEditor: true, // overflowWidgetsDomNode: this.notebookEditor.getOverflowContainerDomNode() }, { contributions: this.notebookEditor.creationOptions.cellEditorContributions diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index 7bda661ed4e..a7145c9ffc9 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -37,10 +37,11 @@ import { preloadsScriptStr, RendererMetadata } from 'vs/workbench/contrib/notebo import { transformWebviewThemeVars } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewThemeMapping'; import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel'; import { CellUri, INotebookRendererInfo, NotebookSetting, RendererMessagingSpec } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { INotebookKernel } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernel, IResolvedNotebookKernel, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { IScopedRendererMessaging } from 'vs/workbench/contrib/notebook/common/notebookRendererMessagingService'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IWebviewElement, IWebviewService, WebviewContentPurpose } from 'vs/workbench/contrib/webview/browser/webview'; +import { WebviewWindowDragMonitor } from 'vs/workbench/contrib/webview/browser/webviewWindowDragMonitor'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { FromWebviewMessage, IAckOutputHeight, IClickedDataUrlMessage, ICodeBlockHighlightRequest, IContentWidgetTopRequest, IControllerPreload, ICreationContent, ICreationRequestMessage, IFindMatch, IMarkupCellInitialization, ToWebviewMessage } from './webviewMessages'; @@ -88,8 +89,11 @@ interface BacklayerWebviewOptions { readonly runGutter: number; readonly dragAndDropEnabled: boolean; readonly fontSize: number; + readonly outputFontSize: number; readonly fontFamily: string; + readonly outputFontFamily: string; readonly markupFontSize: number; + readonly outputLineHeight: number; } export class BackLayerWebView<T extends ICommonCellInfo> extends Disposable { @@ -204,8 +208,9 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Disposable { 'notebook-output-node-left-padding': `${this.options.outputNodeLeftPadding}px`, 'notebook-markdown-min-height': `${this.options.previewNodePadding * 2}px`, 'notebook-markup-font-size': typeof this.options.markupFontSize === 'number' && this.options.markupFontSize > 0 ? `${this.options.markupFontSize}px` : `calc(${this.options.fontSize}px * 1.2)`, - 'notebook-cell-output-font-size': `${this.options.fontSize}px`, - 'notebook-cell-output-font-family': this.options.fontFamily, + 'notebook-cell-output-font-size': `${this.options.outputFontSize || this.options.fontSize}px`, + 'notebook-cell-output-line-height': `${this.options.outputLineHeight}px`, + 'notebook-cell-output-font-family': this.options.outputFontFamily || this.options.fontFamily, 'notebook-cell-markup-empty-content': nls.localize('notebook.emptyMarkdownPlaceholder', "Empty markdown cell, double click or press enter to edit."), 'notebook-cell-renderer-not-found-error': nls.localize({ key: 'notebook.error.rendererNotFound', @@ -523,6 +528,8 @@ var requirejs = (function() { this.webview.mountTo(this.element); this._register(this.webview); + this._register(new WebviewWindowDragMonitor(() => this.webview)); + this._register(this.webview.onDidClickLink(link => { if (this._disposed) { return; @@ -897,7 +904,7 @@ var requirejs = (function() { } this._preloadsCache.clear(); - if (this._currentKernel) { + if (this._currentKernel?.type === NotebookKernelType.Resolved) { this._updatePreloadsFromKernel(this._currentKernel); } @@ -1394,14 +1401,14 @@ var requirejs = (function() { const previousKernel = this._currentKernel; this._currentKernel = kernel; - if (previousKernel && previousKernel.preloadUris.length > 0) { + if (previousKernel?.type === NotebookKernelType.Resolved && previousKernel.preloadUris.length > 0) { this.webview?.reload(); // preloads will be restored after reload - } else if (kernel) { + } else if (kernel?.type === NotebookKernelType.Resolved) { this._updatePreloadsFromKernel(kernel); } } - private _updatePreloadsFromKernel(kernel: INotebookKernel) { + private _updatePreloadsFromKernel(kernel: IResolvedNotebookKernel) { const resources: IControllerPreload[] = []; for (const preload of kernel.preloadUris) { const uri = this.environmentService.isExtensionDevelopment && (preload.scheme === 'http' || preload.scheme === 'https') @@ -1427,7 +1434,7 @@ var requirejs = (function() { const mixedResourceRoots = [ ...(this.localResourceRootsCache || []), - ...(this._currentKernel ? [this._currentKernel.localResourceRoot] : []), + ...(this._currentKernel?.type === NotebookKernelType.Resolved ? [this._currentKernel.localResourceRoot] : []), ]; this.webview.localResourcesRoot = mixedResourceRoots; diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts index 02beef093cf..2bcf55c8531 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -168,7 +168,7 @@ export class MarkupCellRenderer extends AbstractCellRenderer implements IListRen templateDisposables.add(scopedInstaService.createInstance(BetweenCellToolbar, this.notebookEditor, titleToolbarContainer, bottomCellContainer)), templateDisposables.add(scopedInstaService.createInstance(CellEditorStatusBar, this.notebookEditor, container, editorPart, undefined)), templateDisposables.add(new CellFocusIndicator(this.notebookEditor, titleToolbar, focusIndicatorTop, focusIndicatorLeft, focusIndicatorRight, focusIndicatorBottom)), - templateDisposables.add(scopedInstaService.createInstance(FoldedCellHint, this.notebookEditor, DOM.append(container, $('.notebook-folded-hint')))), + templateDisposables.add(new FoldedCellHint(this.notebookEditor, DOM.append(container, $('.notebook-folded-hint')))), templateDisposables.add(new CellDecorations(rootContainer, decorationContainer)), templateDisposables.add(scopedInstaService.createInstance(CellComments, this.notebookEditor, cellCommentPartContainer)), templateDisposables.add(new CollapsedCellInput(this.notebookEditor, cellInputCollapsedContainer)), @@ -274,6 +274,7 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende width: 0, height: 0 }, + enableDropIntoEditor: true, }, { contributions: this.notebookEditor.creationOptions.cellEditorContributions }); diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts index 8b7c306af28..11b51e17e2f 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts @@ -156,8 +156,6 @@ export abstract class BaseCellViewModel extends Disposable { this._onDidChangeState.fire({ outputCollapsedChanged: true }); } - private _textEditorRestore: any; - constructor( readonly viewType: string, readonly model: NotebookCellTextModel, @@ -236,9 +234,7 @@ export abstract class BaseCellViewModel extends Disposable { this._textEditor = editor; if (this._editorViewStates) { - this._textEditorRestore = setTimeout(() => { - this._restoreViewState(this._editorViewStates); - }); + this._restoreViewState(this._editorViewStates); } if (this._editorTransientState) { @@ -264,7 +260,6 @@ export abstract class BaseCellViewModel extends Disposable { } detachTextEditor() { - clearTimeout(this._textEditorRestore); this.saveViewState(); this.saveTransientState(); // decorations need to be cleared first as editors can be resued. @@ -589,7 +584,6 @@ export abstract class BaseCellViewModel extends Disposable { super.dispose(); dispose(this._editorListeners); - clearTimeout(this._textEditorRestore); // Only remove the undo redo stack if we map this cell uri to itself // If we are not in perCell mode, it will map to the full NotebookDocument and diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts index a90477db542..b1cf70a4946 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts @@ -8,7 +8,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { ICellViewModel, INotebookEditorDelegate, KERNEL_EXTENSIONS } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NOTEBOOK_CELL_TOOLBAR_LOCATION, NOTEBOOK_HAS_OUTPUTS, NOTEBOOK_HAS_RUNNING_CELL, NOTEBOOK_INTERRUPTIBLE_KERNEL, NOTEBOOK_KERNEL, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_KERNEL_SELECTED, NOTEBOOK_MISSING_KERNEL_EXTENSION, NOTEBOOK_USE_CONSOLIDATED_OUTPUT_BUTTON, NOTEBOOK_VIEW_TYPE } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; -import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernelService, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; export class NotebookEditorContextKeys { @@ -148,7 +148,7 @@ export class NotebookEditorContextKeys { const { selected, all } = this._notebookKernelService.getMatchingKernel(this._editor.textModel); this._notebookKernelCount.set(all.length); - this._interruptibleKernel.set(selected?.implementsInterrupt ?? false); + this._interruptibleKernel.set((selected?.type === NotebookKernelType.Resolved && selected.implementsInterrupt) ?? false); this._notebookKernelSelected.set(Boolean(selected)); this._notebookKernel.set(selected?.id ?? ''); } diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts index dc9a494193e..e5814ce98ec 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts @@ -6,10 +6,11 @@ import 'vs/css!./notebookKernelActionViewItem'; import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { Action, IAction } from 'vs/base/common/actions'; +import { DisposableStore } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { selectKernelIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; -import { INotebookKernelMatchResult, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernelMatchResult, INotebookKernelService, NotebookKernelType, ProxyKernelState } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { Event } from 'vs/base/common/event'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; @@ -17,6 +18,7 @@ import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookB export class NotebooKernelActionViewItem extends ActionViewItem { private _kernelLabel?: HTMLAnchorElement; + private _kernelDisposable: DisposableStore; constructor( actualAction: IAction, @@ -31,6 +33,7 @@ export class NotebooKernelActionViewItem extends ActionViewItem { this._register(_editor.onDidChangeModel(this._update, this)); this._register(_notebookKernelService.onDidChangeNotebookAffinity(this._update, this)); this._register(_notebookKernelService.onDidChangeSelectedNotebooks(this._update, this)); + this._kernelDisposable = this._register(new DisposableStore()); } override render(container: HTMLElement): void { @@ -63,9 +66,9 @@ export class NotebooKernelActionViewItem extends ActionViewItem { } private _updateActionFromKernelInfo(info: INotebookKernelMatchResult): void { - + this._kernelDisposable.clear(); this._action.enabled = true; - const selectedOrSuggested = info.selected ?? (info.all.length === 1 && info.suggestions.length === 1 ? info.suggestions[0] : undefined); + const selectedOrSuggested = info.selected ?? ((info.suggestions.length === 1 && info.suggestions[0].type === NotebookKernelType.Resolved) ? info.suggestions[0] : undefined); if (selectedOrSuggested) { // selected or suggested kernel this._action.label = selectedOrSuggested.label; @@ -74,6 +77,23 @@ export class NotebooKernelActionViewItem extends ActionViewItem { // special UI for selected kernel? } + if (selectedOrSuggested.type === NotebookKernelType.Proxy) { + if (selectedOrSuggested.connectionState === ProxyKernelState.Initializing) { + this._action.label = localize('initializing', "Initializing..."); + } else { + this._action.label = selectedOrSuggested.label; + } + + this._kernelDisposable.add(selectedOrSuggested.onDidChange(e => { + if (e.connectionState) { + if (selectedOrSuggested.connectionState === ProxyKernelState.Initializing) { + this._action.label = localize('initializing', "Initializing..."); + } else { + this._action.label = selectedOrSuggested.label; + } + } + })); + } } else { // many kernels or no kernels this._action.label = localize('select', "Select Kernel"); diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index bc1f504dc36..28ff3f4f885 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -25,8 +25,7 @@ import { IEditorModel } from 'vs/platform/editor/common/editor'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ThemeColor } from 'vs/platform/theme/common/themeService'; import { UndoRedoGroup } from 'vs/platform/undoRedo/common/undoRedo'; -import { IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor'; -import { EditorInput } from 'vs/workbench/common/editor/editorInput'; +import { IRevertOptions, ISaveOptions, IUntypedEditorInput } from 'vs/workbench/common/editor'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { IWorkingCopyBackupMeta, IWorkingCopySaveEvent } from 'vs/workbench/services/workingCopy/common/workingCopy'; @@ -792,7 +791,7 @@ export interface INotebookEditorModel extends IEditorModel { hasAssociatedFilePath(): boolean; load(options?: INotebookLoadOptions): Promise<IResolvedNotebookEditorModel>; save(options?: ISaveOptions): Promise<boolean>; - saveAs(target: URI): Promise<EditorInput | undefined>; + saveAs(target: URI): Promise<IUntypedEditorInput | undefined>; revert(options?: IRevertOptions): Promise<void>; } @@ -925,7 +924,10 @@ export const NotebookSetting = { textOutputLineLimit: 'notebook.output.textLineLimit', globalToolbarShowLabel: 'notebook.globalToolbarShowLabel', markupFontSize: 'notebook.markup.fontSize', - interactiveWindowCollapseCodeCells: 'interactiveWindow.collapseCellInputCode' + interactiveWindowCollapseCodeCells: 'interactiveWindow.collapseCellInputCode', + outputLineHeight: 'notebook.outputLineHeight', + outputFontSize: 'notebook.outputFontSize', + outputFontFamily: 'notebook.outputFontFamily' } as const; export const enum CellStatusbarAlignment { diff --git a/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts b/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts index fd2279cba63..689f9ca995e 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts @@ -38,6 +38,8 @@ export const NOTEBOOK_CELL_EXECUTING = new RawContextKey<boolean>('notebookCellE export const NOTEBOOK_CELL_HAS_OUTPUTS = new RawContextKey<boolean>('notebookCellHasOutputs', false); export const NOTEBOOK_CELL_INPUT_COLLAPSED = new RawContextKey<boolean>('notebookCellInputIsCollapsed', false); export const NOTEBOOK_CELL_OUTPUT_COLLAPSED = new RawContextKey<boolean>('notebookCellOutputIsCollapsed', false); +export const NOTEBOOK_CELL_RESOURCE = new RawContextKey<string>('notebookCellResource', ''); + // Kernels export const NOTEBOOK_KERNEL = new RawContextKey<string>('notebookKernel', undefined); export const NOTEBOOK_KERNEL_COUNT = new RawContextKey<number>('notebookKernelCount', 0); diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts index cffd2b0362d..b8cbd4985d0 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts @@ -102,6 +102,10 @@ export class NotebookEditorInput extends AbstractResourceEditorInput { } } + if (!(capabilities & EditorInputCapabilities.Readonly)) { + capabilities |= EditorInputCapabilities.CanDropIntoEditor; + } + return capabilities; } @@ -120,7 +124,7 @@ export class NotebookEditorInput extends AbstractResourceEditorInput { return this._editorModelReference.object.isDirty(); } - override async save(group: GroupIdentifier, options?: ISaveOptions): Promise<EditorInput | undefined> { + override async save(group: GroupIdentifier, options?: ISaveOptions): Promise<EditorInput | IUntypedEditorInput | undefined> { if (this._editorModelReference) { if (this.hasCapability(EditorInputCapabilities.Untitled)) { @@ -135,7 +139,7 @@ export class NotebookEditorInput extends AbstractResourceEditorInput { return undefined; } - override async saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise<EditorInput | undefined> { + override async saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise<IUntypedEditorInput | undefined> { if (!this._editorModelReference) { return undefined; } diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts index 8938cff2e80..7cd345036a7 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts @@ -4,8 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor'; -import { EditorInput } from 'vs/workbench/common/editor/editorInput'; +import { IRevertOptions, ISaveOptions, IUntypedEditorInput } from 'vs/workbench/common/editor'; import { EditorModel } from 'vs/workbench/common/editor/editorModel'; import { Emitter, Event } from 'vs/base/common/event'; import { ICellDto2, INotebookEditorModel, INotebookLoadOptions, IResolvedNotebookEditorModel, NotebookCellsChangeType, NotebookData, NotebookDocumentBackupData } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -28,8 +27,6 @@ import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/commo import { StoredFileWorkingCopyState, IStoredFileWorkingCopy, IStoredFileWorkingCopyModel, IStoredFileWorkingCopyModelContentChangedEvent, IStoredFileWorkingCopyModelFactory, IStoredFileWorkingCopySaveEvent } from 'vs/workbench/services/workingCopy/common/storedFileWorkingCopy'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { CancellationError } from 'vs/base/common/errors'; -import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { filter } from 'vs/base/common/objects'; import { IFileWorkingCopyManager } from 'vs/workbench/services/workingCopy/common/fileWorkingCopyManager'; import { IUntitledFileWorkingCopy, IUntitledFileWorkingCopyModel, IUntitledFileWorkingCopyModelContentChangedEvent, IUntitledFileWorkingCopyModelFactory } from 'vs/workbench/services/workingCopy/common/untitledFileWorkingCopy'; @@ -59,7 +56,6 @@ export class ComplexNotebookEditorModel extends EditorModel implements INotebook readonly resource: URI, readonly viewType: string, private readonly _contentProvider: INotebookContentProvider, - @IInstantiationService private readonly _instantiationService: IInstantiationService, @INotebookService private readonly _notebookService: INotebookService, @IWorkingCopyService private readonly _workingCopyService: IWorkingCopyService, @IWorkingCopyBackupService private readonly _workingCopyBackupService: IWorkingCopyBackupService, @@ -393,7 +389,7 @@ export class ComplexNotebookEditorModel extends EditorModel implements INotebook }); } - async saveAs(targetResource: URI): Promise<EditorInput | undefined> { + async saveAs(targetResource: URI): Promise<IUntypedEditorInput | undefined> { if (!this.isResolved()) { return undefined; @@ -419,7 +415,7 @@ export class ComplexNotebookEditorModel extends EditorModel implements INotebook } this.setDirty(false); this._onDidSave.fire({}); - return this._instantiationService.createInstance(NotebookEditorInput, targetResource, this.viewType, {}); + return { resource: targetResource }; } private async _resolveStats(resource: URI) { @@ -462,7 +458,6 @@ export class SimpleNotebookEditorModel extends EditorModel implements INotebookE private readonly _hasAssociatedFilePath: boolean, readonly viewType: string, private readonly _workingCopyManager: IFileWorkingCopyManager<NotebookFileWorkingCopyModel, NotebookFileWorkingCopyModel>, - @IInstantiationService private readonly _instantiationService: IInstantiationService, @IFileService private readonly _fileService: IFileService ) { super(); @@ -547,14 +542,14 @@ export class SimpleNotebookEditorModel extends EditorModel implements INotebookE return this; } - async saveAs(target: URI): Promise<EditorInput | undefined> { + async saveAs(target: URI): Promise<IUntypedEditorInput | undefined> { const newWorkingCopy = await this._workingCopyManager.saveAs(this.resource, target); if (!newWorkingCopy) { return undefined; } // this is a little hacky because we leave the new working copy alone. BUT // the newly created editor input will pick it up and claim ownership of it. - return this._instantiationService.createInstance(NotebookEditorInput, newWorkingCopy.resource, this.viewType, {}); + return { resource: newWorkingCopy.resource }; } private static _isStoredFileWorkingCopy(candidate?: IStoredFileWorkingCopy<NotebookFileWorkingCopyModel> | IUntitledFileWorkingCopy<NotebookFileWorkingCopyModel>): candidate is IStoredFileWorkingCopy<NotebookFileWorkingCopyModel> { diff --git a/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts b/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts index 6610fe7177d..4f5304600b0 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts @@ -31,8 +31,13 @@ export interface INotebookKernelChangeEvent { hasExecutionOrder?: true; } -export interface INotebookKernel { +export const enum NotebookKernelType { + Resolved, + Proxy = 1 +} +export interface IResolvedNotebookKernel { + readonly type: NotebookKernelType.Resolved; readonly id: string; readonly viewType: string; readonly onDidChange: Event<Readonly<INotebookKernelChangeEvent>>; @@ -54,6 +59,34 @@ export interface INotebookKernel { cancelNotebookCellExecution(uri: URI, cellHandles: number[]): Promise<void>; } +export const enum ProxyKernelState { + Disconnected = 1, + Connected = 2, + Initializing = 3 +} + +export interface INotebookProxyKernelChangeEvent extends INotebookKernelChangeEvent { + connectionState?: true; +} + +export interface INotebookProxyKernel { + readonly type: NotebookKernelType.Proxy; + readonly id: string; + readonly viewType: string; + readonly extension: ExtensionIdentifier; + readonly preloadProvides: string[]; + readonly onDidChange: Event<Readonly<INotebookProxyKernelChangeEvent>>; + label: string; + description?: string; + detail?: string; + kind?: string; + supportedLanguages: string[]; + connectionState: ProxyKernelState; + resolveKernel(uri: URI): Promise<string | null>; +} + +export type INotebookKernel = IResolvedNotebookKernel | INotebookProxyKernel; + export interface INotebookTextModelLike { uri: URI; viewType: string } export const INotebookKernelService = createDecorator<INotebookKernelService>('INotebookKernelService'); diff --git a/src/vs/workbench/contrib/notebook/common/notebookOptions.ts b/src/vs/workbench/contrib/notebook/common/notebookOptions.ts index 9564a563def..426871f84fa 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookOptions.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookOptions.ts @@ -62,6 +62,9 @@ export interface NotebookLayoutConfiguration { showFoldingControls: 'always' | 'mouseover'; dragAndDropEnabled: boolean; fontSize: number; + outputFontSize: number; + outputFontFamily: string; + outputLineHeight: number; markupFontSize: number; focusIndicatorLeftMargin: number; editorOptionsCustomizations: any | undefined; @@ -84,9 +87,13 @@ export interface NotebookOptionsChangeEvent { readonly consolidatedRunButton?: boolean; readonly dragAndDropEnabled?: boolean; readonly fontSize?: boolean; + readonly outputFontSize?: boolean; readonly markupFontSize?: boolean; + readonly fontFamily?: boolean; + readonly outputFontFamily?: boolean; readonly editorOptionsCustomizations?: boolean; readonly interactiveWindowCollapseCodeCells?: boolean; + readonly outputLineHeight?: boolean; } const defaultConfigConstants = Object.freeze({ @@ -134,9 +141,12 @@ export class NotebookOptions extends Disposable { const showFoldingControls = this._computeShowFoldingControlsOption(); // const { bottomToolbarGap, bottomToolbarHeight } = this._computeBottomToolbarDimensions(compactView, insertToolbarPosition, insertToolbarAlignment); const fontSize = this.configurationService.getValue<number>('editor.fontSize'); + const outputFontSize = this.configurationService.getValue<number>(NotebookSetting.outputFontSize); + const outputFontFamily = this.configurationService.getValue<string>(NotebookSetting.outputFontFamily); const markupFontSize = this.configurationService.getValue<number>(NotebookSetting.markupFontSize); const editorOptionsCustomizations = this.configurationService.getValue(NotebookSetting.cellEditorOptionsCustomizations); const interactiveWindowCollapseCodeCells: InteractiveWindowCollapseCodeCells = this.configurationService.getValue(NotebookSetting.interactiveWindowCollapseCodeCells); + const outputLineHeight = this._computeOutputLineHeight(); this._layoutConfiguration = { ...(compactView ? compactConfigConstants : defaultConfigConstants), @@ -166,6 +176,9 @@ export class NotebookOptions extends Disposable { insertToolbarAlignment, showFoldingControls, fontSize, + outputFontSize, + outputFontFamily, + outputLineHeight, markupFontSize, editorOptionsCustomizations, focusIndicatorGap: 3, @@ -185,6 +198,29 @@ export class NotebookOptions extends Disposable { })); } + private _computeOutputLineHeight(): number { + const minimumLineHeight = 8; + let lineHeight = this.configurationService.getValue<number>(NotebookSetting.outputLineHeight); + + if (lineHeight < minimumLineHeight) { + // Values too small to be line heights in pixels are in ems. + let fontSize = this.configurationService.getValue<number>(NotebookSetting.outputFontSize); + if (fontSize === 0) { + fontSize = this.configurationService.getValue<number>('editor.fontSize'); + } + + lineHeight = lineHeight * fontSize; + } + + // Enforce integer, minimum constraints + lineHeight = Math.round(lineHeight); + if (lineHeight < minimumLineHeight) { + lineHeight = minimumLineHeight; + } + + return lineHeight; + } + private _updateConfiguration(e: IConfigurationChangeEvent) { const cellStatusBarVisibility = e.affectsConfiguration(NotebookSetting.showCellStatusBar); const cellToolbarLocation = e.affectsConfiguration(NotebookSetting.cellToolbarLocation); @@ -199,9 +235,13 @@ export class NotebookOptions extends Disposable { const showFoldingControls = e.affectsConfiguration(NotebookSetting.showFoldingControls); const dragAndDropEnabled = e.affectsConfiguration(NotebookSetting.dragAndDropEnabled); const fontSize = e.affectsConfiguration('editor.fontSize'); + const outputFontSize = e.affectsConfiguration(NotebookSetting.outputFontSize); const markupFontSize = e.affectsConfiguration(NotebookSetting.markupFontSize); + const fontFamily = e.affectsConfiguration('editor.fontFamily'); + const outputFontFamily = e.affectsConfiguration(NotebookSetting.outputFontFamily); const editorOptionsCustomizations = e.affectsConfiguration(NotebookSetting.cellEditorOptionsCustomizations); const interactiveWindowCollapseCodeCells = e.affectsConfiguration(NotebookSetting.interactiveWindowCollapseCodeCells); + const outputLineHeight = e.affectsConfiguration(NotebookSetting.outputLineHeight); if ( !cellStatusBarVisibility @@ -217,9 +257,13 @@ export class NotebookOptions extends Disposable { && !showFoldingControls && !dragAndDropEnabled && !fontSize + && !outputFontSize && !markupFontSize + && !fontFamily + && !outputFontFamily && !editorOptionsCustomizations - && !interactiveWindowCollapseCodeCells) { + && !interactiveWindowCollapseCodeCells + && !outputLineHeight) { return; } @@ -281,10 +325,18 @@ export class NotebookOptions extends Disposable { configuration.fontSize = this.configurationService.getValue<number>('editor.fontSize'); } + if (outputFontSize) { + configuration.outputFontSize = this.configurationService.getValue<number>(NotebookSetting.outputFontSize) ?? configuration.fontSize; + } + if (markupFontSize) { configuration.markupFontSize = this.configurationService.getValue<number>(NotebookSetting.markupFontSize); } + if (outputFontFamily) { + configuration.outputFontFamily = this.configurationService.getValue<string>(NotebookSetting.outputFontFamily); + } + if (editorOptionsCustomizations) { configuration.editorOptionsCustomizations = this.configurationService.getValue(NotebookSetting.cellEditorOptionsCustomizations); } @@ -293,6 +345,10 @@ export class NotebookOptions extends Disposable { configuration.interactiveWindowCollapseCodeCells = this.configurationService.getValue(NotebookSetting.interactiveWindowCollapseCodeCells); } + if (outputLineHeight || fontSize || outputFontSize) { + configuration.outputLineHeight = this._computeOutputLineHeight(); + } + this._layoutConfiguration = Object.freeze(configuration); // trigger event @@ -310,9 +366,13 @@ export class NotebookOptions extends Disposable { consolidatedRunButton, dragAndDropEnabled, fontSize, + outputFontSize, markupFontSize, + fontFamily, + outputFontFamily, editorOptionsCustomizations, - interactiveWindowCollapseCodeCells + interactiveWindowCollapseCodeCells, + outputLineHeight }); } @@ -502,7 +562,10 @@ export class NotebookOptions extends Disposable { runGutter: this._layoutConfiguration.cellRunGutter, dragAndDropEnabled: this._layoutConfiguration.dragAndDropEnabled, fontSize: this._layoutConfiguration.fontSize, + outputFontSize: this._layoutConfiguration.outputFontSize, + outputFontFamily: this._layoutConfiguration.outputFontFamily, markupFontSize: this._layoutConfiguration.markupFontSize, + outputLineHeight: this._layoutConfiguration.outputLineHeight, }; } @@ -517,7 +580,10 @@ export class NotebookOptions extends Disposable { runGutter: 0, dragAndDropEnabled: false, fontSize: this._layoutConfiguration.fontSize, + outputFontSize: this._layoutConfiguration.outputFontSize, + outputFontFamily: this._layoutConfiguration.outputFontFamily, markupFontSize: this._layoutConfiguration.markupFontSize, + outputLineHeight: this._layoutConfiguration.outputLineHeight, }; } diff --git a/src/vs/workbench/contrib/notebook/test/browser/contrib/find.test.ts b/src/vs/workbench/contrib/notebook/test/browser/contrib/find.test.ts index b9822f87393..6b1282d9afb 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/contrib/find.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/contrib/find.test.ts @@ -68,11 +68,11 @@ suite('Notebook Find', () => { await found; assert.strictEqual(model.findMatches.length, 2); assert.strictEqual(model.currentMatch, -1); - model.find(false); + model.find({ previous: false }); assert.strictEqual(model.currentMatch, 0); - model.find(false); + model.find({ previous: false }); assert.strictEqual(model.currentMatch, 1); - model.find(false); + model.find({ previous: false }); assert.strictEqual(model.currentMatch, 0); assert.strictEqual(editor.textModel.length, 3); @@ -115,11 +115,11 @@ suite('Notebook Find', () => { // find matches is not necessarily find results assert.strictEqual(model.findMatches.length, 4); assert.strictEqual(model.currentMatch, -1); - model.find(false); + model.find({ previous: false }); assert.strictEqual(model.currentMatch, 0); - model.find(false); + model.find({ previous: false }); assert.strictEqual(model.currentMatch, 1); - model.find(false); + model.find({ previous: false }); assert.strictEqual(model.currentMatch, 2); const found2 = new Promise<boolean>(resolve => state.onFindReplaceStateChange(e => { @@ -132,13 +132,13 @@ suite('Notebook Find', () => { assert.strictEqual(model.findMatches.length, 3); assert.strictEqual(model.currentMatch, 2); - model.find(true); + model.find({ previous: true }); assert.strictEqual(model.currentMatch, 1); - model.find(false); + model.find({ previous: false }); assert.strictEqual(model.currentMatch, 2); - model.find(false); + model.find({ previous: false }); assert.strictEqual(model.currentMatch, 3); - model.find(false); + model.find({ previous: false }); assert.strictEqual(model.currentMatch, 0); }); }); @@ -166,7 +166,7 @@ suite('Notebook Find', () => { // find matches is not necessarily find results assert.strictEqual(model.findMatches.length, 4); assert.strictEqual(model.currentMatch, -1); - model.find(true); + model.find({ previous: true }); assert.strictEqual(model.currentMatch, 4); const found2 = new Promise<boolean>(resolve => state.onFindReplaceStateChange(e => { @@ -178,9 +178,9 @@ suite('Notebook Find', () => { await found2; assert.strictEqual(model.findMatches.length, 3); assert.strictEqual(model.currentMatch, 0); - model.find(true); + model.find({ previous: true }); assert.strictEqual(model.currentMatch, 3); - model.find(true); + model.find({ previous: true }); assert.strictEqual(model.currentMatch, 2); }); }); @@ -208,9 +208,9 @@ suite('Notebook Find', () => { // find matches is not necessarily find results assert.strictEqual(model.findMatches.length, 4); assert.strictEqual(model.currentMatch, -1); - model.find(false); - model.find(false); - model.find(false); + model.find({ previous: false }); + model.find({ previous: false }); + model.find({ previous: false }); assert.strictEqual(model.currentMatch, 2); const found2 = new Promise<boolean>(resolve => state.onFindReplaceStateChange(e => { if (e.matchesCount) { resolve(true); } @@ -244,11 +244,11 @@ suite('Notebook Find', () => { await found; assert.strictEqual(model.findMatches.length, 2); assert.strictEqual(model.currentMatch, -1); - model.find(false); + model.find({ previous: false }); assert.strictEqual(model.currentMatch, 0); - model.find(false); + model.find({ previous: false }); assert.strictEqual(model.currentMatch, 1); - model.find(false); + model.find({ previous: false }); assert.strictEqual(model.currentMatch, 0); assert.strictEqual(editor.textModel.length, 3); diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts index d71a25076b3..124729d29b4 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts @@ -10,7 +10,6 @@ import { isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { mock } from 'vs/base/test/common/mock'; import { IFileService } from 'vs/platform/files/common/files'; -import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { ILabelService } from 'vs/platform/label/common/label'; import { NullLogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -187,7 +186,6 @@ suite('NotebookFileWorkingCopyModel', function () { suite('ComplexNotebookEditorModel', function () { - const instaService = new InstantiationService(); const notebokService = new class extends mock<INotebookService>() { }; const backupService = new class extends mock<IWorkingCopyBackupService>() { }; const notificationService = new class extends mock<INotificationService>() { }; @@ -214,8 +212,8 @@ suite('ComplexNotebookEditorModel', function () { } }; - new ComplexNotebookEditorModel(r1, 'fff', notebookDataProvider, instaService, notebokService, workingCopyService, backupService, fileService, notificationService, new NullLogService(), untitledTextEditorService, labelService); - new ComplexNotebookEditorModel(r2, 'fff', notebookDataProvider, instaService, notebokService, workingCopyService, backupService, fileService, notificationService, new NullLogService(), untitledTextEditorService, labelService); + new ComplexNotebookEditorModel(r1, 'fff', notebookDataProvider, notebokService, workingCopyService, backupService, fileService, notificationService, new NullLogService(), untitledTextEditorService, labelService); + new ComplexNotebookEditorModel(r2, 'fff', notebookDataProvider, notebokService, workingCopyService, backupService, fileService, notificationService, new NullLogService(), untitledTextEditorService, labelService); assert.strictEqual(copies.length, 2); assert.strictEqual(!isEqual(copies[0].resource, copies[1].resource), true); diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts index 6f65268830f..8d3bff7a83a 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts @@ -20,7 +20,7 @@ import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewMod import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { CellKind, IOutputDto, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; -import { INotebookKernel, INotebookKernelService, ISelectedNotebooksChangeEvent } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernelService, IResolvedNotebookKernel, ISelectedNotebooksChangeEvent, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { setupInstantiationService, withTestNotebook as _withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; @@ -166,7 +166,8 @@ suite('NotebookExecutionService', () => { }); }); -class TestNotebookKernel implements INotebookKernel { +class TestNotebookKernel implements IResolvedNotebookKernel { + type: NotebookKernelType.Resolved = NotebookKernelType.Resolved; id: string = 'test'; label: string = ''; viewType = '*'; diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts index 887a4a05443..e5b7f84b8bf 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts @@ -21,7 +21,7 @@ import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/no import { CellEditType, CellKind, CellUri, IOutputDto, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookExecutionService } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; -import { INotebookKernel, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernelService, IResolvedNotebookKernel, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { setupInstantiationService, withTestNotebook as _withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; @@ -170,7 +170,8 @@ suite('NotebookExecutionStateService', () => { }); }); -class TestNotebookKernel implements INotebookKernel { +class TestNotebookKernel implements IResolvedNotebookKernel { + type: NotebookKernelType.Resolved = NotebookKernelType.Resolved; id: string = 'test'; label: string = ''; viewType = '*'; diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts index b1ebabc1db2..62d6afecb8b 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts @@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { setupInstantiationService, withTestNotebook as _withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; import { Emitter, Event } from 'vs/base/common/event'; -import { INotebookKernel, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { INotebookKernelService, IResolvedNotebookKernel, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { NotebookKernelService } from 'vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { mock } from 'vs/base/test/common/mock'; @@ -159,7 +159,8 @@ suite('NotebookKernelService', () => { }); }); -class TestNotebookKernel implements INotebookKernel { +class TestNotebookKernel implements IResolvedNotebookKernel { + type: NotebookKernelType.Resolved = NotebookKernelType.Resolved; id: string = Math.random() + 'kernel'; label: string = 'test-label'; viewType = '*'; |