diff --git a/.vscode/notebooks/endgame.github-issues b/.vscode/notebooks/endgame.github-issues index 881af2c14b4..bc2fba29ddf 100644 --- a/.vscode/notebooks/endgame.github-issues +++ b/.vscode/notebooks/endgame.github-issues @@ -7,7 +7,7 @@ { "kind": 2, "language": "github-issues", - "value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-js-debug repo:microsoft/vscode-remote-release repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-remotehub\n\n$MILESTONE=milestone:\"April 2021\"" + "value": "$REPOS=repo:microsoft/vscode repo:microsoft/vscode-internalbacklog repo:microsoft/vscode-js-debug repo:microsoft/vscode-remote-release repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-settings-sync-server repo:microsoft/vscode-emmet-helper repo:microsoft/vscode-remotehub\n\n$MILESTONE=milestone:\"May 2021\"" }, { "kind": 1, diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index b9184c3c3dd..728a439f0dd 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -40,7 +40,7 @@ } }, "contributes": { - "notebookMarkupRenderers": [ + "notebookOutputRenderer": [ { "id": "markdownItRenderer", "displayName": "Markdown it renderer", diff --git a/extensions/notebook-markdown-extensions/package.json b/extensions/notebook-markdown-extensions/package.json index 0c7d9d52d41..a68a8b07114 100644 --- a/extensions/notebook-markdown-extensions/package.json +++ b/extensions/notebook-markdown-extensions/package.json @@ -21,18 +21,28 @@ } }, "contributes": { - "notebookMarkupRenderers": [ + "notebookOutputRenderer": [ { "id": "markdownItRenderer-katex", "displayName": "Markdown it katex renderer", "entrypoint": "./notebook-out/katex.js", - "dependsOn": "markdownItRenderer" + "mimeTypes": [ + "text/markdown" + ], + "dependencies": [ + "markdownItRenderer" + ] }, { "id": "markdownItRenderer-emoji", "displayName": "Markdown it emoji renderer", "entrypoint": "./notebook-out/emoji.js", - "dependsOn": "markdownItRenderer" + "mimeTypes": [ + "text/markdown" + ], + "dependencies": [ + "markdownItRenderer" + ] } ] }, diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index db9279b43fc..da178fb01ae 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -117,7 +117,7 @@ ] } ], - "notebookProvider": [ + "notebooks": [ { "viewType": "notebookCoreTest", "displayName": "Notebook Core Test", diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts index 1a1396d3edf..26f796d3fc8 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts @@ -40,9 +40,7 @@ suite('Notebook Editor', function () { }); - test.skip('showNotebookDocment', async function () { - - const count1 = vscode.notebook.notebookDocuments.length; + test('showNotebookDocment', async function () { const p = utils.asPromise(vscode.notebook.onDidOpenNotebookDocument); const uri = await utils.createRandomFile(undefined, undefined, '.nbdtest'); @@ -53,9 +51,8 @@ suite('Notebook Editor', function () { const event = await p; assert.strictEqual(event.uri.toString(), uri.toString()); - const count2 = vscode.notebook.notebookDocuments.length; - assert.strictEqual(count1 + 1, count2); - + const includes = vscode.notebook.notebookDocuments.includes(editor.document); + assert.strictEqual(true, includes); }); test('notebook editor has viewColumn', async function () { diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts index 76c80cb2810..74ee6bfa705 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts @@ -121,7 +121,7 @@ suite('Notebook API tests', function () { kind: vscode.NotebookCellKind.Code, outputs: [], metadata: new vscode.NotebookCellMetadata().with({ custom: { testCellMetadata: 123 } }), - latestExecutionSummary: { startTime: 10, endTime: 20 } + executionSummary: { startTime: 10, endTime: 20 } }, { value: 'test2', @@ -133,7 +133,7 @@ suite('Notebook API tests', function () { ], { testOutputMetadata: true }) ], - latestExecutionSummary: { executionOrder: 5, success: true }, + executionSummary: { executionOrder: 5, success: true }, metadata: new vscode.NotebookCellMetadata().with({ custom: { testCellMetadata: 456 } }) } ] @@ -478,8 +478,8 @@ suite('Notebook API tests', function () { assert.strictEqual(secondCell!.outputs[0].outputs[0].mime, 'text/plain'); assert.strictEqual(secondCell!.outputs[0].outputs[0].value, 'Hello World'); assert.deepStrictEqual(secondCell!.outputs[0].outputs[0].metadata, { testOutputItemMetadata: true }); - assert.strictEqual(secondCell!.latestExecutionSummary?.executionOrder, 5); - assert.strictEqual(secondCell!.latestExecutionSummary?.success, true); + assert.strictEqual(secondCell!.executionSummary?.executionOrder, 5); + assert.strictEqual(secondCell!.executionSummary?.success, true); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), ''); @@ -1205,30 +1205,30 @@ suite('Notebook API tests', function () { verifyOutputSyncKernel.controller.dispose(); }); - test('latestExecutionSummary', async () => { + test('executionSummary', async () => { const resource = await createRandomNotebookFile(); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); const editor = vscode.window.activeNotebookEditor!; const cell = editor.document.cellAt(0); - assert.strictEqual(cell.latestExecutionSummary?.success, undefined); - assert.strictEqual(cell.latestExecutionSummary?.executionOrder, undefined); + assert.strictEqual(cell.executionSummary?.success, undefined); + assert.strictEqual(cell.executionSummary?.executionOrder, undefined); await vscode.commands.executeCommand('notebook.cell.execute'); assert.strictEqual(cell.outputs.length, 1, 'should execute'); - assert.ok(cell.latestExecutionSummary); - assert.strictEqual(cell.latestExecutionSummary!.success, true); - assert.strictEqual(typeof cell.latestExecutionSummary!.executionOrder, 'number'); + assert.ok(cell.executionSummary); + assert.strictEqual(cell.executionSummary!.success, true); + assert.strictEqual(typeof cell.executionSummary!.executionOrder, 'number'); }); - test('initialize latestExecutionSummary', async () => { + test('initialize executionSummary', async () => { const document = await openRandomNotebookDocument(); const cell = document.cellAt(0); - assert.strictEqual(cell.latestExecutionSummary?.success, undefined); - assert.strictEqual(cell.latestExecutionSummary?.startTime, 10); - assert.strictEqual(cell.latestExecutionSummary?.endTime, 20); + assert.strictEqual(cell.executionSummary?.success, undefined); + assert.strictEqual(cell.executionSummary?.startTime, 10); + assert.strictEqual(cell.executionSummary?.endTime, 20); }); diff --git a/extensions/vscode-notebook-tests/package.json b/extensions/vscode-notebook-tests/package.json index c43a17911d1..c6c690151b6 100644 --- a/extensions/vscode-notebook-tests/package.json +++ b/extensions/vscode-notebook-tests/package.json @@ -34,7 +34,7 @@ "icon": "$(debug)" } ], - "notebookProvider": [ + "notebooks": [ { "viewType": "notebookSmokeTest", "displayName": "Notebook Smoke Test", diff --git a/package.json b/package.json index 426acf5a442..7c7fadfa2bf 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.57.0", - "distro": "5a7e0f9786abefbe5c166898c7cb71537b3d651e", + "distro": "d4088d7fa3ad4446065d4cec45d90d02d04fe745", "author": { "name": "Microsoft Corporation" }, diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index e5b9492f3c0..a5d014dc8a8 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -1179,28 +1179,44 @@ export function computeScreenAwareSize(cssPx: number): number { } /** + * Open safely a new window. This is the best way to do so, but you cannot tell + * if the window was opened or if it was blocked by the brower's popup blocker. + * If you want to tell if the browser blocked the new window, use `windowOpenNoOpenerWithSuccess`. + * * See https://github.com/microsoft/monaco-editor/issues/601 * To protect against malicious code in the linked site, particularly phishing attempts, * the window.opener should be set to null to prevent the linked site from having access * to change the location of the current page. * See https://mathiasbynens.github.io/rel-noopener/ */ -export function windowOpenNoOpener(url: string): boolean { - if (browser.isElectron || browser.isEdgeLegacyWebView) { - // In VSCode, window.open() always returns null... - // The same is true for a WebView (see https://github.com/microsoft/monaco-editor/issues/628) - // Also call directly window.open in sandboxed Electron (see https://github.com/microsoft/monaco-editor/issues/2220) - window.open(url); +export function windowOpenNoOpener(url: string): void { + // By using 'noopener' in the `windowFeatures` argument, the newly created window will + // not be able to use `window.opener` to reach back to the current page. + // See https://stackoverflow.com/a/46958731 + // See https://developer.mozilla.org/en-US/docs/Web/API/Window/open#noopener + // However, this also doesn't allow us to realize if the browser blocked + // the creation of the window. + window.open(url, '_blank', 'noopener'); +} + +/** + * Open safely a new window. This technique is not appropiate in certain contexts, + * like for example when the JS context is executing inside a sandboxed iframe. + * If it is not necessary to know if the browser blocked the new window, use + * `windowOpenNoOpener`. + * + * See https://github.com/microsoft/monaco-editor/issues/601 + * See https://github.com/microsoft/monaco-editor/issues/2474 + * See https://mathiasbynens.github.io/rel-noopener/ + */ +export function windowOpenNoOpenerWithSuccess(url: string): boolean { + const newTab = window.open(); + if (newTab) { + (newTab as any).opener = null; + newTab.location.href = url; return true; - } else { - let newTab = window.open(); - if (newTab) { - (newTab as any).opener = null; - newTab.location.href = url; - return true; - } - return false; } + return false; } export function animate(fn: () => void): IDisposable { @@ -1596,3 +1612,13 @@ export function getCookieValue(name: string): string | undefined { return match ? match.pop() : undefined; } + +export function addMatchMediaChangeListener(query: string, callback: () => void): void { + const mediaQueryList = window.matchMedia(query); + if (typeof mediaQueryList.addEventListener === 'function') { + mediaQueryList.addEventListener('change', callback); + } else { + // Safari 13.x + mediaQueryList.addListener(callback); + } +} diff --git a/src/vs/base/common/platform.ts b/src/vs/base/common/platform.ts index 5bbdbe95df0..512775eaddc 100644 --- a/src/vs/base/common/platform.ts +++ b/src/vs/base/common/platform.ts @@ -53,7 +53,7 @@ declare const self: unknown; export const globals: any = (typeof self === 'object' ? self : typeof global === 'object' ? global : {}); let nodeProcess: INodeProcess | undefined = undefined; -if (typeof globals.vscode !== 'undefined') { +if (typeof globals.vscode !== 'undefined' && typeof globals.vscode.process !== 'undefined') { // Native environment (sandboxed) nodeProcess = globals.vscode.process; } else if (typeof process !== 'undefined') { diff --git a/src/vs/base/common/process.ts b/src/vs/base/common/process.ts index 2df6dd5df2f..6c4b1f407d5 100644 --- a/src/vs/base/common/process.ts +++ b/src/vs/base/common/process.ts @@ -9,7 +9,7 @@ let safeProcess: INodeProcess & { nextTick: (callback: (...args: any[]) => void) declare const process: INodeProcess; // Native sandbox environment -if (typeof globals.vscode !== 'undefined') { +if (typeof globals.vscode !== 'undefined' && typeof globals.vscode.process !== 'undefined') { const sandboxProcess: INodeProcess = globals.vscode.process; safeProcess = { get platform() { return sandboxProcess.platform; }, diff --git a/src/vs/editor/browser/editorExtensions.ts b/src/vs/editor/browser/editorExtensions.ts index 4c22dbf8e3b..9cf6d8db06e 100644 --- a/src/vs/editor/browser/editorExtensions.ts +++ b/src/vs/editor/browser/editorExtensions.ts @@ -349,14 +349,16 @@ export abstract class EditorAction extends EditorCommand { public abstract run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void | Promise; } +export type EditorActionImplementation = (accessor: ServicesAccessor, editor: ICodeEditor, args: any) => boolean | Promise; + export class MultiEditorAction extends EditorAction { - private readonly _implementations: [number, CommandImplementation][] = []; + private readonly _implementations: [number, EditorActionImplementation][] = []; /** * A higher priority gets to be looked at first */ - public addImplementation(priority: number, implementation: CommandImplementation): IDisposable { + public addImplementation(priority: number, implementation: EditorActionImplementation): IDisposable { this._implementations.push([priority, implementation]); this._implementations.sort((a, b) => b[0] - a[0]); return { @@ -373,7 +375,7 @@ export class MultiEditorAction extends EditorAction { public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void | Promise { for (const impl of this._implementations) { - const result = impl[1](accessor, args); + const result = impl[1](accessor, editor, args); if (result) { if (typeof result === 'boolean') { return; diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index cbf16da575e..1365a731be3 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -226,7 +226,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE protected readonly _instantiationService: IInstantiationService; protected readonly _contextKeyService: IContextKeyService; private readonly _notificationService: INotificationService; - private readonly _codeEditorService: ICodeEditorService; + protected readonly _codeEditorService: ICodeEditorService; private readonly _commandService: ICommandService; private readonly _themeService: IThemeService; @@ -1048,6 +1048,10 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE return; } + this._triggerCommand(handlerId, payload); + } + + protected _triggerCommand(handlerId: string, payload: any): void { this._commandService.executeCommand(handlerId, payload); } diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 4eb14e26c69..ffd74758a35 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -383,7 +383,7 @@ export interface IEditorOptions { */ suggest?: ISuggestOptions; /** - * Smart select opptions; + * Smart select options. */ smartSelect?: ISmartSelectOptions; /** @@ -638,6 +638,10 @@ export interface IEditorOptions { * Control the behavior and rendering of the inline hints. */ inlayHints?: IEditorInlayHintsOptions; + /** + * Control if the editor should use shadow DOM. + */ + useShadowDOM?: boolean; } /** @@ -3845,6 +3849,7 @@ export const enum EditorOption { tabCompletion, tabIndex, unusualLineTerminators, + useShadowDOM, useTabStops, wordSeparators, wordWrap, @@ -4460,6 +4465,9 @@ export const EditorOptions = { description: nls.localize('unusualLineTerminators', "Remove unusual line terminators that might cause problems.") } )), + useShadowDOM: register(new EditorBooleanOption( + EditorOption.useShadowDOM, 'useShadowDOM', true + )), useTabStops: register(new EditorBooleanOption( EditorOption.useTabStops, 'useTabStops', true, { description: nls.localize('useTabStops', "Inserting and deleting whitespace follows tab stops.") } diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index cbcaacd273d..cbe55b06871 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -1220,7 +1220,6 @@ export interface ITextModel { /** * An event emitted when the model has been attached to the first editor or detached from the last editor. * @event - * @internal */ onDidChangeAttached(listener: () => void): IDisposable; /** @@ -1247,7 +1246,6 @@ export interface ITextModel { /** * Returns if this model is attached to an editor or not. - * @internal */ isAttachedToEditor(): boolean; diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index 6093e74955f..004d924d1fc 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -278,23 +278,24 @@ export enum EditorOption { tabCompletion = 108, tabIndex = 109, unusualLineTerminators = 110, - useTabStops = 111, - wordSeparators = 112, - wordWrap = 113, - wordWrapBreakAfterCharacters = 114, - wordWrapBreakBeforeCharacters = 115, - wordWrapColumn = 116, - wordWrapOverride1 = 117, - wordWrapOverride2 = 118, - wrappingIndent = 119, - wrappingStrategy = 120, - showDeprecated = 121, - inlayHints = 122, - editorClassName = 123, - pixelRatio = 124, - tabFocusMode = 125, - layoutInfo = 126, - wrappingInfo = 127 + useShadowDOM = 111, + useTabStops = 112, + wordSeparators = 113, + wordWrap = 114, + wordWrapBreakAfterCharacters = 115, + wordWrapBreakBeforeCharacters = 116, + wordWrapColumn = 117, + wordWrapOverride1 = 118, + wordWrapOverride2 = 119, + wrappingIndent = 120, + wrappingStrategy = 121, + showDeprecated = 122, + inlayHints = 123, + editorClassName = 124, + pixelRatio = 125, + tabFocusMode = 126, + layoutInfo = 127, + wrappingInfo = 128 } /** diff --git a/src/vs/editor/contrib/codeAction/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/codeActionMenu.ts index e4a05011d01..d4b830000f0 100644 --- a/src/vs/editor/contrib/codeAction/codeActionMenu.ts +++ b/src/vs/editor/contrib/codeAction/codeActionMenu.ts @@ -11,6 +11,7 @@ import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; import { Lazy } from 'vs/base/common/lazy'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { ScrollType } from 'vs/editor/common/editorCommon'; import { CodeAction, CodeActionProviderRegistry, Command } from 'vs/editor/common/modes'; @@ -92,8 +93,10 @@ export class CodeActionMenu extends Disposable { const anchor = Position.isIPosition(at) ? this._toCoords(at) : at || { x: 0, y: 0 }; const resolver = this._keybindingResolver.getResolver(); + const useShadowDOM = this._editor.getOption(EditorOption.useShadowDOM); + this._contextMenuService.showContextMenu({ - domForShadowRoot: this._editor.getDomNode()!, + domForShadowRoot: useShadowDOM ? this._editor.getDomNode()! : undefined, getAnchor: () => anchor, getActions: () => menuActions, onHide: () => { diff --git a/src/vs/editor/contrib/contextmenu/contextmenu.ts b/src/vs/editor/contrib/contextmenu/contextmenu.ts index 5c95f58c85f..ace4b1b8d3e 100644 --- a/src/vs/editor/contrib/contextmenu/contextmenu.ts +++ b/src/vs/editor/contrib/contextmenu/contextmenu.ts @@ -209,10 +209,12 @@ export class ContextMenuController implements IEditorContribution { anchor = { x: posx, y: posy }; } + const useShadowDOM = this._editor.getOption(EditorOption.useShadowDOM); + // Show menu this._contextMenuIsBeingShownCount++; this._contextMenuService.showContextMenu({ - domForShadowRoot: this._editor.getDomNode(), + domForShadowRoot: useShadowDOM ? this._editor.getDomNode() : undefined, getAnchor: () => anchor!, diff --git a/src/vs/editor/contrib/find/findController.ts b/src/vs/editor/contrib/find/findController.ts index 3993c6c6224..b7f3bba4aef 100644 --- a/src/vs/editor/contrib/find/findController.ts +++ b/src/vs/editor/contrib/find/findController.ts @@ -26,7 +26,6 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag import { IThemeService } from 'vs/platform/theme/common/themeService'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; -import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; const SEARCH_STRING_MAX_LENGTH = 524288; @@ -502,12 +501,7 @@ export const StartFindAction = registerMultiEditorAction(new MultiEditorAction({ } })); -StartFindAction.addImplementation(0, (accessor: ServicesAccessor, args: any): boolean | Promise => { - const codeEditorService = accessor.get(ICodeEditorService); - const editor = codeEditorService.getFocusedCodeEditor() || codeEditorService.getActiveCodeEditor(); - if (!editor) { - return false; - } +StartFindAction.addImplementation(0, (accessor: ServicesAccessor, editor: ICodeEditor, args: any): boolean | Promise => { const controller = CommonFindController.get(editor); if (!controller) { return false; @@ -726,10 +720,8 @@ export const StartFindReplaceAction = registerMultiEditorAction(new MultiEditorA } })); -StartFindReplaceAction.addImplementation(0, (accessor: ServicesAccessor, args: any): boolean | Promise => { - const codeEditorService = accessor.get(ICodeEditorService); - const editor = codeEditorService.getFocusedCodeEditor() || codeEditorService.getActiveCodeEditor(); - if (!editor || !editor.hasModel() || editor.getOption(EditorOption.readOnly)) { +StartFindReplaceAction.addImplementation(0, (accessor: ServicesAccessor, editor: ICodeEditor, args: any): boolean | Promise => { + if (!editor.hasModel() || editor.getOption(EditorOption.readOnly)) { return false; } const controller = CommonFindController.get(editor); diff --git a/src/vs/editor/contrib/linesOperations/linesOperations.ts b/src/vs/editor/contrib/linesOperations/linesOperations.ts index 64b689a5fc2..99abacb0715 100644 --- a/src/vs/editor/contrib/linesOperations/linesOperations.ts +++ b/src/vs/editor/contrib/linesOperations/linesOperations.ts @@ -1045,7 +1045,41 @@ export class TitleCaseAction extends AbstractCaseAction { } } +class BackwardsCompatibleRegExp { + + private _actual: RegExp | null; + private _evaluated: boolean; + + constructor( + private readonly _pattern: string, + private readonly _flags: string + ) { + this._actual = null; + this._evaluated = false; + } + + public get(): RegExp | null { + if (!this._evaluated) { + this._evaluated = true; + try { + this._actual = new RegExp(this._pattern, this._flags); + } catch (err) { + // this browser does not support this regular expression + } + } + return this._actual; + } + + public isSupported(): boolean { + return (this.get() !== null); + } +} + export class SnakeCaseAction extends AbstractCaseAction { + + public static regExp1 = new BackwardsCompatibleRegExp('(\\p{Ll})(\\p{Lu})', 'gmu'); + public static regExp2 = new BackwardsCompatibleRegExp('(\\p{Lu}|\\p{N})(\\p{Lu})(\\p{Ll})', 'gmu'); + constructor() { super({ id: 'editor.action.transformToSnakecase', @@ -1056,9 +1090,15 @@ export class SnakeCaseAction extends AbstractCaseAction { } protected _modifyText(text: string, wordSeparators: string): string { + const regExp1 = SnakeCaseAction.regExp1.get(); + const regExp2 = SnakeCaseAction.regExp2.get(); + if (!regExp1 || !regExp2) { + // cannot support this + return text; + } return (text - .replace(/(\p{Ll})(\p{Lu})/gmu, '$1_$2') - .replace(/(\p{Lu}|\p{N})(\p{Lu})(\p{Ll})/gmu, '$1_$2$3') + .replace(regExp1, '$1_$2') + .replace(regExp2, '$1_$2$3') .toLocaleLowerCase() ); } @@ -1084,4 +1124,7 @@ registerEditorAction(TransposeAction); registerEditorAction(UpperCaseAction); registerEditorAction(LowerCaseAction); registerEditorAction(TitleCaseAction); -registerEditorAction(SnakeCaseAction); + +if (SnakeCaseAction.regExp1.isSupported() && SnakeCaseAction.regExp2.isSupported()) { + registerEditorAction(SnakeCaseAction); +} diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts index 89dff41d626..77884d5ffde 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts @@ -34,6 +34,7 @@ import { StandaloneThemeServiceImpl } from 'vs/editor/standalone/browser/standal import { IModelService } from 'vs/editor/common/services/modelService'; import { ILanguageSelection, IModeService } from 'vs/editor/common/services/modeService'; import { URI } from 'vs/base/common/uri'; +import { StandaloneCodeEditorServiceImpl } from 'vs/editor/standalone/browser/standaloneCodeServiceImpl'; /** * Description of an action contribution @@ -363,6 +364,20 @@ export class StandaloneCodeEditor extends CodeEditorWidget implements IStandalon return toDispose; } + + protected override _triggerCommand(handlerId: string, payload: any): void { + if (this._codeEditorService instanceof StandaloneCodeEditorServiceImpl) { + // Help commands find this editor as the active editor + try { + this._codeEditorService.setActiveCodeEditor(this); + super._triggerCommand(handlerId, payload); + } finally { + this._codeEditorService.setActiveCodeEditor(null); + } + } else { + super._triggerCommand(handlerId, payload); + } + } } export class StandaloneEditor extends StandaloneCodeEditor implements IStandaloneCodeEditor { diff --git a/src/vs/editor/standalone/browser/standaloneCodeServiceImpl.ts b/src/vs/editor/standalone/browser/standaloneCodeServiceImpl.ts index f7808cfa6fc..e14ce169335 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeServiceImpl.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeServiceImpl.ts @@ -18,6 +18,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; export class StandaloneCodeEditorServiceImpl extends CodeEditorServiceImpl { private readonly _editorIsOpen: IContextKey; + private _activeCodeEditor: ICodeEditor | null; constructor( styleSheet: GlobalStyleSheet | null, @@ -28,6 +29,7 @@ export class StandaloneCodeEditorServiceImpl extends CodeEditorServiceImpl { this.onCodeEditorAdd(() => this._checkContextKey()); this.onCodeEditorRemove(() => this._checkContextKey()); this._editorIsOpen = contextKeyService.createKey('editorIsOpen', false); + this._activeCodeEditor = null; } private _checkContextKey(): void { @@ -41,8 +43,12 @@ export class StandaloneCodeEditorServiceImpl extends CodeEditorServiceImpl { this._editorIsOpen.set(hasCodeEditor); } + public setActiveCodeEditor(activeCodeEditor: ICodeEditor | null): void { + this._activeCodeEditor = activeCodeEditor; + } + public getActiveCodeEditor(): ICodeEditor | null { - return null; // not supported in the standalone case + return this._activeCodeEditor; } public openCodeEditor(input: IResourceEditorInput, source: ICodeEditor | null, sideBySide?: boolean): Promise { diff --git a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts index 180961e72fd..ece509fa0d0 100644 --- a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts +++ b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts @@ -233,7 +233,7 @@ export class StandaloneThemeServiceImpl extends Disposable implements IStandalon this._updateCSS(); }); - window.matchMedia('(forced-colors: active)').addEventListener('change', () => { + dom.addMatchMediaChangeListener('(forced-colors: active)', () => { this._updateActualTheme(); }); } diff --git a/src/vs/editor/standalone/common/monarch/monarchCompile.ts b/src/vs/editor/standalone/common/monarch/monarchCompile.ts index 1357baefea7..04dc7241beb 100644 --- a/src/vs/editor/standalone/common/monarch/monarchCompile.ts +++ b/src/vs/editor/standalone/common/monarch/monarchCompile.ts @@ -86,15 +86,14 @@ function createKeywordMatcher(arr: string[], caseInsensitive: boolean = false): * @example /@@text/ will not be replaced and will become /@text/. */ function compileRegExp(lexer: monarchCommon.ILexerMin, str: string): RegExp { + // @@ must be interpreted as a literal @, so we replace all occurences of @@ with a placeholder character + str = str.replace(/@@/g, `\x01`); + let n = 0; let hadExpansion: boolean; do { hadExpansion = false; - str = str.replace(/(.|^)@(\w+)/g, function (s, charBeforeAtSign, attr?) { - if (charBeforeAtSign === '@') { - // do not expand @@ - return s; - } + str = str.replace(/@(\w+)/g, function (s, attr?) { hadExpansion = true; let sub = ''; if (typeof (lexer[attr]) === 'string') { @@ -108,13 +107,13 @@ function compileRegExp(lexer: monarchCommon.ILexerMin, str: string): RegExp { throw monarchCommon.createError(lexer, 'attribute reference \'' + attr + '\' must be a string, used at: ' + str); } } - return charBeforeAtSign + (monarchCommon.empty(sub) ? '' : '(?:' + sub + ')'); + return (monarchCommon.empty(sub) ? '' : '(?:' + sub + ')'); }); n++; } while (hadExpansion && n < 5); // handle escaped @@ - str = str.replace(/@@/g, '@'); + str = str.replace(/\x01/g, '@'); let flags = (lexer.ignoreCase ? 'i' : '') + (lexer.unicode ? 'u' : ''); return new RegExp(str, flags); diff --git a/src/vs/editor/standalone/test/monarch/monarch.test.ts b/src/vs/editor/standalone/test/monarch/monarch.test.ts index fd57288fdca..95686726d7e 100644 --- a/src/vs/editor/standalone/test/monarch/monarch.test.ts +++ b/src/vs/editor/standalone/test/monarch/monarch.test.ts @@ -264,4 +264,31 @@ suite('Monarch', () => { ]); }); + test('microsoft/monaco-editor#2424: Allow to target @@', () => { + const modeService = new ModeServiceImpl(); + + const tokenizer = createMonarchTokenizer(modeService, 'test', { + ignoreCase: false, + tokenizer: { + root: [ + { + regex: /@@@@/, + action: { token: 'ham' } + }, + ], + }, + }); + + const lines = [ + `@@` + ]; + + const actualTokens = getTokens(tokenizer, lines); + assert.deepStrictEqual(actualTokens, [ + [ + new Token(0, 'ham.test', 'test'), + ] + ]); + }); + }); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 9773af510f4..8128cb36216 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -1983,6 +1983,11 @@ declare namespace monaco.editor { * @event */ onDidChangeLanguageConfiguration(listener: (e: IModelLanguageConfigurationChangedEvent) => void): IDisposable; + /** + * An event emitted when the model has been attached to the first editor or detached from the last editor. + * @event + */ + onDidChangeAttached(listener: () => void): IDisposable; /** * An event emitted right before disposing the model. * @event @@ -1993,6 +1998,10 @@ declare namespace monaco.editor { * and make all necessary clean-up to release this object to the GC. */ dispose(): void; + /** + * Returns if this model is attached to an editor or not. + */ + isAttachedToEditor(): boolean; } /** @@ -2965,7 +2974,7 @@ declare namespace monaco.editor { */ suggest?: ISuggestOptions; /** - * Smart select opptions; + * Smart select options. */ smartSelect?: ISmartSelectOptions; /** @@ -3220,6 +3229,10 @@ declare namespace monaco.editor { * Control the behavior and rendering of the inline hints. */ inlayHints?: IEditorInlayHintsOptions; + /** + * Control if the editor should use shadow DOM. + */ + useShadowDOM?: boolean; } /** @@ -4094,23 +4107,24 @@ declare namespace monaco.editor { tabCompletion = 108, tabIndex = 109, unusualLineTerminators = 110, - useTabStops = 111, - wordSeparators = 112, - wordWrap = 113, - wordWrapBreakAfterCharacters = 114, - wordWrapBreakBeforeCharacters = 115, - wordWrapColumn = 116, - wordWrapOverride1 = 117, - wordWrapOverride2 = 118, - wrappingIndent = 119, - wrappingStrategy = 120, - showDeprecated = 121, - inlayHints = 122, - editorClassName = 123, - pixelRatio = 124, - tabFocusMode = 125, - layoutInfo = 126, - wrappingInfo = 127 + useShadowDOM = 111, + useTabStops = 112, + wordSeparators = 113, + wordWrap = 114, + wordWrapBreakAfterCharacters = 115, + wordWrapBreakBeforeCharacters = 116, + wordWrapColumn = 117, + wordWrapOverride1 = 118, + wordWrapOverride2 = 119, + wrappingIndent = 120, + wrappingStrategy = 121, + showDeprecated = 122, + inlayHints = 123, + editorClassName = 124, + pixelRatio = 125, + tabFocusMode = 126, + layoutInfo = 127, + wrappingInfo = 128 } export const EditorOptions: { acceptSuggestionOnCommitCharacter: IEditorOption; @@ -4226,6 +4240,7 @@ declare namespace monaco.editor { tabCompletion: IEditorOption; tabIndex: IEditorOption; unusualLineTerminators: IEditorOption; + useShadowDOM: IEditorOption; useTabStops: IEditorOption; wordSeparators: IEditorOption; wordWrap: IEditorOption; diff --git a/src/vs/platform/extensions/common/extensions.ts b/src/vs/platform/extensions/common/extensions.ts index 002d657337b..2b6d00385f9 100644 --- a/src/vs/platform/extensions/common/extensions.ts +++ b/src/vs/platform/extensions/common/extensions.ts @@ -117,9 +117,9 @@ export interface IWalkthroughStep { readonly id: string; readonly title: string; readonly description: string | undefined; - readonly media: - | { path: string | { dark: string, light: string, hc: string }, altText: string } - | { path: string, }, + readonly media: { path: string | { dark: string, light: string, hc: string }, altText?: string } + readonly completionEvents?: string[]; + /** @deprecated use `completionEvents: 'onCommand:...'` */ readonly doneOn?: { command: string }; readonly when?: string; } diff --git a/src/vs/platform/files/common/fileService.ts b/src/vs/platform/files/common/fileService.ts index 2071ff81d60..f0f7a41c06e 100644 --- a/src/vs/platform/files/common/fileService.ts +++ b/src/vs/platform/files/common/fileService.ts @@ -234,7 +234,7 @@ export class FileService extends Disposable implements IFileService { mtime: stat.mtime, ctime: stat.ctime, size: stat.size, - readonly: Boolean(stat.permissions ?? 0 & FilePermission.Readonly) ?? Boolean(provider.capabilities & FileSystemProviderCapabilities.Readonly), + readonly: Boolean(stat.permissions ?? 0 & FilePermission.Readonly) || Boolean(provider.capabilities & FileSystemProviderCapabilities.Readonly), etag: etag({ mtime: stat.mtime, size: stat.size }) }; diff --git a/src/vs/platform/opener/browser/link.ts b/src/vs/platform/opener/browser/link.ts index c3d3d7d0298..37e677ff045 100644 --- a/src/vs/platform/opener/browser/link.ts +++ b/src/vs/platform/opener/browser/link.ts @@ -18,6 +18,10 @@ export interface ILinkDescriptor { readonly title?: string; } +export interface ILinkOptions { + readonly opener?: (href: string) => void; +} + export interface ILinkStyles { readonly textLinkForeground?: Color; readonly disabled?: boolean; @@ -33,6 +37,7 @@ export class Link extends Disposable { constructor( link: ILinkDescriptor, + options: ILinkOptions | undefined = undefined, @IOpenerService openerService: IOpenerService ) { super(); @@ -53,7 +58,11 @@ export class Link extends Disposable { this._register(onOpen(e => { EventHelper.stop(e, true); if (!this.disabled) { - openerService.open(link.href, { allowCommands: true }); + if (options?.opener) { + options.opener(link.href); + } else { + openerService.open(link.href, { allowCommands: true }); + } } })); diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts index 11a5cdd663a..28e36704128 100644 --- a/src/vs/platform/product/common/product.ts +++ b/src/vs/platform/product/common/product.ts @@ -13,7 +13,7 @@ import { ISandboxConfiguration } from 'vs/base/parts/sandbox/common/sandboxTypes let product: IProductConfiguration; // Native sandbox environment -if (typeof globals.vscode !== 'undefined') { +if (typeof globals.vscode !== 'undefined' && typeof globals.vscode.context !== 'undefined') { const configuration: ISandboxConfiguration | undefined = globals.vscode.context.configuration(); if (configuration) { product = configuration.product; diff --git a/src/vs/platform/telemetry/common/commonProperties.ts b/src/vs/platform/telemetry/common/commonProperties.ts index 454b873fde1..9bcceeef065 100644 --- a/src/vs/platform/telemetry/common/commonProperties.ts +++ b/src/vs/platform/telemetry/common/commonProperties.ts @@ -101,7 +101,7 @@ export async function resolveCommonProperties( return result; } -function verifyMicrosoftInternalDomain(domainList: readonly string[]): boolean { +export function verifyMicrosoftInternalDomain(domainList: readonly string[]): boolean { const userDnsDomain = env['USERDNSDOMAIN']; if (!userDnsDomain) { return false; diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index abdba338579..815db15d8fb 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -174,7 +174,7 @@ export interface IOffProcessTerminalService { getShellEnvironment(): Promise; setTerminalLayoutInfo(layoutInfo?: ITerminalsLayoutInfoById): Promise; updateTitle(id: number, title: string): Promise; - updateIcon(id: number, icon: string): Promise; + updateIcon(id: number, icon: string, color?: string): Promise; getTerminalLayoutInfo(): Promise; reduceConnectionGraceTime(): Promise; } @@ -239,9 +239,8 @@ export interface IPtyService { processBinary(id: number, data: string): Promise; /** Confirm the process is _not_ an orphan. */ orphanQuestionReply(id: number): Promise; - updateTitle(id: number, title: string): Promise - updateIcon(id: number, icon: string): Promise - + updateTitle(id: number, title: string): Promise; + updateIcon(id: number, icon: string, color?: string): Promise; getDefaultSystemShell(osOverride?: OperatingSystem): Promise; getProfiles?(includeDetectedProfiles?: boolean): Promise; getEnvironment(): Promise; @@ -349,7 +348,7 @@ export interface IShellLaunchConfig { /** * This is a terminal that attaches to an already running terminal. */ - attachPersistentProcess?: { id: number; pid: number; title: string; cwd: string; icon?: string; }; + attachPersistentProcess?: { id: number; pid: number; title: string; cwd: string; icon?: string; color?: string }; /** * Whether the terminal process environment should be exactly as provided in @@ -393,6 +392,11 @@ export interface IShellLaunchConfig { * icon. */ icon?: string; + + /** + * The color ID to use for this terminal. If not specified it will use the default fallback + */ + color?: string; } export interface IShellLaunchConfigDto { diff --git a/src/vs/platform/terminal/common/terminalProcess.ts b/src/vs/platform/terminal/common/terminalProcess.ts index 2b0ea4ac642..3fd0e6bb77b 100644 --- a/src/vs/platform/terminal/common/terminalProcess.ts +++ b/src/vs/platform/terminal/common/terminalProcess.ts @@ -56,6 +56,7 @@ export interface IProcessDetails { workspaceName: string; isOrphan: boolean; icon: string | undefined; + color: string | undefined; } export type ITerminalTabLayoutInfoDto = IRawTerminalTabLayoutInfo; diff --git a/src/vs/platform/terminal/node/ptyHostService.ts b/src/vs/platform/terminal/node/ptyHostService.ts index e82d2a8a611..3568e0e3d94 100644 --- a/src/vs/platform/terminal/node/ptyHostService.ts +++ b/src/vs/platform/terminal/node/ptyHostService.ts @@ -170,8 +170,8 @@ export class PtyHostService extends Disposable implements IPtyService { updateTitle(id: number, title: string): Promise { return this._proxy.updateTitle(id, title); } - updateIcon(id: number, icon: string): Promise { - return this._proxy.updateIcon(id, icon); + updateIcon(id: number, icon: string, color?: string): Promise { + return this._proxy.updateIcon(id, icon, color); } attachToProcess(id: number): Promise { return this._proxy.attachToProcess(id); diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts index b3ce3a0292a..da9f3f99a9e 100644 --- a/src/vs/platform/terminal/node/ptyService.ts +++ b/src/vs/platform/terminal/node/ptyService.ts @@ -118,8 +118,8 @@ export class PtyService extends Disposable implements IPtyService { this._throwIfNoPty(id).setTitle(title); } - async updateIcon(id: number, icon: string): Promise { - this._throwIfNoPty(id).setIcon(icon); + async updateIcon(id: number, icon: string, color?: string): Promise { + this._throwIfNoPty(id).setIcon(icon, color); } async detachFromProcess(id: number): Promise { @@ -256,7 +256,8 @@ export class PtyService extends Disposable implements IPtyService { workspaceName: persistentProcess.workspaceName, cwd, isOrphan, - icon: persistentProcess.icon + icon: persistentProcess.icon, + color: persistentProcess.color }; } @@ -309,13 +310,15 @@ export class PersistentTerminalProcess extends Disposable { get pid(): number { return this._pid; } get title(): string { return this._title || this._terminalProcess.currentTitle; } get icon(): string | undefined { return this._icon; } + get color(): string | undefined { return this._color; } setTitle(title: string): void { this._title = title; } - setIcon(icon: string): void { + setIcon(icon: string, color?: string): void { this._icon = icon; + this._color = color; } constructor( @@ -326,7 +329,8 @@ export class PersistentTerminalProcess extends Disposable { readonly shouldPersistTerminal: boolean, cols: number, rows: number, private readonly _logService: ILogService, - private _icon?: string + private _icon?: string, + private _color?: string ) { super(); this._recorder = new TerminalRecorder(cols, rows); diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index c61cff53778..475e9301a36 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -392,6 +392,25 @@ declare module 'vscode' { Warning = 2, } + /** + * A message regarding a completed search. + */ + export interface TextSearchCompleteMessage { + /** + * Markdown text of the message. + */ + text: string, + /** + * Whether the source of the message is trusted, command links are disabled for untrusted message sources. + * Messaged are untrusted by default. + */ + trusted?: boolean, + /** + * The message type, this affects how the message will be rendered. + */ + type: TextSearchCompleteMessageType, + } + /** * Information collected when text search is complete. */ @@ -411,8 +430,10 @@ declare module 'vscode' { * Messages with "Information" tyle support links in markdown syntax: * - Click to [run a command](command:workbench.action.OpenQuickPick) * - Click to [open a website](https://aka.ms) + * + * Commands may optionally return { triggerSearch: true } to signal to VS Code that the original search should run be agian. */ - message?: { text: string, type: TextSearchCompleteMessageType } | { text: string, type: TextSearchCompleteMessageType }[]; + message?: TextSearchCompleteMessage | TextSearchCompleteMessage[]; } /** @@ -1071,8 +1092,10 @@ declare module 'vscode' { */ readonly outputs: ReadonlyArray; - // todo@API maybe just executionSummary or lastExecutionSummary? - readonly latestExecutionSummary: NotebookCellExecutionSummary | undefined; + /** + * The most recent {@link NotebookCellExecutionSummary excution summary} for this cell. + */ + readonly executionSummary?: NotebookCellExecutionSummary; } /** @@ -1269,9 +1292,17 @@ declare module 'vscode' { // static textplain(value:string): NotebookCellOutputItem; // static errortrace(value:any): NotebookCellOutputItem; + /** + * Creates `application/x.notebook.error` + * + * @param err An error for which an output item is wanted + */ + static error(err: Error): NotebookCellOutputItem; + mime: string; //todo@API string or Unit8Array? + // value: string | Uint8Array | unknown; value: unknown; metadata?: { [key: string]: any }; @@ -1320,8 +1351,10 @@ declare module 'vscode' { */ metadata?: NotebookCellMetadata; - // todo@API just executionSummary or lastExecutionSummary - latestExecutionSummary?: NotebookCellExecutionSummary; + /** + * The execution summary of this cell data. + */ + executionSummary?: NotebookCellExecutionSummary; /** * Create new cell data. Minimal cell data specifies its kind, its source value, and the @@ -1332,9 +1365,9 @@ declare module 'vscode' { * @param languageId The language identifier of the source value. * @param outputs //TODO@API remove ctor? * @param metadata //TODO@API remove ctor? - * @param latestExecutionSummary //TODO@API remove ctor? + * @param executionSummary //TODO@API remove ctor? */ - constructor(kind: NotebookCellKind, value: string, languageId: string, outputs?: NotebookCellOutput[], metadata?: NotebookCellMetadata, latestExecutionSummary?: NotebookCellExecutionSummary); + constructor(kind: NotebookCellKind, value: string, languageId: string, outputs?: NotebookCellOutput[], metadata?: NotebookCellMetadata, executionSummary?: NotebookCellExecutionSummary); } /** diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index a452392f3bc..888929dfa12 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -145,7 +145,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostDocumentContentProviders = rpcProtocol.set(ExtHostContext.ExtHostDocumentContentProviders, new ExtHostDocumentContentProvider(rpcProtocol, extHostDocumentsAndEditors, extHostLogService)); const extHostDocumentSaveParticipant = rpcProtocol.set(ExtHostContext.ExtHostDocumentSaveParticipant, new ExtHostDocumentSaveParticipant(extHostLogService, extHostDocuments, rpcProtocol.getProxy(MainContext.MainThreadBulkEdits))); const extHostNotebook = rpcProtocol.set(ExtHostContext.ExtHostNotebook, new ExtHostNotebookController(rpcProtocol, extHostCommands, extHostDocumentsAndEditors, extHostDocuments, extHostLogService, extensionStoragePaths)); - const extHostNotebookKernels = rpcProtocol.set(ExtHostContext.ExtHostNotebookKernels, new ExtHostNotebookKernels(rpcProtocol, initData, extHostNotebook)); + const extHostNotebookKernels = rpcProtocol.set(ExtHostContext.ExtHostNotebookKernels, new ExtHostNotebookKernels(rpcProtocol, initData, extHostNotebook, extHostLogService)); const extHostEditors = rpcProtocol.set(ExtHostContext.ExtHostEditors, new ExtHostEditors(rpcProtocol, extHostDocumentsAndEditors)); const extHostTreeViews = rpcProtocol.set(ExtHostContext.ExtHostTreeViews, new ExtHostTreeViews(rpcProtocol.getProxy(MainContext.MainThreadTreeViews), extHostCommands, extHostLogService)); const extHostEditorInsets = rpcProtocol.set(ExtHostContext.ExtHostEditorInsets, new ExtHostEditorInsets(rpcProtocol.getProxy(MainContext.MainThreadEditorInsets), extHostEditors, { ...initData.environment, remote: initData.remote })); diff --git a/src/vs/workbench/api/common/extHostNotebookDocument.ts b/src/vs/workbench/api/common/extHostNotebookDocument.ts index 58ccef8c010..78d10165bb5 100644 --- a/src/vs/workbench/api/common/extHostNotebookDocument.ts +++ b/src/vs/workbench/api/common/extHostNotebookDocument.ts @@ -87,7 +87,7 @@ export class ExtHostCell { document: data.document, get outputs() { return that._outputs.slice(0); }, get metadata() { return that._metadata; }, - get latestExecutionSummary() { return that._previousResult; } + get executionSummary() { return that._previousResult; } }); } return this._apiCell; diff --git a/src/vs/workbench/api/common/extHostNotebookKernels.ts b/src/vs/workbench/api/common/extHostNotebookKernels.ts index 3f91dd9e8f9..91007850945 100644 --- a/src/vs/workbench/api/common/extHostNotebookKernels.ts +++ b/src/vs/workbench/api/common/extHostNotebookKernels.ts @@ -20,6 +20,7 @@ import { CellEditType, IImmediateCellEditOperation, NullablePartialNotebookCellM import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { NotebookCellExecutionState } from 'vs/workbench/api/common/extHostTypes'; import { asArray } from 'vs/base/common/arrays'; +import { ILogService } from 'vs/platform/log/common/log'; interface IKernelData { extensionId: ExtensionIdentifier, @@ -40,7 +41,8 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape { constructor( private readonly _mainContext: IMainContext, private readonly _initData: IExtHostInitDataService, - private readonly _extHostNotebook: ExtHostNotebookController + private readonly _extHostNotebook: ExtHostNotebookController, + @ILogService private readonly _logService: ILogService, ) { this._proxy = _mainContext.getProxy(MainContext.MainThreadNotebookKernels); } @@ -53,9 +55,12 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape { } } + const handle = this._handlePool++; const that = this; + this._logService.trace(`NotebookController[${handle}], CREATED by ${extension.identifier.value}, ${id}`); + const _defaultExecutHandler = () => console.warn(`NO execute handler from notebook controller '${data.id}' of extension: '${extension.identifier}'`); let isDisposed = false; @@ -164,12 +169,14 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape { throw new Error('notebook controller is DISPOSED'); } if (!associatedNotebooks.has(cell.notebook.uri)) { + that._logService.trace(`NotebookController[${handle}] NOT associated to notebook, associated to THESE notebooks:`, Array.from(associatedNotebooks.keys()).map(u => u.toString())); throw new Error(`notebook controller is NOT associated to notebook: ${cell.notebook.uri.toString()}`); } return that._createNotebookCellExecution(cell); }, dispose: () => { if (!isDisposed) { + this._logService.trace(`NotebookController[${handle}], DISPOSED`); isDisposed = true; this._kernelData.delete(handle); commandDisposables.dispose(); @@ -212,6 +219,7 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape { } else { obj.associatedNotebooks.delete(notebook.uri); } + this._logService.trace(`NotebookController[${handle}] ASSOCIATE notebook`, notebook.uri.toString(), value); // send event obj.onDidChangeSelection.fire({ selected: value, @@ -236,9 +244,11 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape { } try { + this._logService.trace(`NotebookController[${handle}] EXECUTE cells`, document.uri.toString(), cells.length); await obj.controller.executeHandler.call(obj.controller, cells, document.apiNotebook, obj.controller); } catch (err) { // + this._logService.error(`NotebookController[${handle}] execute cells FAILED`, err); console.error(err); } } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index a197afac238..505ad95ddbe 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -1494,7 +1494,7 @@ export namespace NotebookCellData { source: data.value, metadata: { ...data.metadata, - ...NotebookCellPreviousExecutionResult.from(data.latestExecutionSummary ?? {}) + ...NotebookCellPreviousExecutionResult.from(data.executionSummary ?? {}) }, outputs: data.outputs ? data.outputs.map(NotebookCellOutput.from) : [] }; diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 5f9140d0621..0cd309dbf10 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -3049,15 +3049,15 @@ export class NotebookCellData { languageId: string; outputs?: NotebookCellOutput[]; metadata?: NotebookCellMetadata; - latestExecutionSummary?: vscode.NotebookCellExecutionSummary; + executionSummary?: vscode.NotebookCellExecutionSummary; - constructor(kind: NotebookCellKind, value: string, languageId: string, outputs?: NotebookCellOutput[], metadata?: NotebookCellMetadata, latestExecutionSummary?: vscode.NotebookCellExecutionSummary) { + constructor(kind: NotebookCellKind, value: string, languageId: string, outputs?: NotebookCellOutput[], metadata?: NotebookCellMetadata, executionSummary?: vscode.NotebookCellExecutionSummary) { this.kind = kind; this.value = value; this.languageId = languageId; this.outputs = outputs ?? []; this.metadata = metadata; - this.latestExecutionSummary = latestExecutionSummary; + this.executionSummary = executionSummary; NotebookCellData.validate(this); } @@ -3087,6 +3087,13 @@ export class NotebookCellOutputItem { return typeof (obj).mime === 'string'; } + static error(err: Error): NotebookCellOutputItem { + return new NotebookCellOutputItem( + 'application/x.notebook.error', + JSON.stringify({ name: err.name, message: err.message, stack: err.stack }) + ); + } + constructor( public mime: string, public value: unknown, // JSON'able diff --git a/src/vs/workbench/browser/parts/banner/bannerPart.ts b/src/vs/workbench/browser/parts/banner/bannerPart.ts index 3790474a87f..93a87f9179b 100644 --- a/src/vs/workbench/browser/parts/banner/bannerPart.ts +++ b/src/vs/workbench/browser/parts/banner/bannerPart.ts @@ -162,7 +162,7 @@ export class BannerPart extends Part implements IBannerService { const actionContainer = append(this.element, $('div.message-actions-container')); for (const action of item.actions) { - const actionLink = this._register(this.instantiationService.createInstance(Link, action)); + const actionLink = this._register(this.instantiationService.createInstance(Link, action, {})); this._register(attachLinkStyler(actionLink, this.themeService, { textLinkForeground: BANNER_FOREGROUND })); actionContainer.appendChild(actionLink.el); } diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index f155c7aa228..34f61e1ec8a 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -487,7 +487,7 @@ function registerOpenEditorAPICommands(): void { group = editorGroupsService.getGroup(viewColumnToEditorGroup(editorGroupsService, columnArg)) ?? editorGroupsService.activeGroup; } - return editorService.openEditor({ resource: URI.revive(resource), options: { ...optionsArg, override: id } }, group); + return editorService.openEditor({ resource: URI.revive(resource), options: { ...optionsArg, pinned: true, override: id } }, group); }); } diff --git a/src/vs/workbench/browser/parts/editor/editorControl.ts b/src/vs/workbench/browser/parts/editor/editorControl.ts index c063c541e47..e6dcdc9c47a 100644 --- a/src/vs/workbench/browser/parts/editor/editorControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorControl.ts @@ -63,7 +63,7 @@ export class EditorControl extends Disposable { this._register(this.workspaceTrustService.onDidChangeTrust(() => this.onDidChangeWorkspaceTrust())); } - private async onDidChangeWorkspaceTrust(): Promise { + private onDidChangeWorkspaceTrust() { // If the active editor pane requires workspace trust // we need to re-open it anytime trust changes to @@ -72,7 +72,7 @@ export class EditorControl extends Disposable { // to handle errors properly. const editor = this._activeEditorPane?.input; const options = this._activeEditorPane?.options; - if (editor && await editor.requiresWorkspaceTrust()) { + if (editor?.requiresWorkspaceTrust()) { this.groupView.openEditor(editor, options); } } @@ -80,7 +80,7 @@ export class EditorControl extends Disposable { async openEditor(editor: EditorInput, options: EditorOptions | undefined, context: IEditorOpenContext = Object.create(null)): Promise { // Editor descriptor - const descriptor = await this.resolveEditorDescriptor(editor); + const descriptor = this.getEditorDescriptor(editor); // Editor pane const editorPane = this.doShowEditorPane(descriptor); @@ -90,17 +90,16 @@ export class EditorControl extends Disposable { return { editorPane, editorChanged }; } - private async resolveEditorDescriptor(editor: EditorInput): Promise { - const editorRequiresTrust = await editor.requiresWorkspaceTrust(); - const editorBlockedByTrust = editorRequiresTrust && !this.workspaceTrustService.isWorkpaceTrusted(); + private getEditorDescriptor(editor: EditorInput): IEditorDescriptor { + if (editor.requiresWorkspaceTrust() && !this.workspaceTrustService.isWorkpaceTrusted()) { + // Workspace trust: if an editor signals it needs workspace trust + // but the current workspace is untrusted, we fallback to a generic + // editor descriptor to indicate this an do NOT load the registered + // editor. + return WorkspaceTrustRequiredEditor.DESCRIPTOR; + } - // Workspace trust: if an editor signals it needs workspace trust - // but the current workspace is untrusted, we fallback to a generic - // editor descriptor to indicate this an do NOT load the registered - // editor. - const descriptor = editorBlockedByTrust ? WorkspaceTrustRequiredEditor.DESCRIPTOR : this.editorsRegistry.getEditor(editor); - - return assertIsDefined(descriptor); + return assertIsDefined(this.editorsRegistry.getEditor(editor)); } private doShowEditorPane(descriptor: IEditorDescriptor): EditorPane { diff --git a/src/vs/workbench/browser/parts/views/viewPane.ts b/src/vs/workbench/browser/parts/views/viewPane.ts index 0181ce2c89b..97586c50e69 100644 --- a/src/vs/workbench/browser/parts/views/viewPane.ts +++ b/src/vs/workbench/browser/parts/views/viewPane.ts @@ -579,7 +579,7 @@ export abstract class ViewPane extends Pane implements IView { if (typeof node === 'string') { append(p, document.createTextNode(node)); } else { - const link = this.instantiationService.createInstance(Link, node); + const link = this.instantiationService.createInstance(Link, node, {}); append(p, link.el); disposables.add(link); disposables.add(attachLinkStyler(link, this.themeService)); diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index d5bf2097591..e658791524a 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -63,6 +63,7 @@ import { ITimerService } from 'vs/workbench/services/timer/browser/timerService' import { WorkspaceTrustManagementService } from 'vs/workbench/services/workspaces/common/workspaceTrust'; import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; import { HTMLFileSystemProvider } from 'vs/platform/files/browser/htmlFileSystemProvider'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; class BrowserMain extends Disposable { @@ -106,16 +107,22 @@ class BrowserMain extends Disposable { const commandService = accessor.get(ICommandService); const lifecycleService = accessor.get(ILifecycleService); const timerService = accessor.get(ITimerService); + const openerService = accessor.get(IOpenerService); + const productService = accessor.get(IProductService); return { commands: { executeCommand: (command, ...args) => commandService.executeCommand(command, ...args) }, env: { + uriScheme: productService.urlProtocol, async retrievePerformanceMarks() { await timerService.whenReady(); return timerService.getPerformanceMarks(); + }, + async openUri(uri: URI): Promise { + return openerService.open(uri, {}); } }, shutdown: () => lifecycleService.shutdown() diff --git a/src/vs/workbench/browser/window.ts b/src/vs/workbench/browser/window.ts index e72a514fc20..d5d3991352d 100644 --- a/src/vs/workbench/browser/window.ts +++ b/src/vs/workbench/browser/window.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { setFullscreen } from 'vs/base/browser/browser'; -import { addDisposableListener, addDisposableThrottledListener, detectFullscreen, EventHelper, EventType, windowOpenNoOpener } from 'vs/base/browser/dom'; +import { addDisposableListener, addDisposableThrottledListener, detectFullscreen, EventHelper, EventType, windowOpenNoOpenerWithSuccess, windowOpenNoOpener } from 'vs/base/browser/dom'; import { domEvent } from 'vs/base/browser/event'; import { timeout } from 'vs/base/common/async'; import { Event } from 'vs/base/common/event'; @@ -144,7 +144,7 @@ export class BrowserWindow extends Disposable { this.openerService.setDefaultExternalOpener({ openExternal: async (href: string) => { if (matchesScheme(href, Schemas.http) || matchesScheme(href, Schemas.https)) { - const opened = windowOpenNoOpener(href); + const opened = windowOpenNoOpenerWithSuccess(href); if (!opened) { const showResult = await this.dialogService.show(Severity.Warning, localize('unableToOpenExternal', "The browser interrupted the opening of a new tab or window. Press 'Open' to open it anyway."), [localize('open', "Open"), localize('learnMore', "Learn More"), localize('cancel', "Cancel")], { cancelId: 2, detail: href }); diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index d331c4c1035..907640f18c4 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -435,7 +435,7 @@ export interface IEditorInput extends IDisposable { /** * Returns if the input requires workspace trust or not. */ - requiresWorkspaceTrust(): Promise; + requiresWorkspaceTrust(): boolean /** * Returns if this input is readonly or not. @@ -577,7 +577,7 @@ export abstract class EditorInput extends Disposable implements IEditorInput { return { typeId: this.typeId }; } - async requiresWorkspaceTrust(): Promise { + requiresWorkspaceTrust(): boolean { return false; } @@ -799,10 +799,8 @@ export class SideBySideEditorInput extends EditorInput { return this.description; } - override async requiresWorkspaceTrust(): Promise { - const requiresTrust = await Promise.all([this.primary.requiresWorkspaceTrust(), this.secondary.requiresWorkspaceTrust()]); - - return requiresTrust.some(value => value === true); + override requiresWorkspaceTrust(): boolean { + return this.primary.requiresWorkspaceTrust() || this.secondary.requiresWorkspaceTrust(); } override isReadonly(): boolean { diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 6fbd452f382..25794f976cf 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -877,8 +877,8 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi }); this.registerExtensionAction({ - id: 'workbench.extensions.action.listTrustRequiredExtensions', - title: { value: localize('showTrustRequiredExtensions', "Show Extensions Requiring Trust"), original: 'Show Extensions Requiring Trust' }, + id: 'workbench.extensions.action.listWorkspaceUnsupportedExtensions', + title: { value: localize('showWorkspaceUnsupportedExtensions', "Show Extensions Unsupported By Workspace"), original: 'Show Extensions Unsupported By Workspace' }, category: ExtensionsLocalizedLabel, menu: [{ id: MenuId.CommandPalette, @@ -889,9 +889,9 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi order: 6, }], menuTitles: { - [extensionsFilterSubMenu.id]: localize('trust required filter', "Trust Required") + [extensionsFilterSubMenu.id]: localize('workspace unsupported filter', "Workspace Unsupported") }, - run: () => runAction(this.instantiationService.createInstance(SearchExtensionsAction, '@trustRequired')) + run: () => runAction(this.instantiationService.createInstance(SearchExtensionsAction, '@workspaceUnsupported')) }); this.registerExtensionAction({ diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 9db5eca0667..3ecddfc19bb 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -22,7 +22,7 @@ import { InstallLocalExtensionsInRemoteAction, InstallRemoteExtensionsInLocalAct import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkbenchExtensionEnablementService, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; -import { ExtensionsListView, EnabledExtensionsView, DisabledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, BuiltInFeatureExtensionsView, BuiltInThemesExtensionsView, BuiltInProgrammingLanguageExtensionsView, ServerInstalledExtensionsView, DefaultRecommendedExtensionsView, TrustRequiredOnStartExtensionsView, TrustRequiredOnDemandExtensionsView } from 'vs/workbench/contrib/extensions/browser/extensionsViews'; +import { ExtensionsListView, EnabledExtensionsView, DisabledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, BuiltInFeatureExtensionsView, BuiltInThemesExtensionsView, BuiltInProgrammingLanguageExtensionsView, ServerInstalledExtensionsView, DefaultRecommendedExtensionsView, UntrustedWorkspaceUnsupportedExtensionsView, UntrustedWorkspacePartiallySupportedExtensionsView, VirtualWorkspaceUnsupportedExtensionsView } from 'vs/workbench/contrib/extensions/browser/extensionsViews'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import Severity from 'vs/base/common/severity'; @@ -70,8 +70,8 @@ const HasInstalledExtensionsContext = new RawContextKey('hasInstalledEx const HasInstalledWebExtensionsContext = new RawContextKey('hasInstalledWebExtensions', false); const BuiltInExtensionsContext = new RawContextKey('builtInExtensions', false); const SearchBuiltInExtensionsContext = new RawContextKey('searchBuiltInExtensions', false); -const TrustRequiredExtensionsContext = new RawContextKey('trustRequiredExtensions', false); -const SearchTrustRequiredExtensionsContext = new RawContextKey('searchTrustRequiredExtensions', false); +const UnsupportedWorkspaceExtensionsContext = new RawContextKey('unsupportedWorkspaceExtensions', false); +const SearchUnsupportedWorkspaceExtensionsContext = new RawContextKey('searchUnsupportedWorkspaceExtensions', false); const RecommendedExtensionsContext = new RawContextKey('recommendedExtensions', false); export class ExtensionsViewletViewsContribution implements IWorkbenchContribution { @@ -104,7 +104,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio viewDescriptors.push(...this.createBuiltinExtensionsViewDescriptors()); /* Trust Required extensions views */ - viewDescriptors.push(...this.createTrustRequiredExtensionsViewDescriptors()); + viewDescriptors.push(...this.createUnsupportedWorkspaceExtensionsViewDescriptors()); Registry.as(Extensions.ViewsRegistry).registerViews(viewDescriptors, this.container); } @@ -344,13 +344,13 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio }); /* - * View used for searching trustRequired extensions + * View used for searching workspace unsupported extensions */ viewDescriptors.push({ - id: 'workbench.views.extensions.searchTrustRequired', - name: localize('trustRequired', "Trust Required"), + id: 'workbench.views.extensions.searchWorkspaceUnsupported', + name: localize('workspaceUnsupported', "Workspace Unsupported"), ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]), - when: ContextKeyExpr.and(ContextKeyExpr.has('searchTrustRequiredExtensions')), + when: ContextKeyExpr.and(ContextKeyExpr.has('searchWorkspaceUnsupportedExtensions')), }); return viewDescriptors; @@ -405,21 +405,28 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio return viewDescriptors; } - private createTrustRequiredExtensionsViewDescriptors(): IViewDescriptor[] { + private createUnsupportedWorkspaceExtensionsViewDescriptors(): IViewDescriptor[] { const viewDescriptors: IViewDescriptor[] = []; viewDescriptors.push({ - id: 'workbench.views.extensions.trustRequiredOnStartExtensions', - name: localize('trustRequiredOnStartExtensions', "Trust Required To Enable"), - ctorDescriptor: new SyncDescriptor(TrustRequiredOnStartExtensionsView, [{}]), - when: ContextKeyExpr.has('trustRequiredExtensions'), + id: 'workbench.views.extensions.untrustedUnsupportedExtensions', + name: localize('untrustedUnsupportedExtensions', "Disabled in Restricted Mode"), + ctorDescriptor: new SyncDescriptor(UntrustedWorkspaceUnsupportedExtensionsView, [{}]), + when: ContextKeyExpr.has('unsupportedWorkspaceExtensions'), }); viewDescriptors.push({ - id: 'workbench.views.extensions.trustRequiredOnDemandExtensions', - name: localize('trustRequiredOnDemandExtensions', "Trust Required For Features"), - ctorDescriptor: new SyncDescriptor(TrustRequiredOnDemandExtensionsView, [{}]), - when: ContextKeyExpr.has('trustRequiredExtensions'), + id: 'workbench.views.extensions.untrustedPartiallySupportedExtensions', + name: localize('untrustedPartiallySupportedExtensions', "Limited in Restricted Mode"), + ctorDescriptor: new SyncDescriptor(UntrustedWorkspacePartiallySupportedExtensionsView, [{}]), + when: ContextKeyExpr.has('unsupportedWorkspaceExtensions'), + }); + + viewDescriptors.push({ + id: 'workbench.views.extensions.virtualUnsupportedExtensions', + name: localize('virtualUnsupportedExtensions', "Disabled in Virtual Workspaces"), + ctorDescriptor: new SyncDescriptor(VirtualWorkspaceUnsupportedExtensionsView, [{}]), + when: ContextKeyExpr.and(ContextKeyExpr.has('unsupportedWorkspaceExtensions'), ContextKeyExpr.has('virtualWorkspace')), }); return viewDescriptors; @@ -440,8 +447,8 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE private hasInstalledWebExtensionsContextKey: IContextKey; private builtInExtensionsContextKey: IContextKey; private searchBuiltInExtensionsContextKey: IContextKey; - private trustRequiredExtensionsContextKey: IContextKey; - private searchTrustRequiredExtensionsContextKey: IContextKey; + private workspaceUnsupportedExtensionsContextKey: IContextKey; + private searchWorkspaceUnsupportedExtensionsContextKey: IContextKey; private recommendedExtensionsContextKey: IContextKey; private searchDelayer: Delayer; @@ -478,8 +485,8 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE this.sortByContextKey = ExtensionsSortByContext.bindTo(contextKeyService); this.searchMarketplaceExtensionsContextKey = SearchMarketplaceExtensionsContext.bindTo(contextKeyService); this.searchInstalledExtensionsContextKey = SearchIntalledExtensionsContext.bindTo(contextKeyService); - this.trustRequiredExtensionsContextKey = TrustRequiredExtensionsContext.bindTo(contextKeyService); - this.searchTrustRequiredExtensionsContextKey = SearchTrustRequiredExtensionsContext.bindTo(contextKeyService); + this.workspaceUnsupportedExtensionsContextKey = UnsupportedWorkspaceExtensionsContext.bindTo(contextKeyService); + this.searchWorkspaceUnsupportedExtensionsContextKey = SearchUnsupportedWorkspaceExtensionsContext.bindTo(contextKeyService); this.searchOutdatedExtensionsContextKey = SearchOutdatedExtensionsContext.bindTo(contextKeyService); this.searchEnabledExtensionsContextKey = SearchEnabledExtensionsContext.bindTo(contextKeyService); this.searchDisabledExtensionsContextKey = SearchDisabledExtensionsContext.bindTo(contextKeyService); @@ -517,7 +524,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE const header = append(this.root, $('.header')); const placeholder = localize('searchExtensions', "Search Extensions in Marketplace"); - const searchValue = this.searchViewletState['query.value'] ? this.searchViewletState['query.value'] : (this.workspaceTrustService.isWorkpaceTrusted() ? '' : '@trustRequired'); + const searchValue = this.searchViewletState['query.value'] ? this.searchViewletState['query.value'] : (this.workspaceTrustService.isWorkpaceTrusted() ? '' : '@workspaceUnsupported'); this.searchBox = this._register(this.instantiationService.createInstance(SuggestEnabledInput, `${VIEWLET_ID}.searchbox`, header, { triggerCharacters: ['@'], @@ -671,8 +678,8 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE this.searchEnabledExtensionsContextKey.set(ExtensionsListView.isEnabledExtensionsQuery(value)); this.searchDisabledExtensionsContextKey.set(ExtensionsListView.isDisabledExtensionsQuery(value)); this.searchBuiltInExtensionsContextKey.set(ExtensionsListView.isSearchBuiltInExtensionsQuery(value)); - this.trustRequiredExtensionsContextKey.set(ExtensionsListView.isTrustRequiredExtensionsQuery(value)); - this.searchTrustRequiredExtensionsContextKey.set(ExtensionsListView.isSearchTrustRequiredExtensionsQuery(value)); + this.workspaceUnsupportedExtensionsContextKey.set(ExtensionsListView.isWorkspaceUnsupportedExtensionsQuery(value)); + this.searchWorkspaceUnsupportedExtensionsContextKey.set(ExtensionsListView.isSearchWorkspaceUnsupportedExtensionsQuery(value)); this.builtInExtensionsContextKey.set(ExtensionsListView.isBuiltInExtensionsQuery(value)); this.recommendedExtensionsContextKey.set(isRecommendedExtensionsQuery); this.searchMarketplaceExtensionsContextKey.set(!!value && !ExtensionsListView.isLocalExtensionsQuery(value) && !isRecommendedExtensionsQuery); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index 67e0af11729..e2e6efc6ced 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -49,6 +49,7 @@ import { IPreferencesService } from 'vs/workbench/services/preferences/common/pr import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; +import { getVirtualWorkspaceScheme } from 'vs/platform/remote/common/remoteHosts'; // Extensions that are automatically classified as Programming Language extensions, but should be Feature extensions const FORCE_FEATURE_EXTENSIONS = ['vscode.git', 'vscode.search-result']; @@ -121,6 +122,7 @@ export class ExtensionsListView extends ViewPane { @IExtensionManagementServerService protected readonly extensionManagementServerService: IExtensionManagementServerService, @IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService, @IWorkbenchExtensionManagementService protected readonly extensionManagementService: IWorkbenchExtensionManagementService, + @IWorkspaceContextService protected readonly workspaceService: IWorkspaceContextService, @IProductService protected readonly productService: IProductService, @IContextKeyService contextKeyService: IContextKeyService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService, @@ -394,8 +396,8 @@ export class ExtensionsListView extends ViewPane { extensions = this.filterEnabledExtensions(local, runningExtensions, query, options); } - else if (/@trustRequired/i.test(value)) { - extensions = this.filterTrustRequiredExtensions(local, query, options); + else if (/@workspaceUnsupported/i.test(value)) { + extensions = this.filterWorkspaceUnsupportedExtensions(local, query, options); } return { extensions, canIncludeInstalledExtensions }; @@ -548,32 +550,46 @@ export class ExtensionsListView extends ViewPane { return this.sortExtensions(result, options); } - private filterTrustRequiredExtensions(local: IExtension[], query: Query, options: IQueryOptions): IExtension[] { + private filterWorkspaceUnsupportedExtensions(local: IExtension[], query: Query, options: IQueryOptions): IExtension[] { let value = query.value; - const onStartOnly = /@trustRequired:onStart/i.test(value); - if (onStartOnly) { - value = value.replace(/@trustRequired:onStart/g, ''); + const untrustedPartiallySupportedOnly = /@workspaceUnsupported:untrustedPartial/i.test(value); + if (untrustedPartiallySupportedOnly) { + value = value.replace(/@workspaceUnsupported:untrustedPartial/g, ''); } - const onDemandOnly = /@trustRequired:onDemand/i.test(value); - if (onDemandOnly) { - value = value.replace(/@trustRequired:onDemand/g, ''); + const untrustedUnsupportedOnly = /@workspaceUnsupported:untrusted/i.test(value); + if (untrustedUnsupportedOnly) { + value = value.replace(/@workspaceUnsupported:untrusted/g, ''); + } + const virtualUnsupportedOnly = /@workspaceUnsupported:virtual/i.test(value); + if (virtualUnsupportedOnly) { + value = value.replace(/@workspaceUnsupported:virtual/g, ''); } - value = value.replace(/@trustRequired/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase(); + value = value.replace(/@workspaceUnsupported/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase(); - const result = local.filter(extension => extension.local && this.extensionManifestPropertiesService.getExtensionUntrustedWorkspaceSupportType(extension.local.manifest) !== true && (extension.name.toLowerCase().indexOf(value) > -1 || extension.displayName.toLowerCase().indexOf(value) > -1)); + const isVirtualWorkspace = getVirtualWorkspaceScheme(this.workspaceService.getWorkspace()) !== undefined; + const virtualUnsupportedExtensions = local.filter(extension => extension.local && !this.extensionManifestPropertiesService.canSupportVirtualWorkspace(extension.local.manifest) && (extension.name.toLowerCase().indexOf(value) > -1 || extension.displayName.toLowerCase().indexOf(value) > -1)); - if (onStartOnly) { - const onStartExtensions = result.filter(extension => extension.local && this.extensionManifestPropertiesService.getExtensionUntrustedWorkspaceSupportType(extension.local.manifest) === false); - return this.sortExtensions(onStartExtensions, options); + let trustRequiringExtensions = local.filter(extension => extension.local && this.extensionManifestPropertiesService.getExtensionUntrustedWorkspaceSupportType(extension.local.manifest) !== true && (extension.name.toLowerCase().indexOf(value) > -1 || extension.displayName.toLowerCase().indexOf(value) > -1)); + if (isVirtualWorkspace) { + trustRequiringExtensions = trustRequiringExtensions.filter(extension => extension.local && this.extensionManifestPropertiesService.canSupportVirtualWorkspace(extension.local.manifest)); } - if (onDemandOnly) { - const onDemandExtensions = result.filter(extension => extension.local && this.extensionManifestPropertiesService.getExtensionUntrustedWorkspaceSupportType(extension.local.manifest) === 'limited'); - return this.sortExtensions(onDemandExtensions, options); + if (untrustedUnsupportedOnly) { + const untrustedUnsupportedExtensions = trustRequiringExtensions.filter(extension => extension.local && this.extensionManifestPropertiesService.getExtensionUntrustedWorkspaceSupportType(extension.local.manifest) === false); + return this.sortExtensions(untrustedUnsupportedExtensions, options); } - return this.sortExtensions(result, options); + if (untrustedPartiallySupportedOnly) { + const untrustedPartiallySupportedExtensions = trustRequiringExtensions.filter(extension => extension.local && this.extensionManifestPropertiesService.getExtensionUntrustedWorkspaceSupportType(extension.local.manifest) === 'limited'); + return this.sortExtensions(untrustedPartiallySupportedExtensions, options); + } + + if (virtualUnsupportedOnly) { + return this.sortExtensions(virtualUnsupportedExtensions, options); + } + + return this.sortExtensions(trustRequiringExtensions, options); } @@ -964,9 +980,9 @@ export class ExtensionsListView extends ViewPane { || this.isBuiltInExtensionsQuery(query) || this.isSearchBuiltInExtensionsQuery(query) || this.isBuiltInGroupExtensionsQuery(query) - || this.isSearchTrustRequiredExtensionsQuery(query) - || this.isTrustRequiredExtensionsQuery(query) - || this.isTrustRequiredGroupExtensionsQuery(query); + || this.isSearchWorkspaceUnsupportedExtensionsQuery(query) + || this.isWorkspaceUnsupportedExtensionsQuery(query) + || this.isWorkspaceUnsupportedGroupExtensionsQuery(query); } static isSearchBuiltInExtensionsQuery(query: string): boolean { @@ -981,16 +997,16 @@ export class ExtensionsListView extends ViewPane { return /^\s*@builtin:.+$/i.test(query.trim()); } - static isSearchTrustRequiredExtensionsQuery(query: string): boolean { - return /@trustRequired\s.+/i.test(query); + static isSearchWorkspaceUnsupportedExtensionsQuery(query: string): boolean { + return /@workspaceUnsupported\s.+/i.test(query); } - static isTrustRequiredExtensionsQuery(query: string): boolean { - return /^\s*@trustRequired$/i.test(query.trim()); + static isWorkspaceUnsupportedExtensionsQuery(query: string): boolean { + return /^\s*@workspaceUnsupported$/i.test(query.trim()); } - static isTrustRequiredGroupExtensionsQuery(query: string): boolean { - return /^\s*@trustRequired:.+$/i.test(query.trim()); + static isWorkspaceUnsupportedGroupExtensionsQuery(query: string): boolean { + return /^\s*@workspaceUnsupported:.+$/i.test(query.trim()); } static isInstalledExtensionsQuery(query: string): boolean { @@ -1088,15 +1104,21 @@ export class BuiltInProgrammingLanguageExtensionsView extends ExtensionsListView } } -export class TrustRequiredOnStartExtensionsView extends ExtensionsListView { +export class UntrustedWorkspaceUnsupportedExtensionsView extends ExtensionsListView { override async show(query: string): Promise> { - return (query && query.trim() !== '@trustRequired') ? this.showEmptyModel() : super.show('@trustRequired:onStart'); + return (query && query.trim() !== '@workspaceUnsupported') ? this.showEmptyModel() : super.show('@workspaceUnsupported:untrusted'); } } -export class TrustRequiredOnDemandExtensionsView extends ExtensionsListView { +export class UntrustedWorkspacePartiallySupportedExtensionsView extends ExtensionsListView { override async show(query: string): Promise> { - return (query && query.trim() !== '@trustRequired') ? this.showEmptyModel() : super.show('@trustRequired:onDemand'); + return (query && query.trim() !== '@workspaceUnsupported') ? this.showEmptyModel() : super.show('@workspaceUnsupported:untrustedPartial'); + } +} + +export class VirtualWorkspaceUnsupportedExtensionsView extends ExtensionsListView { + override async show(query: string): Promise> { + return (query && query.trim() !== '@workspaceUnsupported') ? this.showEmptyModel() : super.show('@workspaceUnsupported:virtual'); } } diff --git a/src/vs/workbench/contrib/extensions/common/extensionQuery.ts b/src/vs/workbench/contrib/extensions/common/extensionQuery.ts index e1cac15cc90..fe3716f36d4 100644 --- a/src/vs/workbench/contrib/extensions/common/extensionQuery.ts +++ b/src/vs/workbench/contrib/extensions/common/extensionQuery.ts @@ -13,7 +13,7 @@ export class Query { } static suggestions(query: string): string[] { - const commands = ['installed', 'outdated', 'enabled', 'disabled', 'builtin', 'recommended', 'trustRequired', 'sort', 'category', 'tag', 'ext', 'id'] as const; + const commands = ['installed', 'outdated', 'enabled', 'disabled', 'builtin', 'recommended', 'workspaceUnsupported', 'sort', 'category', 'tag', 'ext', 'id'] as const; const subcommands = { 'sort': ['installs', 'rating', 'name'], 'category': EXTENSION_CATEGORIES.map(c => `"${c.toLowerCase()}"`), diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileEditorTracker.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditorTracker.ts index 20ed5ecfea8..51bc0067083 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileEditorTracker.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileEditorTracker.ts @@ -17,6 +17,7 @@ import { IFilesConfigurationService, AutoSaveMode } from 'vs/workbench/services/ import { FILE_EDITOR_INPUT_ID } from 'vs/workbench/contrib/files/common/files'; import { Schemas } from 'vs/base/common/network'; import { UntitledTextEditorInput } from 'vs/workbench/services/untitled/common/untitledTextEditorInput'; +import { IWorkingCopyEditorService } from 'vs/workbench/services/workingCopy/common/workingCopyEditorService'; export class TextFileEditorTracker extends Disposable implements IWorkbenchContribution { @@ -26,7 +27,8 @@ export class TextFileEditorTracker extends Disposable implements IWorkbenchContr @ILifecycleService private readonly lifecycleService: ILifecycleService, @IHostService private readonly hostService: IHostService, @ICodeEditorService private readonly codeEditorService: ICodeEditorService, - @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService + @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, + @IWorkingCopyEditorService private readonly workingCopyEditorService: IWorkingCopyEditorService ) { super(); @@ -57,19 +59,24 @@ export class TextFileEditorTracker extends Disposable implements IWorkbenchContr return false; // resource must be dirty } - const model = this.textFileService.files.get(resource); - if (model?.hasState(TextFileEditorModelState.PENDING_SAVE)) { + const fileModel = this.textFileService.files.get(resource); + if (fileModel?.hasState(TextFileEditorModelState.PENDING_SAVE)) { return false; // resource must not be pending to save } - if (this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY && !model?.hasState(TextFileEditorModelState.ERROR)) { + if (this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY && !fileModel?.hasState(TextFileEditorModelState.ERROR)) { // leave models auto saved after short delay unless // the save resulted in an error return false; } if (this.editorService.isOpened({ resource, typeId: resource.scheme === Schemas.untitled ? UntitledTextEditorInput.ID : FILE_EDITOR_INPUT_ID })) { - return false; // model must not be opened already as file + return false; // model must not be opened already as file (fast check via editor type) + } + + const model = fileModel ?? this.textFileService.untitled.get(resource); + if (model && this.workingCopyEditorService.findEditor(model)) { + return false; // model must not be opened already as file (slower check via working copy) } return true; diff --git a/src/vs/workbench/contrib/files/common/explorerModel.ts b/src/vs/workbench/contrib/files/common/explorerModel.ts index 4809b811ad6..82955982e10 100644 --- a/src/vs/workbench/contrib/files/common/explorerModel.ts +++ b/src/vs/workbench/contrib/files/common/explorerModel.ts @@ -30,7 +30,7 @@ export class ExplorerModel implements IDisposable { fileService: IFileService ) { const setRoots = () => this._roots = this.contextService.getWorkspace().folders - .map(folder => new ExplorerItem(folder.uri, fileService, undefined, true, false, folder.name)); + .map(folder => new ExplorerItem(folder.uri, fileService, undefined, true, false, false, folder.name)); setRoots(); this._listener = this.contextService.onDidChangeWorkspaceFolders(() => { @@ -89,6 +89,7 @@ export class ExplorerItem { private _parent: ExplorerItem | undefined, private _isDirectory?: boolean, private _isSymbolicLink?: boolean, + private _readonly?: boolean, private _name: string = basenameOrAuthority(resource), private _mtime?: number, private _unknown = false @@ -124,7 +125,7 @@ export class ExplorerItem { } get isReadonly(): boolean { - return this.fileService.hasCapability(this.resource, FileSystemProviderCapabilities.Readonly); + return this._readonly || this.fileService.hasCapability(this.resource, FileSystemProviderCapabilities.Readonly); } get mtime(): number | undefined { @@ -179,7 +180,7 @@ export class ExplorerItem { } static create(fileService: IFileService, raw: IFileStat, parent: ExplorerItem | undefined, resolveTo?: readonly URI[]): ExplorerItem { - const stat = new ExplorerItem(raw.resource, fileService, parent, raw.isDirectory, raw.isSymbolicLink, raw.name, raw.mtime, !raw.isFile && !raw.isDirectory); + const stat = new ExplorerItem(raw.resource, fileService, parent, raw.isDirectory, raw.isSymbolicLink, raw.readonly, raw.name, raw.mtime, !raw.isFile && !raw.isDirectory); // Recursively add children if present if (stat.isDirectory) { diff --git a/src/vs/workbench/contrib/files/test/browser/explorerModel.test.ts b/src/vs/workbench/contrib/files/test/browser/explorerModel.test.ts index d7b872b7628..877158e416f 100644 --- a/src/vs/workbench/contrib/files/test/browser/explorerModel.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/explorerModel.test.ts @@ -14,7 +14,7 @@ import { TestFileService } from 'vs/workbench/test/browser/workbenchTestServices const fileService = new TestFileService(); function createStat(this: any, path: string, name: string, isFolder: boolean, hasChildren: boolean, size: number, mtime: number): ExplorerItem { - return new ExplorerItem(toResource.call(this, path), fileService, undefined, isFolder, false, name, mtime); + return new ExplorerItem(toResource.call(this, path), fileService, undefined, isFolder, false, false, name, mtime); } suite('Files - View Model', function () { @@ -245,19 +245,19 @@ suite('Files - View Model', function () { }); test('Merge Local with Disk', function () { - const merge1 = new ExplorerItem(URI.file(join('C:\\', '/path/to')), fileService, undefined, true, false, 'to', Date.now()); - const merge2 = new ExplorerItem(URI.file(join('C:\\', '/path/to')), fileService, undefined, true, false, 'to', Date.now()); + const merge1 = new ExplorerItem(URI.file(join('C:\\', '/path/to')), fileService, undefined, true, false, false, 'to', Date.now()); + const merge2 = new ExplorerItem(URI.file(join('C:\\', '/path/to')), fileService, undefined, true, false, false, 'to', Date.now()); // Merge Properties ExplorerItem.mergeLocalWithDisk(merge2, merge1); assert.strictEqual(merge1.mtime, merge2.mtime); // Merge Child when isDirectoryResolved=false is a no-op - merge2.addChild(new ExplorerItem(URI.file(join('C:\\', '/path/to/foo.html')), fileService, undefined, true, false, 'foo.html', Date.now())); + merge2.addChild(new ExplorerItem(URI.file(join('C:\\', '/path/to/foo.html')), fileService, undefined, true, false, false, 'foo.html', Date.now())); ExplorerItem.mergeLocalWithDisk(merge2, merge1); // Merge Child with isDirectoryResolved=true - const child = new ExplorerItem(URI.file(join('C:\\', '/path/to/foo.html')), fileService, undefined, true, false, 'foo.html', Date.now()); + const child = new ExplorerItem(URI.file(join('C:\\', '/path/to/foo.html')), fileService, undefined, true, false, false, 'foo.html', Date.now()); merge2.removeChild(child); merge2.addChild(child); (merge2)._isDirectoryResolved = true; diff --git a/src/vs/workbench/contrib/files/test/browser/explorerView.test.ts b/src/vs/workbench/contrib/files/test/browser/explorerView.test.ts index 8af54cf4ea7..723981a6238 100644 --- a/src/vs/workbench/contrib/files/test/browser/explorerView.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/explorerView.test.ts @@ -19,7 +19,7 @@ const $ = dom.$; const fileService = new TestFileService(); function createStat(this: any, path: string, name: string, isFolder: boolean, hasChildren: boolean, size: number, mtime: number, isSymLink = false, isUnknown = false): ExplorerItem { - return new ExplorerItem(toResource.call(this, path), fileService, undefined, isFolder, isSymLink, name, mtime, isUnknown); + return new ExplorerItem(toResource.call(this, path), fileService, undefined, isFolder, isSymLink, false, name, mtime, isUnknown); } suite('Files - ExplorerView', () => { diff --git a/src/vs/workbench/contrib/notebook/browser/constants.ts b/src/vs/workbench/contrib/notebook/browser/constants.ts index 06627113262..5ed8e659f83 100644 --- a/src/vs/workbench/contrib/notebook/browser/constants.ts +++ b/src/vs/workbench/contrib/notebook/browser/constants.ts @@ -6,41 +6,3 @@ // Scrollable Element export const SCROLLABLE_ELEMENT_PADDING_TOP = 20; -// export const SCROLLABLE_ELEMENT_PADDING_TOP_WITH_TOOLBAR = 8; - -// Code cell layout: -// [CODE_CELL_LEFT_MARGIN][CELL_RUN_GUTTER][editorWidth][CELL_RIGHT_MARGIN] - -// Markdown cell layout: -// [CELL_MARGIN][content][CELL_RIGHT_MARGIN] - -// Markdown editor cell layout: -// [CODE_CELL_LEFT_MARGIN][content][CELL_RIGHT_MARGIN] - -// Cell sizing related -export const CELL_RIGHT_MARGIN = 16; -export const CELL_RUN_GUTTER = 28; -export const CODE_CELL_LEFT_MARGIN = 32; - -export const EDITOR_TOOLBAR_HEIGHT = 0; -export const BOTTOM_CELL_TOOLBAR_GAP = 18; -export const BOTTOM_CELL_TOOLBAR_HEIGHT = 22; -export const CELL_STATUSBAR_HEIGHT = 22; - -// Margin above editor -export const CELL_TOP_MARGIN = 6; -export const CELL_BOTTOM_MARGIN = 6; - -export const MARKDOWN_CELL_TOP_MARGIN = 8; -export const MARKDOWN_CELL_BOTTOM_MARGIN = 8; - -// Top and bottom padding inside the monaco editor in a cell, which are included in `cell.editorHeight` -// export const EDITOR_TOP_PADDING = 12; -export const EDITOR_BOTTOM_PADDING = 4; -export const EDITOR_BOTTOM_PADDING_WITHOUT_STATUSBAR = 12; - -export const CELL_OUTPUT_PADDING = 14; - -export const COLLAPSED_INDICATOR_HEIGHT = 24; - -export const MARKDOWN_PREVIEW_PADDING = 8; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts index 374c3759e3f..0ff295fff63 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/coreActions.ts @@ -35,6 +35,9 @@ import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/not import { Iterable } from 'vs/base/common/iterator'; import { flatten } from 'vs/base/common/arrays'; +// Kernel Command +export const SELECT_KERNEL_ID = 'notebook.selectKernel'; + // Notebook Commands const EXECUTE_NOTEBOOK_COMMAND_ID = 'notebook.execute'; const CANCEL_NOTEBOOK_COMMAND_ID = 'notebook.cancelExecution'; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts index 007a73f8dde..29364b4abc7 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts @@ -28,6 +28,7 @@ import { StartFindAction, StartFindReplaceAction } from 'vs/editor/contrib/find/ import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { NLS_MATCHES_LOCATION, NLS_NO_RESULTS } from 'vs/editor/contrib/find/findWidget'; import { FindModel } from 'vs/workbench/contrib/notebook/browser/contrib/find/findModel'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; const FIND_HIDE_TRANSITION = 'find-hide-transition'; const FIND_SHOW_TRANSITION = 'find-show-transition'; @@ -325,7 +326,7 @@ registerAction2(class extends Action2 { } }); -StartFindAction.addImplementation(100, (accessor: ServicesAccessor, args: any) => { +StartFindAction.addImplementation(100, (accessor: ServicesAccessor, codeEditor: ICodeEditor, args: any) => { const editorService = accessor.get(IEditorService); const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); @@ -338,7 +339,7 @@ StartFindAction.addImplementation(100, (accessor: ServicesAccessor, args: any) = return true; }); -StartFindReplaceAction.addImplementation(100, (accessor: ServicesAccessor, args: any) => { +StartFindReplaceAction.addImplementation(100, (accessor: ServicesAccessor, codeEditor: ICodeEditor, args: any) => { const editorService = accessor.get(IEditorService); const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/fold/folding.ts b/src/vs/workbench/contrib/notebook/browser/contrib/fold/folding.ts index f3fda701c19..73e57fe8921 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/fold/folding.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/fold/folding.ts @@ -38,7 +38,7 @@ export class FoldingController extends Disposable implements INotebookEditorCont return; } - this._localStore.add(this._notebookEditor.viewModel.eventDispatcher.onDidChangeCellState(e => { + this._localStore.add(this._notebookEditor.viewModel.viewContext.eventDispatcher.onDidChangeCellState(e => { if (e.source.editStateChanged && e.cell.cellKind === CellKind.Markup) { this._foldingModel?.recompute(); // this._updateEditorFoldingRanges(); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts b/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts index 6907f5bc30c..1a5a3e8224a 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/status/editorStatus.ts @@ -5,11 +5,11 @@ import * as nls from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; -import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; +import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IQuickInputButton, IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { NOTEBOOK_ACTIONS_CATEGORY } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; -import { getNotebookEditorFromEditorPane, INotebookEditor, NOTEBOOK_IS_ACTIVE_EDITOR } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { NOTEBOOK_ACTIONS_CATEGORY, SELECT_KERNEL_ID } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; +import { getNotebookEditorFromEditorPane, INotebookEditor, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_KERNEL_COUNT } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; @@ -26,16 +26,23 @@ import { ILogService } from 'vs/platform/log/common/log'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { HoverProviderRegistry } from 'vs/editor/common/modes'; import { Schemas } from 'vs/base/common/network'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; 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, icon: selectKernelIcon, f1: true, + menu: { + id: MenuId.EditorTitle, + when: ContextKeyExpr.and(NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_KERNEL_COUNT.notEqualsTo(0), ContextKeyExpr.equals('config.notebook.experimental.showKernelInEditorTitle', true),), + group: 'navigation', + order: -10 + }, description: { description: nls.localize('notebookActions.selectKernel.args', "Notebook Kernel Args"), args: [ @@ -57,7 +64,6 @@ registerAction2(class extends Action2 { } ] }, - }); } @@ -265,7 +271,7 @@ export class KernelStatus extends Disposable implements IWorkbenchContribution { text: `$(notebook-kernel-select) ${kernel.label}`, ariaLabel: kernel.label, tooltip: isSuggested ? nls.localize('tooltop', "{0} (suggestion)", tooltip) : tooltip, - command: 'notebook.selectKernel', + command: SELECT_KERNEL_ID, }, 'notebook.selectKernel', nls.localize('notebook.info', "Notebook Kernel Info"), @@ -282,7 +288,7 @@ export class KernelStatus extends Disposable implements IWorkbenchContribution { { text: nls.localize('kernel.select.label', "Select Kernel"), ariaLabel: nls.localize('kernel.select.label', "Select Kernel"), - command: 'notebook.selectKernel', + command: SELECT_KERNEL_ID, backgroundColor: { id: 'statusBarItem.prominentBackground' } }, 'notebook.selectKernel', diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts index 3b095c3039b..18f142fe53f 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts @@ -10,7 +10,6 @@ import { IDiffEditorOptions, IEditorOptions } from 'vs/editor/common/config/edit import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { DiffElementViewModelBase, getFormatedMetadataJSON, OUTPUT_EDITOR_HEIGHT_MAGIC, PropertyFoldingState, SideBySideDiffElementViewModel, SingleSideDiffElementViewModel } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; import { CellDiffSideBySideRenderTemplate, CellDiffSingleSideRenderTemplate, DiffSide, DIFF_CELL_MARGIN, INotebookTextDiffEditor, NOTEBOOK_DIFF_CELL_PROPERTY, NOTEBOOK_DIFF_CELL_PROPERTY_EXPANDED } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser'; -import { EDITOR_BOTTOM_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditorWidget'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -26,7 +25,6 @@ import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/men import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Delayer } from 'vs/base/common/async'; import { CodiconActionViewItem } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellActionView'; -import { getEditorTopPadding } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { collapsedIcon, expandedIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; import { OutputContainer } from 'vs/workbench/contrib/notebook/browser/diff/diffElementOutputs'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; @@ -980,7 +978,8 @@ export class DeletedElement extends SingleSideDiffElement { const originalCell = this.cell.original!; const lineCount = originalCell.textModel.textBuffer.getLineCount(); const lineHeight = this.notebookEditor.getLayoutInfo().fontInfo.lineHeight || 17; - const editorHeight = lineCount * lineHeight + getEditorTopPadding() + EDITOR_BOTTOM_PADDING; + const editorPadding = this.notebookEditor.notebookOptions.computeEditorPadding(); + const editorHeight = lineCount * lineHeight + editorPadding.top + editorPadding.bottom; this._editor = this.templateData.sourceEditor; this._editor.layout({ @@ -1131,7 +1130,8 @@ export class InsertElement extends SingleSideDiffElement { const modifiedCell = this.cell.modified!; const lineCount = modifiedCell.textModel.textBuffer.getLineCount(); const lineHeight = this.notebookEditor.getLayoutInfo().fontInfo.lineHeight || 17; - const editorHeight = lineCount * lineHeight + getEditorTopPadding() + EDITOR_BOTTOM_PADDING; + const editorPadding = this.notebookEditor.notebookOptions.computeEditorPadding(); + const editorHeight = lineCount * lineHeight + editorPadding.top + editorPadding.bottom; this._editor = this.templateData.sourceEditor; this._editor.layout( @@ -1469,7 +1469,9 @@ export class ModifiedElement extends AbstractElementRenderer { const modifiedCell = this.cell.modified!; const lineCount = modifiedCell.textModel.textBuffer.getLineCount(); const lineHeight = this.notebookEditor.getLayoutInfo().fontInfo.lineHeight || 17; - const editorHeight = this.cell.layoutInfo.editorHeight !== 0 ? this.cell.layoutInfo.editorHeight : lineCount * lineHeight + getEditorTopPadding() + EDITOR_BOTTOM_PADDING; + const editorPadding = this.notebookEditor.notebookOptions.computeEditorPadding(); + + const editorHeight = this.cell.layoutInfo.editorHeight !== 0 ? this.cell.layoutInfo.editorHeight : lineCount * lineHeight + editorPadding.top + editorPadding.bottom; this._editorContainer = this.templateData.editorContainer; this._editor = this.templateData.sourceEditor; diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.ts index 0ca043aacff..9685efbaec3 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.ts @@ -15,6 +15,7 @@ import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { NotebookOptions } from 'vs/workbench/contrib/notebook/common/notebookOptions'; export enum DiffSide { Original = 0, @@ -26,6 +27,7 @@ export interface IDiffCellInfo extends ICommonCellInfo { } export interface INotebookTextDiffEditor extends ICommonNotebookEditor { + notebookOptions: NotebookOptions; readonly textModel?: NotebookTextModel; onMouseUp: Event<{ readonly event: MouseEvent; readonly target: DiffElementViewModelBase; }>; onDidDynamicOutputRendered: Event<{ cell: IGenericCellViewModel, output: ICellOutputViewModel }>; diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts index cf5718517bd..9db388ec10e 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts @@ -38,9 +38,9 @@ import { generateUuid } from 'vs/base/common/uuid'; import { IMouseWheelEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { DiffNestedCellViewModel } from 'vs/workbench/contrib/notebook/browser/diff/diffNestedCellViewModel'; import { BackLayerWebView } from 'vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView'; -import { CELL_OUTPUT_PADDING, MARKDOWN_PREVIEW_PADDING } from 'vs/workbench/contrib/notebook/browser/constants'; import { NotebookDiffEditorEventDispatcher, NotebookDiffLayoutChangedEvent } from 'vs/workbench/contrib/notebook/browser/diff/eventDispatcher'; import { readFontInfo } from 'vs/editor/browser/config/configuration'; +import { NotebookOptions } from 'vs/workbench/contrib/notebook/common/notebookOptions'; const $ = DOM.$; @@ -75,6 +75,12 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD protected _onDidDynamicOutputRendered = new Emitter<{ cell: IGenericCellViewModel, output: ICellOutputViewModel }>(); onDidDynamicOutputRendered = this._onDidDynamicOutputRendered.event; + private _notebookOptions: NotebookOptions; + + get notebookOptions() { + return this._notebookOptions; + } + private readonly _localStore = this._register(new DisposableStore()); private _isDisposed: boolean = false; @@ -93,10 +99,11 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD @IStorageService storageService: IStorageService, ) { super(NotebookTextDiffEditor.ID, telemetryService, themeService, storageService); + this._notebookOptions = new NotebookOptions(this.configurationService); + this._register(this._notebookOptions); const editorOptions = this.configurationService.getValue('editor'); this._fontInfo = readFontInfo(BareFontInfo.createFromRawSettings(editorOptions, getZoomLevel(), getPixelRatio())); this._revealFirst = true; - this._outputRenderer = new OutputRenderer(this, this.instantiationService); } @@ -366,21 +373,12 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD })); } - private readonly webviewOptions = { - outputNodePadding: CELL_OUTPUT_PADDING, - outputNodeLeftPadding: 32, - previewNodePadding: MARKDOWN_PREVIEW_PADDING, - leftMargin: 0, - rightMargin: 0, - runGutter: 0 - }; - private async _createModifiedWebview(id: string, resource: URI): Promise { if (this._modifiedWebview) { this._modifiedWebview.dispose(); } - this._modifiedWebview = this.instantiationService.createInstance(BackLayerWebView, this, id, resource, this.webviewOptions) as BackLayerWebView; + this._modifiedWebview = this.instantiationService.createInstance(BackLayerWebView, this, id, resource, this._notebookOptions.computeDiffWebviewOptions()) as BackLayerWebView; // attach the webview container to the DOM tree first this._list.rowsContainer.insertAdjacentElement('afterbegin', this._modifiedWebview.element); await this._modifiedWebview.createWebview(); @@ -393,7 +391,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD this._originalWebview.dispose(); } - this._originalWebview = this.instantiationService.createInstance(BackLayerWebView, this, id, resource, this.webviewOptions) as BackLayerWebView; + this._originalWebview = this.instantiationService.createInstance(BackLayerWebView, this, id, resource, this._notebookOptions.computeDiffWebviewOptions()) as BackLayerWebView; // attach the webview container to the DOM tree first this._list.rowsContainer.insertAdjacentElement('afterbegin', this._originalWebview.element); await this._originalWebview.createWebview(); diff --git a/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts b/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts index ca86ffb5a77..de640c7926e 100644 --- a/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts +++ b/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts @@ -42,22 +42,6 @@ export interface INotebookRendererContribution { readonly [NotebookRendererContribution.optionalDependencies]: readonly string[]; } -enum NotebookMarkupRendererContribution { - id = 'id', - displayName = 'displayName', - entrypoint = 'entrypoint', - dependsOn = 'dependsOn', - mimeTypes = 'mimeTypes', -} - -export interface INotebookMarkupRendererContribution { - readonly [NotebookMarkupRendererContribution.id]?: string; - readonly [NotebookMarkupRendererContribution.displayName]: string; - readonly [NotebookMarkupRendererContribution.entrypoint]: string; - readonly [NotebookMarkupRendererContribution.dependsOn]: string | undefined; - readonly [NotebookMarkupRendererContribution.mimeTypes]: string[] | undefined; -} - const notebookProviderContribution: IJSONSchema = { description: nls.localize('contributes.notebook.provider', 'Contributes notebook document provider.'), type: 'array', @@ -164,48 +148,6 @@ const notebookRendererContribution: IJSONSchema = { } } }; -const notebookMarkupRendererContribution: IJSONSchema = { - description: nls.localize('contributes.notebook.markdownRenderer', 'Contributes a renderer for markdown cells in notebooks.'), - type: 'array', - defaultSnippets: [{ body: [{ id: '', displayName: '', entrypoint: '' }] }], - items: { - type: 'object', - required: [ - NotebookMarkupRendererContribution.id, - NotebookMarkupRendererContribution.displayName, - NotebookMarkupRendererContribution.entrypoint, - ], - properties: { - [NotebookMarkupRendererContribution.id]: { - type: 'string', - description: nls.localize('contributes.notebook.markdownRenderer.id', 'Unique identifier of the notebook markdown renderer.'), - }, - [NotebookMarkupRendererContribution.displayName]: { - type: 'string', - description: nls.localize('contributes.notebook.markdownRenderer.displayName', 'Human readable name of the notebook markdown renderer.'), - }, - [NotebookMarkupRendererContribution.entrypoint]: { - type: 'string', - description: nls.localize('contributes.notebook.markdownRenderer.entrypoint', 'File to load in the webview to render the extension.'), - }, - [NotebookMarkupRendererContribution.mimeTypes]: { - type: 'array', - items: { type: 'string' }, - description: nls.localize('contributes.notebook.markdownRenderer.mimeTypes', 'The mime type that the renderer handles.'), - }, - [NotebookMarkupRendererContribution.dependsOn]: { - type: 'string', - description: nls.localize('contributes.notebook.markdownRenderer.dependsOn', 'If specified, this renderer augments another renderer instead of providing full rendering.'), - }, - } - } -}; - -export const notebooksExtensionPoint2 = ExtensionsRegistry.registerExtensionPoint( - { - extensionPoint: 'notebookProvider', - jsonSchema: { deprecationMessage: 'Use \'notebooks\' instead' } - }); export const notebooksExtensionPoint = ExtensionsRegistry.registerExtensionPoint( { @@ -218,9 +160,3 @@ export const notebookRendererExtensionPoint = ExtensionsRegistry.registerExtensi extensionPoint: 'notebookOutputRenderer', jsonSchema: notebookRendererContribution }); - -export const notebookMarkupRendererExtensionPoint = ExtensionsRegistry.registerExtensionPoint( - { - extensionPoint: 'notebookMarkupRenderers', - jsonSchema: notebookMarkupRendererContribution - }); diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebookKernelActionViewItem.css b/src/vs/workbench/contrib/notebook/browser/media/notebookKernelActionViewItem.css new file mode 100644 index 00000000000..5f394da2d1f --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/media/notebookKernelActionViewItem.css @@ -0,0 +1,24 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-workbench .kernel-action-view-item { + border-radius: 5px; +} +.monaco-workbench .kernel-action-view-item:hover { + background-color: var(--code-toolbarHoverBackground); +} + +.monaco-workbench .kernel-action-view-item .action-label { + display: inline-flex; +} + +.monaco-workbench .kernel-action-view-item .kernel-label { + font-size: 11px; + padding: 3px 5px 3px 3px; + border-radius: 5px; + height: 16px; + display: inline-flex; + vertical-align: text-bottom; +} diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index ac887b9a8f0..fd69c02aa50 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -8,7 +8,7 @@ import { IListContextMenuEvent, IListEvent, IListMouseEvent } from 'vs/base/brow import { IListOptions, IListStyles } from 'vs/base/browser/ui/list/listWidget'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; -import { Emitter, Event } from 'vs/base/common/event'; +import { Event } from 'vs/base/common/event'; import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { ScrollEvent } from 'vs/base/common/scrollable'; import { URI } from 'vs/base/common/uri'; @@ -31,6 +31,7 @@ import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; import { IConstructorSignature1 } from 'vs/platform/instantiation/common/instantiation'; import { CellEditorStatusBar } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets'; import { INotebookWebviewMessage } from 'vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView'; +import { NotebookOptions } from 'vs/workbench/contrib/notebook/common/notebookOptions'; export const NOTEBOOK_EDITOR_ID = 'workbench.editor.notebook'; export const NOTEBOOK_DIFF_EDITOR_ID = 'workbench.editor.notebookTextDiffEditor'; @@ -382,6 +383,7 @@ export interface INotebookEditor extends ICommonNotebookEditor { readonly onDidScroll: Event; readonly onDidChangeActiveCell: Event; + readonly notebookOptions: NotebookOptions; isDisposed: boolean; dispose(): void; @@ -908,20 +910,6 @@ export function getNotebookEditorFromEditorPane(editorPane?: IEditorPane): INote return editorPane?.getId() === NOTEBOOK_EDITOR_ID ? editorPane.getControl() as INotebookEditor | undefined : undefined; } -let EDITOR_TOP_PADDING = 12; -const editorTopPaddingChangeEmitter = new Emitter(); - -export const EditorTopPaddingChangeEvent = editorTopPaddingChangeEmitter.event; - -export function updateEditorTopPadding(top: number) { - EDITOR_TOP_PADDING = top; - editorTopPaddingChangeEmitter.fire(); -} - -export function getEditorTopPadding() { - return EDITOR_TOP_PADDING; -} - /** * ranges: model selections * this will convert model selections to view indexes first, and then include the hidden ranges in the list view diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index 940296f98c5..218a8d1bec8 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -29,6 +29,10 @@ import { NotebookEditorOptions, NOTEBOOK_EDITOR_ID } from 'vs/workbench/contrib/ import { IBorrowValue, INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService'; import { clearMarks, getAndClearMarks, mark } from 'vs/workbench/contrib/notebook/common/notebookPerformance'; import { IFileService } from 'vs/platform/files/common/files'; +import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IAction } from 'vs/base/common/actions'; +import { SELECT_KERNEL_ID } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; +import { NotebooKernelActionViewItem } from 'vs/workbench/contrib/notebook/browser/notebookKernelActionViewItem'; const NOTEBOOK_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'NotebookEditorViewState'; @@ -103,6 +107,14 @@ export class NotebookEditor extends EditorPane { return this._rootElement; } + override getActionViewItem(action: IAction): IActionViewItem | undefined { + if (action.id === SELECT_KERNEL_ID) { + // this is being disposed by the consumer + return this.instantiationService.createInstance(NotebooKernelActionViewItem, action, this); + } + return undefined; + } + override getControl(): NotebookEditorWidget | undefined { return this._widget.value; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorKernelManager.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorKernelManager.ts index c4417a2a3c8..033e28d165f 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorKernelManager.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorKernelManager.ts @@ -10,6 +10,7 @@ import { CellKind, INotebookKernel, INotebookTextModel, NotebookCellExecutionSta import { ICommandService } from 'vs/platform/commands/common/commands'; import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust'; +import { SELECT_KERNEL_ID } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; export class NotebookEditorKernelManager extends Disposable { @@ -36,7 +37,7 @@ export class NotebookEditorKernelManager extends Disposable { let kernel = this.getSelectedOrSuggestedKernel(notebook); if (!kernel) { - await this._commandService.executeCommand('notebook.selectKernel'); + await this._commandService.executeCommand(SELECT_KERNEL_ID); kernel = this.getSelectedOrSuggestedKernel(notebook); } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index a5495997673..4e844150b73 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -42,7 +42,6 @@ import { IEditorMemento } from 'vs/workbench/common/editor'; import { Memento, MementoObject } from 'vs/workbench/common/memento'; import { PANEL_BORDER } from 'vs/workbench/common/theme'; import { debugIconStartForeground } from 'vs/workbench/contrib/debug/browser/debugColors'; -import { BOTTOM_CELL_TOOLBAR_GAP, BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_BOTTOM_MARGIN, CELL_OUTPUT_PADDING, CELL_RIGHT_MARGIN, CELL_RUN_GUTTER, CELL_TOP_MARGIN, CODE_CELL_LEFT_MARGIN, COLLAPSED_INDICATOR_HEIGHT, MARKDOWN_CELL_BOTTOM_MARGIN, MARKDOWN_CELL_TOP_MARGIN, MARKDOWN_PREVIEW_PADDING, SCROLLABLE_ELEMENT_PADDING_TOP } from 'vs/workbench/contrib/notebook/browser/constants'; import { CellEditState, CellFocusMode, IActiveNotebookEditor, ICellOutputViewModel, ICellViewModel, ICommonCellInfo, IDisplayOutputLayoutUpdateRequest, IFocusNotebookCellOptions, IGenericCellViewModel, IInsetRenderOutput, INotebookCellList, INotebookCellOutputLayoutInfo, INotebookDeltaDecoration, INotebookEditor, INotebookEditorContribution, INotebookEditorContributionDescription, INotebookEditorCreationOptions, INotebookEditorMouseEvent, NotebookEditorOptions, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_ID, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookDecorationCSSRules, NotebookRefCountedStyleSheet } from 'vs/workbench/contrib/notebook/browser/notebookEditorDecorations'; import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; @@ -59,7 +58,7 @@ import { NotebookEventDispatcher, NotebookLayoutChangedEvent } from 'vs/workbenc import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel'; import { CellViewModel, IModelDecorationsChangeAccessor, INotebookEditorViewState, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { CellKind, CellToolbarLocKey, CellToolbarVisibility, ExperimentalUseMarkdownRenderer, SelectionStateType, ShowCellStatusBarKey } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, ExperimentalUseMarkdownRenderer, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { editorGutterModifiedBackground } from 'vs/workbench/contrib/scm/browser/dirtydiffDecorator'; import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; @@ -75,6 +74,9 @@ import { mark } from 'vs/workbench/contrib/notebook/common/notebookPerformance'; import { readFontInfo } from 'vs/editor/browser/config/configuration'; import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { NotebookEditorContextKeys } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidgetContextKeys'; +import { NotebookOptions } from 'vs/workbench/contrib/notebook/common/notebookOptions'; +import { SCROLLABLE_ELEMENT_PADDING_TOP } from 'vs/workbench/contrib/notebook/browser/constants'; +import { ViewContext } from 'vs/workbench/contrib/notebook/browser/viewModel/viewContext'; const $ = DOM.$; @@ -203,6 +205,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor private _overlayContainer!: HTMLElement; private _notebookTopToolbarContainer!: HTMLElement; private _body!: HTMLElement; + private _styleElement!: HTMLStyleElement; private _overflowContainer!: HTMLElement; private _webview: BackLayerWebView | null = null; private _webviewResolvePromise: Promise | null> | null = null; @@ -213,7 +216,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor private _dndController: CellDragAndDropController | null = null; private _listTopCellToolbar: ListTopCellToolbar | null = null; private _renderedEditors: Map = new Map(); - private _eventDispatcher: NotebookEventDispatcher | undefined; + private _viewContext: ViewContext | undefined; private _notebookViewModel: NotebookViewModel | undefined; private _localStore: DisposableStore = this._register(new DisposableStore()); private _localCellStateListeners: DisposableStore[] = []; @@ -306,6 +309,11 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor public readonly scopedContextKeyService: IContextKeyService; private readonly instantiationService: IInstantiationService; + private readonly _notebookOptions: NotebookOptions; + + get notebookOptions() { + return this._notebookOptions; + } constructor( readonly creationOptions: INotebookEditorCreationOptions, @@ -330,6 +338,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this.isEmbedded = creationOptions.isEmbedded || false; this.useRenderer = !isWeb && !!this.configurationService.getValue(ExperimentalUseMarkdownRenderer) && !accessibilityService.isScreenReaderOptimized(); + this._notebookOptions = new NotebookOptions(this.configurationService); + this._register(this._notebookOptions); this._overlayContainer = document.createElement('div'); this.scopedContextKeyService = contextKeyService.createScoped(this._overlayContainer); @@ -356,8 +366,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this.layout(this._dimension); } } + })); - if (e.affectsConfiguration(CellToolbarLocKey) || e.affectsConfiguration(ShowCellStatusBarKey) || e.affectsConfiguration(CellToolbarVisibility)) { + this._register(this._notebookOptions.onDidChangeOptions(e => { + if (e.cellStatusBarVisibility || e.cellToolbarLocation || e.cellToolbarInteraction) { this._updateForNotebookConfiguration(); } })); @@ -488,43 +500,16 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return; } - const cellToolbarLocation = this.configurationService.getValue(CellToolbarLocKey); this._overlayContainer.classList.remove('cell-title-toolbar-left'); this._overlayContainer.classList.remove('cell-title-toolbar-right'); this._overlayContainer.classList.remove('cell-title-toolbar-hidden'); + const cellToolbarLocation = this._notebookOptions.computeCellToolbarLocation(this.viewModel?.viewType); + this._overlayContainer.classList.add(`cell-title-toolbar-${cellToolbarLocation}`); - if (typeof cellToolbarLocation === 'string') { - if (cellToolbarLocation === 'left' || cellToolbarLocation === 'right' || cellToolbarLocation === 'hidden') { - this._overlayContainer.classList.add(`cell-title-toolbar-${cellToolbarLocation}`); - } - } else { - if (this.viewModel) { - const notebookSpecificSetting = cellToolbarLocation[this.viewModel.viewType] ?? cellToolbarLocation['default']; - let cellToolbarLocationForCurrentView = 'right'; - - switch (notebookSpecificSetting) { - case 'left': - cellToolbarLocationForCurrentView = 'left'; - break; - case 'right': - cellToolbarLocationForCurrentView = 'right'; - case 'hidden': - cellToolbarLocationForCurrentView = 'hidden'; - default: - cellToolbarLocationForCurrentView = 'right'; - break; - } - - this._overlayContainer.classList.add(`cell-title-toolbar-${cellToolbarLocationForCurrentView}`); - } else { - this._overlayContainer.classList.add(`cell-title-toolbar-right`); - } - } - - const showCellStatusBar = this.configurationService.getValue(ShowCellStatusBarKey); + const showCellStatusBar = this._notebookOptions.getLayoutConfiguration().showCellStatusBar; this._overlayContainer.classList.toggle('cell-statusbar-hidden', !showCellStatusBar); - const cellToolbarInteraction = this.configurationService.getValue(CellToolbarVisibility); + const cellToolbarInteraction = this._notebookOptions.getLayoutConfiguration().cellToolbarInteraction; let cellToolbarInteractionState = 'hover'; this._overlayContainer.classList.remove('cell-toolbar-hover'); this._overlayContainer.classList.remove('cell-toolbar-click'); @@ -547,15 +532,74 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this._notebookTopToolbarContainer.style.display = 'none'; DOM.append(parent, this._notebookTopToolbarContainer); this._body = document.createElement('div'); - this._body.classList.add('cell-list-container'); - this._createCellList(); DOM.append(parent, this._body); + this._body.classList.add('cell-list-container'); + this._createLayoutStyles(); + this._createCellList(); this._overflowContainer = document.createElement('div'); this._overflowContainer.classList.add('notebook-overflow-widget-container', 'monaco-editor'); DOM.append(parent, this._overflowContainer); } + private _createLayoutStyles(): void { + this._styleElement = DOM.createStyleSheet(this._body); + const { + cellRightMargin, + cellTopMargin, + cellRunGutter, + cellBottomMargin, + codeCellLeftMargin, + markdownCellBottomMargin, + markdownCellTopMargin, + bottomCellToolbarGap, + bottomCellToolbarHeight, + collapsedIndicatorHeight + } = this._notebookOptions.getLayoutConfiguration(); + + const styleSheets: string[] = []; + styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .markdown-cell-row div.cell.code { margin-left: ${codeCellLeftMargin}px; }`); + styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .code-cell-row div.cell.code { margin-left: ${codeCellLeftMargin + cellRunGutter}px; }`); + styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row div.cell { margin-right: ${cellRightMargin}px; }`); + styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row > .cell-inner-container { padding-top: ${cellTopMargin}px; }`); + styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .markdown-cell-row > .cell-inner-container { padding-bottom: ${markdownCellBottomMargin}px; padding-top: ${markdownCellTopMargin}px; }`); + styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .markdown-cell-row > .cell-inner-container.webview-backed-markdown-cell { padding: 0; }`); + styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .markdown-cell-row > .webview-backed-markdown-cell.markdown-cell-edit-mode .cell.code { padding-bottom: ${markdownCellBottomMargin}px; padding-top: ${markdownCellTopMargin}px; }`); + styleSheets.push(`.notebookOverlay .output { margin: 0px ${cellRightMargin}px 0px ${codeCellLeftMargin + cellRunGutter}px; }`); + styleSheets.push(`.notebookOverlay .output { width: calc(100% - ${codeCellLeftMargin + cellRunGutter + cellRightMargin}px); }`); + + styleSheets.push(`.notebookOverlay .output-show-more-container { margin: 0px ${cellRightMargin}px 0px ${codeCellLeftMargin + cellRunGutter}px; }`); + styleSheets.push(`.notebookOverlay .output-show-more-container { width: calc(100% - ${codeCellLeftMargin + cellRunGutter + cellRightMargin}px); }`); + + styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row div.cell.markdown { padding-left: ${cellRunGutter}px; }`); + styleSheets.push(`.notebookOverlay .cell .run-button-container { width: 20px; left: ${codeCellLeftMargin + Math.floor(cellRunGutter - 20) / 2}px }`); + styleSheets.push(`.notebookOverlay .monaco-list .monaco-list-row :not(.webview-backed-markdown-cell) .cell-focus-indicator-top { height: ${cellTopMargin}px; }`); + styleSheets.push(`.notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-side { bottom: ${bottomCellToolbarGap}px; }`); + styleSheets.push(`.notebookOverlay .monaco-list .monaco-list-row.code-cell-row .cell-focus-indicator-left, + .notebookOverlay .monaco-list .monaco-list-row.code-cell-row .cell-drag-handle { width: ${codeCellLeftMargin + cellRunGutter}px; }`); + styleSheets.push(`.notebookOverlay .monaco-list .monaco-list-row.markdown-cell-row .cell-focus-indicator-left { width: ${codeCellLeftMargin}px; }`); + styleSheets.push(`.notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator.cell-focus-indicator-right { width: ${cellRightMargin}px; }`); + styleSheets.push(`.notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-bottom { height: ${cellBottomMargin}px; }`); + styleSheets.push(`.notebookOverlay .monaco-list .monaco-list-row .cell-shadow-container-bottom { top: ${cellBottomMargin}px; }`); + + styleSheets.push(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-collapsed-part { margin-left: ${codeCellLeftMargin + cellRunGutter}px; height: ${collapsedIndicatorHeight}px; }`); + styleSheets.push(`.notebookOverlay .cell-list-top-cell-toolbar-container { top: -${SCROLLABLE_ELEMENT_PADDING_TOP}px }`); + + styleSheets.push(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container { height: ${bottomCellToolbarHeight}px }`); + styleSheets.push(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .cell-list-top-cell-toolbar-container { height: ${bottomCellToolbarHeight}px }`); + + // left and right border margins + styleSheets.push(` + .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.code-cell-row.focused .cell-focus-indicator-left:before, + .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.code-cell-row.focused .cell-focus-indicator-right:before, + .monaco-workbench .notebookOverlay .monaco-list.selection-multiple .monaco-list-row.code-cell-row.selected .cell-focus-indicator-left:before, + .monaco-workbench .notebookOverlay .monaco-list.selection-multiple .monaco-list-row.code-cell-row.selected .cell-focus-indicator-right:before { + top: -${cellTopMargin}px; height: calc(100% + ${cellTopMargin + cellBottomMargin}px) + }`); + + this._styleElement.textContent = styleSheets.join('\n'); + } + private _createCellList(): void { this._body.classList.add('cell-list-container'); @@ -1076,14 +1120,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } private async _createWebview(id: string, resource: URI): Promise { - this._webview = this.instantiationService.createInstance(BackLayerWebView, this, id, resource, { - outputNodePadding: CELL_OUTPUT_PADDING, - outputNodeLeftPadding: CELL_OUTPUT_PADDING, - previewNodePadding: MARKDOWN_PREVIEW_PADDING, - leftMargin: CODE_CELL_LEFT_MARGIN, - rightMargin: CELL_RIGHT_MARGIN, - runGutter: CELL_RUN_GUTTER, - }); + this._webview = this.instantiationService.createInstance(BackLayerWebView, this, id, resource, this._notebookOptions.computeWebviewOptions()); this._webview.element.style.width = '100%'; // attach the webview container to the DOM tree first @@ -1093,9 +1130,9 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor private async _attachModel(textModel: NotebookTextModel, viewState: INotebookEditorViewState | undefined) { await this._createWebview(this.getId(), textModel.uri); - this._eventDispatcher = new NotebookEventDispatcher(); - this.viewModel = this.instantiationService.createInstance(NotebookViewModel, textModel.viewType, textModel, this._eventDispatcher, this.getLayoutInfo()); - this._eventDispatcher.emit([new NotebookLayoutChangedEvent({ width: true, fontInfo: true }, this.getLayoutInfo())]); + this._viewContext = new ViewContext(this._notebookOptions, new NotebookEventDispatcher()); + this.viewModel = this.instantiationService.createInstance(NotebookViewModel, textModel.viewType, textModel, this._viewContext, this.getLayoutInfo()); + this._viewContext.eventDispatcher.emit([new NotebookLayoutChangedEvent({ width: true, fontInfo: true }, this.getLayoutInfo())]); this._updateForOptions(); this._updateForNotebookConfiguration(); @@ -1449,7 +1486,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this._webviewTransparentCover.style.width = `${dimension.width}px`; } - this._eventDispatcher?.emit([new NotebookLayoutChangedEvent({ width: true, fontInfo: true }, this.getLayoutInfo())]); + this._viewContext?.eventDispatcher.emit([new NotebookLayoutChangedEvent({ width: true, fontInfo: true }, this.getLayoutInfo())]); } //#endregion @@ -2385,9 +2422,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor updateMarkdownCellHeight(cellId: string, height: number, isInit: boolean) { const cell = this.getCellById(cellId); + const layoutConfiguration = this._notebookOptions.getLayoutConfiguration(); if (cell && cell instanceof MarkdownCellViewModel) { - if (height + BOTTOM_CELL_TOOLBAR_GAP !== cell.layoutInfo.totalHeight) { - this._debug('updateMarkdownCellHeight', cell.handle, height + BOTTOM_CELL_TOOLBAR_GAP, isInit); + if (height + layoutConfiguration.bottomCellToolbarGap !== cell.layoutInfo.totalHeight) { + this._debug('updateMarkdownCellHeight', cell.handle, height + layoutConfiguration.bottomCellToolbarGap, isInit); cell.renderedMarkdownHeight = height; } } @@ -2461,7 +2499,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor this._webviewTransparentCover = null; this._dndController = null; this._listTopCellToolbar = null; - this._eventDispatcher = undefined; + this._viewContext = undefined; this._notebookViewModel = undefined; this._cellContextKeyManager = null; this._renderedEditors.clear(); @@ -2653,17 +2691,20 @@ registerThemingParticipant((theme, collector) => { collector.addRule(`.notebookOverlay .output-show-more-container { background-color: ${containerBackground}; }`); } + const notebookBackground = theme.getColor(editorBackground); + if (notebookBackground) { + collector.addRule(`.notebookOverlay .cell-drag-image .cell-editor-container > div { background: ${notebookBackground} !important; }`); + collector.addRule(`.notebookOverlay .monaco-list-row .cell-title-toolbar { background-color: ${notebookBackground}; }`); + collector.addRule(`.notebookOverlay .monaco-list-row.cell-drag-image { background-color: ${notebookBackground}; }`); + collector.addRule(`.notebookOverlay .cell-bottom-toolbar-container .action-item { background-color: ${notebookBackground} }`); + collector.addRule(`.notebookOverlay .cell-list-top-cell-toolbar-container .action-item { background-color: ${notebookBackground} }`); + } + const editorBackgroundColor = theme.getColor(cellEditorBackground) ?? theme.getColor(editorBackground); if (editorBackgroundColor) { collector.addRule(`.notebookOverlay .cell .monaco-editor-background, - .notebookOverlay .cell .margin-view-overlays, - .notebookOverlay .cell .cell-statusbar-container { background: ${editorBackgroundColor}; }`); - collector.addRule(`.notebookOverlay .cell-drag-image .cell-editor-container > div { background: ${editorBackgroundColor} !important; }`); - - collector.addRule(`.notebookOverlay .monaco-list-row .cell-title-toolbar { background-color: ${editorBackgroundColor}; }`); - collector.addRule(`.notebookOverlay .monaco-list-row.cell-drag-image { background-color: ${editorBackgroundColor}; }`); - collector.addRule(`.notebookOverlay .cell-bottom-toolbar-container .action-item { background-color: ${editorBackgroundColor} }`); - collector.addRule(`.notebookOverlay .cell-list-top-cell-toolbar-container .action-item { background-color: ${editorBackgroundColor} }`); + .notebookOverlay .cell .margin-view-overlays, + .notebookOverlay .cell .cell-statusbar-container { background: ${editorBackgroundColor}; }`); } const cellToolbarSeperator = theme.getColor(CELL_TOOLBAR_SEPERATOR); @@ -2822,43 +2863,5 @@ registerThemingParticipant((theme, collector) => { }`); } - // Cell Margin - collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .markdown-cell-row div.cell.code { margin-left: ${CODE_CELL_LEFT_MARGIN}px; }`); - collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .code-cell-row div.cell.code { margin-left: ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER}px; }`); - collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row div.cell { margin-right: ${CELL_RIGHT_MARGIN}px; }`); - collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row > .cell-inner-container { padding-top: ${CELL_TOP_MARGIN}px; }`); - collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .markdown-cell-row > .cell-inner-container { padding-bottom: ${MARKDOWN_CELL_BOTTOM_MARGIN}px; padding-top: ${MARKDOWN_CELL_TOP_MARGIN}px; }`); - collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .markdown-cell-row > .cell-inner-container.webview-backed-markdown-cell { padding: 0; }`); - collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .markdown-cell-row > .webview-backed-markdown-cell.markdown-cell-edit-mode .cell.code { padding-bottom: ${MARKDOWN_CELL_BOTTOM_MARGIN}px; padding-top: ${MARKDOWN_CELL_TOP_MARGIN}px; }`); - collector.addRule(`.notebookOverlay .output { margin: 0px ${CELL_RIGHT_MARGIN}px 0px ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER}px; }`); - collector.addRule(`.notebookOverlay .output { width: calc(100% - ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER + CELL_RIGHT_MARGIN}px); }`); - collector.addRule(`.notebookOverlay .output-show-more-container { margin: 0px ${CELL_RIGHT_MARGIN}px 0px ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER}px; }`); - collector.addRule(`.notebookOverlay .output-show-more-container { width: calc(100% - ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER + CELL_RIGHT_MARGIN}px); }`); - - collector.addRule(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row div.cell.markdown { padding-left: ${CELL_RUN_GUTTER}px; }`); - collector.addRule(`.notebookOverlay .cell .run-button-container { width: 20px; left: ${CODE_CELL_LEFT_MARGIN + Math.floor(CELL_RUN_GUTTER - 20) / 2}px }`); - collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row :not(.webview-backed-markdown-cell) .cell-focus-indicator-top { height: ${CELL_TOP_MARGIN}px; }`); - collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-side { bottom: ${BOTTOM_CELL_TOOLBAR_GAP}px; }`); - collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row.code-cell-row .cell-focus-indicator-left, - .notebookOverlay .monaco-list .monaco-list-row.code-cell-row .cell-drag-handle { width: ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER}px; }`); - collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row.markdown-cell-row .cell-focus-indicator-left { width: ${CODE_CELL_LEFT_MARGIN}px; }`); - collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator.cell-focus-indicator-right { width: ${CELL_RIGHT_MARGIN}px; }`); - collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-bottom { height: ${CELL_BOTTOM_MARGIN}px; }`); - collector.addRule(`.notebookOverlay .monaco-list .monaco-list-row .cell-shadow-container-bottom { top: ${CELL_BOTTOM_MARGIN}px; }`); - - collector.addRule(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-collapsed-part { margin-left: ${CODE_CELL_LEFT_MARGIN + CELL_RUN_GUTTER}px; height: ${COLLAPSED_INDICATOR_HEIGHT}px; }`); - collector.addRule(`.notebookOverlay .cell-list-top-cell-toolbar-container { top: -${SCROLLABLE_ELEMENT_PADDING_TOP}px }`); - - collector.addRule(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container { height: ${BOTTOM_CELL_TOOLBAR_HEIGHT}px }`); - collector.addRule(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .cell-list-top-cell-toolbar-container { height: ${BOTTOM_CELL_TOOLBAR_HEIGHT}px }`); - - // left and right border margins - collector.addRule(` - .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.code-cell-row.focused .cell-focus-indicator-left:before, - .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.code-cell-row.focused .cell-focus-indicator-right:before, - .monaco-workbench .notebookOverlay .monaco-list.selection-multiple .monaco-list-row.code-cell-row.selected .cell-focus-indicator-left:before, - .monaco-workbench .notebookOverlay .monaco-list.selection-multiple .monaco-list-row.code-cell-row.selected .cell-focus-indicator-right:before { - top: -${CELL_TOP_MARGIN}px; height: calc(100% + ${CELL_TOP_MARGIN + CELL_BOTTOM_MARGIN}px) - }`); }); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookKernelActionViewItem.ts b/src/vs/workbench/contrib/notebook/browser/notebookKernelActionViewItem.ts new file mode 100644 index 00000000000..b473b71f80d --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/notebookKernelActionViewItem.ts @@ -0,0 +1,100 @@ +/*--------------------------------------------------------------------------------------------- + * 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/notebookKernelActionViewItem'; +import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; +import { Action, IAction } from 'vs/base/common/actions'; +import { localize } from 'vs/nls'; +import { registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { NotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookEditor'; +import { selectKernelIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; +import { INotebookKernelMatchResult, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; +import { toolbarHoverBackground } from 'vs/platform/theme/common/colorRegistry'; + +registerThemingParticipant((theme, collector) => { + const value = theme.getColor(toolbarHoverBackground); + collector.addRule(`:root { + --code-toolbarHoverBackground: ${value}; + }`); +}); + +export class NotebooKernelActionViewItem extends ActionViewItem { + + private _kernelLabel?: HTMLAnchorElement; + + constructor( + actualAction: IAction, + private readonly _editor: NotebookEditor, + @INotebookKernelService private readonly _notebookKernelService: INotebookKernelService, + ) { + super( + undefined, + new Action('fakeAction', undefined, ThemeIcon.asClassName(selectKernelIcon), true, (event) => actualAction.run(event)), + { label: false, icon: true } + ); + this._register(_editor.onDidChangeModel(this._update, this)); + this._register(_notebookKernelService.onDidChangeNotebookAffinity(this._update, this)); + this._register(_notebookKernelService.onDidChangeNotebookKernelBinding(this._update, this)); + } + + override render(container: HTMLElement): void { + this._update(); + super.render(container); + container.classList.add('kernel-action-view-item'); + this._kernelLabel = document.createElement('a'); + container.appendChild(this._kernelLabel); + this.updateLabel(); + } + + override updateLabel() { + if (this._kernelLabel) { + this._kernelLabel.classList.add('kernel-label'); + this._kernelLabel.innerText = this._action.label; + this._kernelLabel.title = this._action.tooltip; + } + } + + private _update(): void { + const widget = this._editor.getControl(); + if (!widget || !widget.hasModel()) { + this._resetAction(); + return; + } + const notebook = widget.viewModel.notebookDocument; + const info = this._notebookKernelService.getMatchingKernel(notebook); + this._updateActionFromKernelInfo(info); + } + + private _updateActionFromKernelInfo(info: INotebookKernelMatchResult): void { + + if (info.all.length === 0) { + // should not happen - means "bad" context keys + this._resetAction(); + return; + } + + this._action.enabled = true; + const selectedOrSuggested = info.selected ?? info.suggested; + if (selectedOrSuggested) { + // selected or suggested kernel + this._action.label = selectedOrSuggested.label; + this._action.tooltip = selectedOrSuggested.description ?? selectedOrSuggested.detail ?? ''; + if (!info.selected) { + // special UI for selected kernel? + } + + } else { + // many kernels + this._action.label = localize('select', "Select Kernel"); + this._action.tooltip = ''; + } + } + + private _resetAction(): void { + this._action.enabled = false; + this._action.label = ''; + this._action.class = ''; + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index c8cc2383e76..05db784c920 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -27,14 +27,14 @@ import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.pr import { IEditorInput } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { Memento } from 'vs/workbench/common/memento'; -import { INotebookEditorContribution, notebookMarkupRendererExtensionPoint, notebooksExtensionPoint, notebookRendererExtensionPoint, notebooksExtensionPoint2 } from 'vs/workbench/contrib/notebook/browser/extensionPoint'; -import { NotebookEditorOptions, updateEditorTopPadding } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { INotebookEditorContribution, notebooksExtensionPoint, notebookRendererExtensionPoint } from 'vs/workbench/contrib/notebook/browser/extensionPoint'; +import { NotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookDiffEditorInput } from 'vs/workbench/contrib/notebook/browser/notebookDiffEditorInput'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, BUILTIN_RENDERER_ID, CellUri, DisplayOrderKey, INotebookExclusiveDocumentFilter, INotebookMarkupRendererInfo, INotebookContributionData, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, IOutputDto, mimeTypeIsAlwaysSecure, mimeTypeSupportedByCore, NotebookDataDto, NotebookEditorPriority, NotebookRendererMatch, NotebookTextDiffEditorPreview, RENDERER_NOT_AVAILABLE, sortMimeTypes, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, BUILTIN_RENDERER_ID, CellUri, DisplayOrderKey, INotebookExclusiveDocumentFilter, INotebookContributionData, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, IOutputDto, mimeTypeIsAlwaysSecure, mimeTypeSupportedByCore, NotebookDataDto, NotebookEditorPriority, NotebookRendererMatch, NotebookTextDiffEditorPreview, RENDERER_NOT_AVAILABLE, sortMimeTypes, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; -import { NotebookMarkupRendererInfo as NotebookMarkupRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookMarkdownRenderer'; +import { updateEditorTopPadding } from 'vs/workbench/contrib/notebook/common/notebookOptions'; import { NotebookOutputRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookOutputRenderer'; import { NotebookEditorDescriptor, NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; import { ComplexNotebookProviderInfo, INotebookContentProvider, INotebookSerializer, INotebookService, SimpleNotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookService'; @@ -81,7 +81,6 @@ export class NotebookProviderInfoStore extends Disposable { })); notebooksExtensionPoint.setHandler(extensions => this._setupHandler(extensions)); - notebooksExtensionPoint2.setHandler(extensions => this._setupHandler(extensions)); } override dispose(): void { @@ -250,6 +249,10 @@ export class NotebookOutputRendererInfoStore { return this.contributedRenderers.get(rendererId); } + getAll(): NotebookOutputRendererInfo[] { + return Array.from(this.contributedRenderers.values()); + } + add(info: NotebookOutputRendererInfo): void { if (this.contributedRenderers.has(info.id)) { return; @@ -302,7 +305,6 @@ export class NotebookService extends Disposable implements INotebookService { private readonly _notebookProviders = new Map(); private readonly _notebookProviderInfoStore: NotebookProviderInfoStore; private readonly _notebookRenderersInfoStore = this._instantiationService.createInstance(NotebookOutputRendererInfoStore); - private readonly _markdownRenderersInfos = new Set(); private readonly _models = new ResourceMap(); private readonly _onWillAddNotebookDocument = this._register(new Emitter()); @@ -369,38 +371,6 @@ export class NotebookService extends Disposable implements INotebookService { } } }); - notebookMarkupRendererExtensionPoint.setHandler((renderers) => { - this._markdownRenderersInfos.clear(); - - for (const extension of renderers) { - if (!extension.description.enableProposedApi && !extension.description.isBuiltin) { - // Only allow proposed extensions to use this extension point - return; - } - - for (const notebookContribution of extension.value) { - if (!notebookContribution.entrypoint) { // avoid crashing - console.error(`Cannot register renderer for ${extension.description.identifier.value} since it did not have an entrypoint. This is now required: https://github.com/microsoft/vscode/issues/102644`); - continue; - } - - const id = notebookContribution.id; - if (!id) { - console.error(`Notebook renderer from ${extension.description.identifier.value} is missing an 'id'`); - continue; - } - - this._markdownRenderersInfos.add(new NotebookMarkupRendererInfo({ - id, - extension: extension.description, - entrypoint: notebookContribution.entrypoint, - displayName: notebookContribution.displayName, - mimeTypes: notebookContribution.mimeTypes, - dependsOn: notebookContribution.dependsOn, - })); - } - } - }); const updateOrder = () => { const userOrder = this._configurationService.getValue(DisplayOrderKey); @@ -552,8 +522,8 @@ export class NotebookService extends Disposable implements INotebookService { this._notebookRenderersInfoStore.setPreferred(mimeType, rendererId); } - getMarkupRendererInfo(): INotebookMarkupRendererInfo[] { - return Array.from(this._markdownRenderersInfos); + getRenderers(): INotebookRendererInfo[] { + return this._notebookRenderersInfoStore.getAll(); } // --- notebook documents: create, destory, retrieve, enumerate diff --git a/src/vs/workbench/contrib/notebook/browser/view/output/outputRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/output/outputRenderer.ts index 9aa00edf264..14ce25b10ba 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/output/outputRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/output/outputRenderer.ts @@ -9,36 +9,35 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { ICellOutputViewModel, ICommonNotebookEditor, IOutputTransformContribution, IRenderOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { URI } from 'vs/base/common/uri'; import { Disposable } from 'vs/base/common/lifecycle'; +import { localize } from 'vs/nls'; export class OutputRenderer extends Disposable { - protected _contributions: { [key: string]: IOutputTransformContribution; }; - protected _renderers: IOutputTransformContribution[]; - private _richMimeTypeRenderers = new Map(); + + private readonly _richMimeTypeRenderers = new Map(); constructor( notebookEditor: ICommonNotebookEditor, private readonly instantiationService: IInstantiationService ) { super(); - - this._contributions = {}; - this._renderers = []; - - const contributions = NotebookRegistry.getOutputTransformContributions(); - - for (const desc of contributions) { + for (const desc of NotebookRegistry.getOutputTransformContributions()) { try { const contribution = this.instantiationService.createInstance(desc.ctor, notebookEditor); - this._contributions[desc.id] = contribution; contribution.getMimetypes().forEach(mimetype => { this._richMimeTypeRenderers.set(mimetype, contribution); }); + this._register(contribution); } catch (err) { onUnexpectedError(err); } } } + override dispose(): void { + super.dispose(); + this._richMimeTypeRenderers.clear(); + } + getContribution(preferredMimeType: string | undefined): IOutputTransformContribution | undefined { if (preferredMimeType) { return this._richMimeTypeRenderers.get(preferredMimeType); @@ -47,17 +46,16 @@ export class OutputRenderer extends Disposable { return undefined; } - renderNoop(viewModel: ICellOutputViewModel, container: HTMLElement): IRenderOutput { + private _renderNoop(viewModel: ICellOutputViewModel, container: HTMLElement): IRenderOutput { const contentNode = document.createElement('p'); - - contentNode.innerText = `No renderer could be found for output.`; + contentNode.innerText = localize('empty', "No renderer could be found for output."); container.appendChild(contentNode); return { type: RenderOutputType.Mainframe }; } render(viewModel: ICellOutputViewModel, container: HTMLElement, preferredMimeType: string | undefined, notebookUri: URI): IRenderOutput { if (!viewModel.model.outputs.length) { - return this.renderNoop(viewModel, container); + return this._renderNoop(viewModel, container); } if (!preferredMimeType || !this._richMimeTypeRenderers.has(preferredMimeType)) { @@ -67,9 +65,9 @@ export class OutputRenderer extends Disposable { const mimeTypesMessage = mimeTypes.join(', '); if (preferredMimeType) { - contentNode.innerText = `No renderer could be found for MIME type: ${preferredMimeType}`; + contentNode.innerText = localize('noRenderer.1', "No renderer could be found for MIME type: {0}", preferredMimeType); } else { - contentNode.innerText = `No renderer could be found for output. It has the following MIME types: ${mimeTypesMessage}`; + contentNode.innerText = localize('noRenderer.2', "No renderer could be found for output. It has the following MIME types: {0}", mimeTypesMessage); } container.appendChild(contentNode); @@ -82,13 +80,7 @@ export class OutputRenderer extends Disposable { if (items.length && renderer) { return renderer.render(viewModel, items, container, notebookUri); } else { - return this.renderNoop(viewModel, container); + return this._renderNoop(viewModel, container); } } - - override dispose() { - this._contributions = {}; - this._renderers = []; - this._richMimeTypeRenderers.clear(); - } } diff --git a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts index d9e5bd8ee35..60a32f043c7 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as DOM from 'vs/base/browser/dom'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; import { dirname } from 'vs/base/common/resources'; import { isArray } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; @@ -14,6 +14,7 @@ import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ILogService } from 'vs/platform/log/common/log'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { handleANSIOutput } from 'vs/workbench/contrib/debug/browser/debugANSIHandling'; @@ -120,6 +121,8 @@ class CodeRendererContrib extends Disposable implements IOutputRendererContribut return ['text/x-javascript']; } + private readonly _cellDisposables = new Map(); + constructor( public notebookEditor: ICommonNotebookEditor, @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -129,7 +132,19 @@ class CodeRendererContrib extends Disposable implements IOutputRendererContribut super(); } + override dispose(): void { + dispose(this._cellDisposables.values()); + this._cellDisposables.clear(); + super.dispose(); + } + render(output: ICellOutputViewModel, items: IOutputItemDto[], container: HTMLElement, notebookUri: URI): IRenderOutput { + + let cellDisposables = this._cellDisposables.get(output.cellViewModel.handle); + cellDisposables?.dispose(); + cellDisposables = new DisposableStore(); + this._cellDisposables.set(output.cellViewModel.handle, cellDisposables); + const str = items.map(item => getStringValue(item.value)).join(''); const editor = this.instantiationService.createInstance(CodeEditorWidget, container, { ...getOutputSimpleEditorOptions(), @@ -155,6 +170,9 @@ class CodeRendererContrib extends Disposable implements IOutputRendererContribut width }); + cellDisposables.add(editor); + cellDisposables.add(textModel); + container.style.height = `${height + 8}px`; return { type: RenderOutputType.Mainframe }; @@ -260,6 +278,64 @@ class ErrorRendererContrib extends Disposable implements IOutputRendererContribu } } +class JSErrorRendererContrib implements IOutputRendererContribution { + + constructor( + public notebookEditor: ICommonNotebookEditor, + @IThemeService private readonly _themeService: IThemeService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @ILogService private readonly _logService: ILogService, + ) { } + + dispose(): void { + // nothing + } + + getType() { + return RenderOutputType.Mainframe; + } + + getMimetypes() { + return ['application/x.notebook.error']; + } + + render(_output: ICellOutputViewModel, items: IOutputItemDto[], container: HTMLElement, _notebookUri: URI): IRenderOutput { + const linkDetector = this._instantiationService.createInstance(LinkDetector); + + for (let item of items) { + + if (typeof item.value !== 'string') { + this._logService.warn('INVALID output item (not a string)', item.value); + continue; + } + + let err: Error; + try { + err = JSON.parse(item.value); + } catch (e) { + this._logService.warn('INVALID output item (failed to parse)', e); + continue; + } + + const header = document.createElement('div'); + const headerMessage = err.name && err.message ? `${err.name}: ${err.message}` : err.name || err.message; + if (headerMessage) { + header.innerText = headerMessage; + container.appendChild(header); + } + const stack = document.createElement('pre'); + stack.classList.add('traceback'); + if (err.stack) { + stack.appendChild(handleANSIOutput(err.stack, linkDetector, this._themeService, undefined)); + } + container.appendChild(stack); + container.classList.add('error'); + } + + return { type: RenderOutputType.Mainframe }; + } +} + class PlainTextRendererContrib extends Disposable implements IOutputRendererContribution { getType() { return RenderOutputType.Mainframe; @@ -442,6 +518,7 @@ NotebookRegistry.registerOutputTransform('jpeg', JPEGRendererContrib); NotebookRegistry.registerOutputTransform('plain', PlainTextRendererContrib); NotebookRegistry.registerOutputTransform('code', CodeRendererContrib); NotebookRegistry.registerOutputTransform('error-trace', ErrorRendererContrib); +NotebookRegistry.registerOutputTransform('jserror', JSErrorRendererContrib); NotebookRegistry.registerOutputTransform('stream-text', StreamRendererContrib); NotebookRegistry.registerOutputTransform('stderr', StderrRendererContrib); 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 b8a0d789cca..7fc1c3f8bf3 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -29,7 +29,7 @@ import { CellEditState, ICellOutputViewModel, ICommonCellInfo, ICommonNotebookEd import { preloadsScriptStr, WebviewPreloadRenderer } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads'; import { transformWebviewThemeVars } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewThemeMapping'; import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel'; -import { INotebookKernel, INotebookRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookKernel, INotebookRendererInfo, NotebookRendererMatch } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IWebviewService, WebviewContentPurpose, WebviewElement } from 'vs/workbench/contrib/webview/browser/webview'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -713,41 +713,41 @@ export class BackLayerWebView extends Disposable { } private getMarkdownRenderer(): WebviewPreloadRenderer[] { - const allRenderers = this.notebookService.getMarkupRendererInfo(); - const markdownMimeType = 'text/markdown'; + const allRenderers = this.notebookService.getRenderers() + .filter(renderer => renderer.matchesWithoutKernel(markdownMimeType) !== NotebookRendererMatch.Never); const topLevelMarkdownRenderers = allRenderers - .filter(renderer => !renderer.dependsOn) - .filter(renderer => renderer.mimeTypes?.includes(markdownMimeType)); + .filter(renderer => renderer.dependencies.length === 0); const subRenderers = new Map>(); for (const renderer of allRenderers) { - if (renderer.dependsOn) { - if (!subRenderers.has(renderer.dependsOn)) { - subRenderers.set(renderer.dependsOn, []); + for (const dep of renderer.dependencies) { + if (!subRenderers.has(dep)) { + subRenderers.set(dep, []); } - const entryPoint = this.asWebviewUri(renderer.entrypoint); - subRenderers.get(renderer.dependsOn)!.push({ entrypoint: entryPoint.toString(true) }); + const entryPoint = this.asWebviewUri(renderer.entrypoint, renderer.extensionLocation); + subRenderers.get(dep)!.push({ entrypoint: entryPoint.toString(true) }); } } return topLevelMarkdownRenderers.map((renderer): WebviewPreloadRenderer => { - const src = this.asWebviewUri(renderer.entrypoint); + const src = this.asWebviewUri(renderer.entrypoint, renderer.extensionLocation); return { entrypoint: src.toString(), - mimeTypes: [markdownMimeType], + mimeTypes: renderer.mimeTypes, dependencies: subRenderers.get(renderer.id) || [], }; }); } - private asWebviewUri(uri: URI) { + private asWebviewUri(uri: URI, fromExtension: URI | undefined) { + const remoteAuthority = fromExtension?.scheme === Schemas.vscodeRemote ? fromExtension.authority : undefined; return asWebviewUri({ isExtensionDevelopmentDebug: this.environmentService.isExtensionDevelopment, webviewCspSource: this.environmentService.webviewCspSource, webviewResourceRoot: this.environmentService.webviewResourceRoot, - remote: { authority: undefined } // TODO + remote: { authority: remoteAuthority } }, this.id, uri); } @@ -781,11 +781,11 @@ export class BackLayerWebView extends Disposable { resolveFunc = resolve; }); - const baseUrl = this.asWebviewUri(dirname(this.documentUri)); + const baseUrl = this.asWebviewUri(dirname(this.documentUri), undefined); if (!isWeb) { const loaderUri = FileAccess.asFileUri('vs/loader.js', require); - const loader = this.asWebviewUri(loaderUri); + const loader = this.asWebviewUri(loaderUri, undefined); coreDependencies = `