diff --git a/extensions/emmet/package.json b/extensions/emmet/package.json index 0af69221a29..77b08c02ac9 100644 --- a/extensions/emmet/package.json +++ b/extensions/emmet/package.json @@ -226,7 +226,11 @@ }, "output.selfClosingStyle": { "type": "string", - "enum": ["html", "xhtml", "xml"], + "enum": [ + "html", + "xhtml", + "xml" + ], "default": "html", "markdownDescription": "%emmetPreferencesOutputSelfClosingStyle%" }, diff --git a/extensions/emmet/yarn.lock b/extensions/emmet/yarn.lock index b9d9785a550..2d35a2c78ab 100644 --- a/extensions/emmet/yarn.lock +++ b/extensions/emmet/yarn.lock @@ -54,9 +54,9 @@ integrity sha1-Rs/+oRmgoAMxKiHC2bVijLX81EI= "@types/node@14.x": - version "14.14.43" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.43.tgz#26bcbb0595b305400e8ceaf9a127a7f905ae49c8" - integrity sha512-3pwDJjp1PWacPTpH0LcfhgjvurQvrZFBrC6xxjaUEZ7ifUtT32jtjPxEMMblpqd2Mvx+k8haqQJLQxolyGN/cQ== + version "14.17.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.1.tgz#5e07e0cb2ff793aa7a1b41deae76221e6166049f" + integrity sha512-/tpUyFD7meeooTRwl3sYlihx2BrJE7q9XF71EguPFIySj9B7qgnRtHsHTho+0AUm4m1SvWGm6uSncrR94q6Vtw== emmet@^2.3.0: version "2.3.4" @@ -77,9 +77,9 @@ jsonc-parser@^2.3.0: integrity sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg== vscode-emmet-helper@^2.3.0: - version "2.6.2" - resolved "https://registry.yarnpkg.com/vscode-emmet-helper/-/vscode-emmet-helper-2.6.2.tgz#777b471a7851ba0ca8e4151533be7f92511f39b0" - integrity sha512-SkL1WjZZsA+bfTo52QH4PgqXCQAJSqzOmJtAY3rOl17MKbY6iJhVv2T26PshjmUnHoXnXMNa7PcLMCS75RsQDQ== + version "2.6.4" + resolved "https://registry.yarnpkg.com/vscode-emmet-helper/-/vscode-emmet-helper-2.6.4.tgz#bea47f17649bba26b412f3d1fac18aaee43eba25" + integrity sha512-fP0nunW1RUWEKGf4gqiYLOVNFFGXSRHjCl0pikxtwCFlty8WwimM+RBJ5o0aIiwerrYD30HqeaVyvDW027Sseg== dependencies: emmet "^2.3.0" jsonc-parser "^2.3.0" diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 728a439f0dd..a9e98206b3e 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -40,7 +40,7 @@ } }, "contributes": { - "notebookOutputRenderer": [ + "notebookRenderer": [ { "id": "markdownItRenderer", "displayName": "Markdown it renderer", diff --git a/extensions/notebook-markdown-extensions/package.json b/extensions/notebook-markdown-extensions/package.json index ef3911f2eb9..981566798f6 100644 --- a/extensions/notebook-markdown-extensions/package.json +++ b/extensions/notebook-markdown-extensions/package.json @@ -21,7 +21,7 @@ } }, "contributes": { - "notebookOutputRenderer": [ + "notebookRenderer": [ { "id": "markdownItRenderer-katex", "displayName": "Markdown it katex renderer", 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 b00302b2808..98b9ac8c12b 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts @@ -7,6 +7,7 @@ import 'mocha'; import * as assert from 'assert'; import * as vscode from 'vscode'; import { createRandomFile, asPromise, disposeAll, closeAllEditors, revertAllDirty, saveAllEditors, assertNoRpc } from '../utils'; +import { TextDecoder } from 'util'; async function createRandomNotebookFile() { return createRandomFile('', undefined, '.vsctestnb'); @@ -60,13 +61,13 @@ class Kernel { task.executionOrder = 1; if (cell.notebook.uri.path.endsWith('customRenderer.vsctestnb')) { await task.replaceOutput([new vscode.NotebookCellOutput([ - new vscode.NotebookCellOutputItem('text/custom', ['test'], undefined) + vscode.NotebookCellOutputItem.text('test', 'text/custom', undefined) ])]); return; } await task.replaceOutput([new vscode.NotebookCellOutput([ - new vscode.NotebookCellOutputItem('text/plain', ['my output'], undefined) + vscode.NotebookCellOutputItem.text('my output', 'text/plain', undefined) ])]); task.end({ success: true }); } @@ -129,7 +130,7 @@ suite('Notebook API tests', function () { kind: vscode.NotebookCellKind.Code, outputs: [ new vscode.NotebookCellOutput([ - new vscode.NotebookCellOutputItem('text/plain', 'Hello World', { testOutputItemMetadata: true }) + vscode.NotebookCellOutputItem.text('Hello World', 'text/plain', { testOutputItemMetadata: true }) ], { testOutputMetadata: true }) ], @@ -182,7 +183,7 @@ suite('Notebook API tests', function () { const task = this.controller.createNotebookCellExecutionTask(cell); task.start(); await task.replaceOutput([new vscode.NotebookCellOutput([ - new vscode.NotebookCellOutputItem('text/plain', ['my second output'], undefined) + vscode.NotebookCellOutputItem.text('my second output', 'text/plain', undefined) ])]); task.end({ success: true }); } @@ -476,7 +477,7 @@ suite('Notebook API tests', function () { assert.deepStrictEqual(secondCell!.outputs[0].metadata, { testOutputMetadata: true }); assert.strictEqual(secondCell!.outputs[0].outputs.length, 1); assert.strictEqual(secondCell!.outputs[0].outputs[0].mime, 'text/plain'); - assert.strictEqual(secondCell!.outputs[0].outputs[0].value, 'Hello World'); + assert.strictEqual(new TextDecoder().decode(secondCell!.outputs[0].outputs[0].data), 'Hello World'); assert.deepStrictEqual(secondCell!.outputs[0].outputs[0].metadata, { testOutputItemMetadata: true }); assert.strictEqual(secondCell!.executionSummary?.executionOrder, 5); assert.strictEqual(secondCell!.executionSummary?.success, true); @@ -747,9 +748,7 @@ suite('Notebook API tests', function () { assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked assert.strictEqual(cell.outputs[0].outputs.length, 1); assert.strictEqual(cell.outputs[0].outputs[0].mime, 'text/plain'); - assert.deepStrictEqual(cell.outputs[0].outputs[0].value, [ - 'my output' - ]); + assert.deepStrictEqual(new TextDecoder().decode(cell.outputs[0].outputs[0].data), 'my output'); }); await withEvent(vscode.notebook.onDidChangeCellOutputs, async (event) => { @@ -759,9 +758,7 @@ suite('Notebook API tests', function () { assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked assert.strictEqual(cell.outputs[0].outputs.length, 1); assert.strictEqual(cell.outputs[0].outputs[0].mime, 'text/plain'); - assert.deepStrictEqual(cell.outputs[0].outputs[0].value, [ - 'my second output' - ]); + assert.deepStrictEqual(new TextDecoder().decode(cell.outputs[0].outputs[0].data), 'my second output'); }); }); @@ -779,7 +776,7 @@ suite('Notebook API tests', function () { task.start(); task.token.onCancellationRequested(async () => { await task.replaceOutput([new vscode.NotebookCellOutput([ - new vscode.NotebookCellOutputItem('text/plain', ['Canceled'], undefined) + vscode.NotebookCellOutputItem.text('Canceled', 'text/plain', undefined) ])]); task.end({}); }); @@ -801,9 +798,7 @@ suite('Notebook API tests', function () { assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked assert.strictEqual(cell.outputs[0].outputs.length, 1); assert.strictEqual(cell.outputs[0].outputs[0].mime, 'text/plain'); - assert.deepStrictEqual(cell.outputs[0].outputs[0].value, [ - 'Canceled' - ]); + assert.deepStrictEqual(new TextDecoder().decode(cell.outputs[0].outputs[0].data), 'Canceled'); }); cancelableKernel.controller.dispose(); @@ -826,7 +821,7 @@ suite('Notebook API tests', function () { async interrupt() { await this._task!.replaceOutput([new vscode.NotebookCellOutput([ - new vscode.NotebookCellOutputItem('text/plain', ['Interrupted'], undefined) + vscode.NotebookCellOutputItem.text('Interrupted', 'text/plain', undefined) ])]); this._task!.end({}); } @@ -845,9 +840,7 @@ suite('Notebook API tests', function () { assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked assert.strictEqual(cell.outputs[0].outputs.length, 1); assert.strictEqual(cell.outputs[0].outputs[0].mime, 'text/plain'); - assert.deepStrictEqual(cell.outputs[0].outputs[0].value, [ - 'Interrupted' - ]); + assert.deepStrictEqual(new TextDecoder().decode(cell.outputs[0].outputs[0].data), 'Interrupted'); }); interruptableKernel.controller.dispose(); @@ -1188,10 +1181,10 @@ suite('Notebook API tests', function () { const task = this.controller.createNotebookCellExecutionTask(cell); task.start(); await task.replaceOutput([new vscode.NotebookCellOutput([ - new vscode.NotebookCellOutputItem('text/plain', ['Some output'], undefined) + vscode.NotebookCellOutputItem.text('Some output', 'text/plain', undefined) ])]); assert.strictEqual(cell.notebook.cellAt(0).outputs.length, 1); - assert.deepStrictEqual(cell.notebook.cellAt(0).outputs[0].outputs[0].value, ['Some output']); + assert.deepStrictEqual(new TextDecoder().decode(cell.notebook.cellAt(0).outputs[0].outputs[0].data), 'Some output'); task.end({}); called = true; } diff --git a/extensions/vscode-notebook-tests/package.json b/extensions/vscode-notebook-tests/package.json index c6c690151b6..6a0e2d648de 100644 --- a/extensions/vscode-notebook-tests/package.json +++ b/extensions/vscode-notebook-tests/package.json @@ -46,7 +46,7 @@ ] } ], - "notebookOutputRenderer": [ + "notebookRenderer": [ { "id": "notebookCoreTestRenderer", "displayName": "Notebook Core Test Renderer", diff --git a/extensions/vscode-notebook-tests/src/extension.ts b/extensions/vscode-notebook-tests/src/extension.ts index 7ca6edc14f6..935968c0b4e 100644 --- a/extensions/vscode-notebook-tests/src/extension.ts +++ b/extensions/vscode-notebook-tests/src/extension.ts @@ -69,7 +69,7 @@ export function activate(context: vscode.ExtensionContext): any { const task = controller.createNotebookCellExecutionTask(cell); task.start(); task.replaceOutput([new vscode.NotebookCellOutput([ - new vscode.NotebookCellOutputItem('text/html', ['test output'], undefined) + vscode.NotebookCellOutputItem.text('test output', 'text/html', undefined) ])]); task.end({ success: true }); } diff --git a/extensions/vscode-test-resolver/package.json b/extensions/vscode-test-resolver/package.json index ae6a4f0680f..9aa252ea511 100644 --- a/extensions/vscode-test-resolver/package.json +++ b/extensions/vscode-test-resolver/package.json @@ -32,7 +32,8 @@ "capabilities": { "untrustedWorkspaces": { "supported": true - } + }, + "virtualWorkspaces": true }, "contributes": { "resourceLabelFormatters": [ @@ -88,7 +89,7 @@ "statusBar/remoteIndicator": [ { "command": "vscode-testresolver.newWindow", - "when": "!remoteName", + "when": "!remoteName && !virtualWorkspace", "group": "remote_90_test_1_local@2" }, { diff --git a/src/vs/code/electron-browser/workbench/workbench.html b/src/vs/code/electron-browser/workbench/workbench.html index bd37436f1fd..38405e41e15 100644 --- a/src/vs/code/electron-browser/workbench/workbench.html +++ b/src/vs/code/electron-browser/workbench/workbench.html @@ -4,7 +4,7 @@ - + diff --git a/src/vs/code/electron-sandbox/workbench/workbench.html b/src/vs/code/electron-sandbox/workbench/workbench.html index bd37436f1fd..38405e41e15 100644 --- a/src/vs/code/electron-sandbox/workbench/workbench.html +++ b/src/vs/code/electron-sandbox/workbench/workbench.html @@ -4,7 +4,7 @@ - + diff --git a/src/vs/platform/terminal/common/terminalPlatformConfiguration.ts b/src/vs/platform/terminal/common/terminalPlatformConfiguration.ts index 3ab5f4e8620..95e697e62cf 100644 --- a/src/vs/platform/terminal/common/terminalPlatformConfiguration.ts +++ b/src/vs/platform/terminal/common/terminalPlatformConfiguration.ts @@ -10,6 +10,7 @@ import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; import { Registry } from 'vs/platform/registry/common/platform'; import { Codicon, iconRegistry } from 'vs/base/common/codicons'; import { OperatingSystem } from 'vs/base/common/platform'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; const terminalProfileBaseProperties: IJSONSchemaMap = { args: { @@ -372,7 +373,7 @@ export function registerTerminalDefaultProfileConfiguration(detectedProfiles?: { } function createProfileDescription(profile: ITerminalProfile): string { - let description = `$(${profile.icon || Codicon.terminal.id}) ${profile.profileName}\n- path: ${profile.path}`; + let description = `$(${ThemeIcon.isThemeIcon(profile.icon) ? profile.icon.id : profile.icon ? profile.icon : Codicon.terminal.id}) ${profile.profileName}\n- path: ${profile.path}`; if (profile.args) { if (typeof profile.args === 'string') { description += `\n- args: "${profile.args}"`; diff --git a/src/vs/platform/terminal/node/terminalProfiles.ts b/src/vs/platform/terminal/node/terminalProfiles.ts index 8082d39f17f..878376b9e2a 100644 --- a/src/vs/platform/terminal/node/terminalProfiles.ts +++ b/src/vs/platform/terminal/node/terminalProfiles.ts @@ -37,6 +37,7 @@ export function detectAvailableProfiles( safeConfigProvider(TerminalSettingId.UseWslProfiles) !== false, safeConfigProvider(TerminalSettingId.ProfilesWindows), safeConfigProvider(TerminalSettingId.DefaultProfileWindows), + testPaths, variableResolver ); } @@ -58,6 +59,7 @@ async function detectAvailableWindowsProfiles( useWslProfiles?: boolean, configProfiles?: { [key: string]: ITerminalProfileObject }, defaultProfileName?: string, + testPaths?: string[], variableResolver?: (text: string[]) => Promise ): Promise { // Determine the correct System32 path. We want to point to Sysnative @@ -73,7 +75,7 @@ async function detectAvailableWindowsProfiles( useWSLexe = true; } - await initializeWindowsProfiles(); + await initializeWindowsProfiles(testPaths); const detectedProfiles: Map = new Map(); @@ -175,7 +177,7 @@ async function transformToTerminalProfiles( return resultProfiles; } -async function initializeWindowsProfiles(): Promise { +async function initializeWindowsProfiles(testPaths?: string[]): Promise { if (profileSources) { return; } @@ -197,7 +199,7 @@ async function initializeWindowsProfiles(): Promise { }); profileSources.set('PowerShell', { profileName: 'PowerShell', - paths: await getPowershellPaths(), + paths: testPaths || await getPowershellPaths(), icon: ThemeIcon.asThemeIcon(Codicon.terminalPowershell) }); } @@ -245,23 +247,25 @@ async function getWslProfiles(wslPath: string, defaultProfileName: string | unde profileName, path: wslPath, args: [`-d`, `${distroName}`], - isDefault: profileName === defaultProfileName + isDefault: profileName === defaultProfileName, + icon: getWslIcon(distroName) }; - if (distroName.includes('Ubuntu')) { - profile.icon = ThemeIcon.asThemeIcon(Codicon.terminalUbuntu); - } - else if (distroName.includes('Debian')) { - profile.icon = ThemeIcon.asThemeIcon(Codicon.terminalDebian); - } else { - profile.icon = ThemeIcon.asThemeIcon(Codicon.terminalLinux); - } - // Add the profile profiles.push(profile); } return profiles; } +function getWslIcon(distroName: string): ThemeIcon { + if (distroName.includes('Ubuntu')) { + return ThemeIcon.asThemeIcon(Codicon.terminalUbuntu); + } else if (distroName.includes('Debian')) { + return ThemeIcon.asThemeIcon(Codicon.terminalDebian); + } else { + return ThemeIcon.asThemeIcon(Codicon.terminalLinux); + } +} + async function detectAvailableUnixProfiles( fsProvider: IFsProvider, logService?: ILogService, diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 94096b84ecf..10df4b473fa 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1247,11 +1247,12 @@ declare module 'vscode' { * * *Note* that an UTF-8 encoder is used to create bytes for the string. * - * @param value A string/ + * @param value A string. * @param mime Optional MIME type, defaults to `text/plain`. + * @param metadata Optional metadata. * @returns A new output item object. */ - static text(value: string, mime?: string): NotebookCellOutputItem; + static text(value: string, mime?: string, metadata?: { [key: string]: any }): NotebookCellOutputItem; /** * Factory function to create a `NotebookCellOutputItem` from @@ -1263,46 +1264,40 @@ declare module 'vscode' { * * @param value A JSON-stringifyable value. * @param mime Optional MIME type, defaults to `application/json` + * @param metadata Optional metadata. * @returns A new output item object. */ - static json(value: any, mime?: string): NotebookCellOutputItem; - - /** - * Factory function to create a `NotebookCellOutputItem` from bytes. - * - * @param value An array of unsigned 8-bit integers. - * @param mime Optional MIME type, defaults to `application/octet-stream`. - * @returns A new output item object. - */ - //todo@API better names: bytes, raw, buffer? - static bytes(value: Uint8Array, mime?: string): NotebookCellOutputItem; + static json(value: any, mime?: string, metadata?: { [key: string]: any }): NotebookCellOutputItem; /** * Factory function to create a `NotebookCellOutputItem` that uses * uses the `application/vnd.code.notebook.stdout` mime type. * * @param value A string. + * @param metadata Optional metadata. * @returns A new output item object. */ - static stdout(value: string): NotebookCellOutputItem; + static stdout(value: string, metadata?: { [key: string]: any }): NotebookCellOutputItem; /** * Factory function to create a `NotebookCellOutputItem` that uses * uses the `application/vnd.code.notebook.stderr` mime type. * * @param value A string. + * @param metadata Optional metadata. * @returns A new output item object. */ - static stderr(value: string): NotebookCellOutputItem; + static stderr(value: string, metadata?: { [key: string]: any }): NotebookCellOutputItem; /** * Factory function to create a `NotebookCellOutputItem` that uses * uses the `application/vnd.code.notebook.error` mime type. * * @param value An error object. + * @param metadata Optional metadata. * @returns A new output item object. */ - static error(value: Error): NotebookCellOutputItem; + static error(value: Error, metadata?: { [key: string]: any }): NotebookCellOutputItem; /** * The mime type which determines how the {@link NotebookCellOutputItem.value `value`}-property @@ -1314,21 +1309,26 @@ declare module 'vscode' { mime: string; /** - * The value of this output item. Must always be an array of unsigned 8-bit integers. + * The data of this output item. Must always be an array of unsigned 8-bit integers. */ - //todo@API only Unit8Array - value: Uint8Array | unknown; + data: Uint8Array; + /** + * @deprecated + */ + value: unknown; + + //todo@API metadata?: { [key: string]: any }; /** * Create a new notbook cell output item. * + * @param data The value of the output item. * @param mime The mime type of the output item. - * @param value The value of the output item. * @param metadata Optional metadata for this output item. */ - constructor(mime: string, value: Uint8Array | unknown, metadata?: { [key: string]: any }); + constructor(data: Uint8Array, mime: string, metadata?: { [key: string]: any }); } // @jrieken transient @@ -1563,6 +1563,9 @@ declare module 'vscode' { /** * The identifier of this notebook controller. + * + * _Note_ that controllers are remembered by their identifier and that extensions should use + * stable identifiers across sessions. */ readonly id: string; @@ -1654,6 +1657,7 @@ declare module 'vscode' { * @param cell The notebook cell for which to create the execution. * @returns A notebook cell execution. */ + // todo@API rename to NotebookCellExecution createNotebookCellExecutionTask(cell: NotebookCell): NotebookCellExecutionTask; // todo@API find a better name than "preloads" diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index 1deba734389..ea3e1713eff 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -350,7 +350,7 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I public getDefaultShellArgs(useAutomationShell: boolean): string[] | string { const profile = useAutomationShell ? this._defaultAutomationProfile : this._defaultProfile; - return profile?.args || ['']; + return profile?.args || []; } public createExtensionTerminal(options: vscode.ExtensionTerminalOptions): vscode.Terminal { diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 94bfff83773..9cb42ef3d54 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { coalesce, isNonEmptyArray } from 'vs/base/common/arrays'; -import { VSBuffer } from 'vs/base/common/buffer'; import * as htmlContent from 'vs/base/common/htmlContent'; import { DisposableStore } from 'vs/base/common/lifecycle'; import * as marked from 'vs/base/common/marked/marked'; @@ -1536,9 +1535,9 @@ export namespace NotebookCellOutputItem { export function from(item: types.NotebookCellOutputItem): notebooks.IOutputItemDto { let value: unknown; let valueBytes: number[] | undefined; - if (item.value instanceof Uint8Array) { + if (item.data instanceof Uint8Array) { //todo@jrieken this HACKY and SLOW... hoist VSBuffer instead - valueBytes = Array.from(item.value); + valueBytes = Array.from(item.data); } else { value = item.value; } @@ -1551,15 +1550,13 @@ export namespace NotebookCellOutputItem { } export function to(item: notebooks.IOutputItemDto): types.NotebookCellOutputItem { - - let value: Uint8Array | unknown; - if (item.value instanceof VSBuffer) { - value = item.value.buffer; + let value: Uint8Array | any; + if (Array.isArray(item.valueBytes)) { + value = new Uint8Array(item.valueBytes); } else { value = item.value; } - - return new types.NotebookCellOutputItem(item.mime, value, item.metadata); + return new types.NotebookCellOutputItem(value, item.mime, item.metadata); } } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 1dfecfab513..fdccb8c119c 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -3112,44 +3112,50 @@ export class NotebookCellOutputItem { return typeof (obj).mime === 'string'; } - static error(err: Error | { name: string, message?: string, stack?: string }): NotebookCellOutputItem { + static error(err: Error | { name: string, message?: string, stack?: string }, metadata?: { [key: string]: any }): NotebookCellOutputItem { const obj = { name: err.name, message: err.message, stack: err.stack }; - return NotebookCellOutputItem.json(obj, 'application/vnd.code.notebook.error'); + return NotebookCellOutputItem.json(obj, 'application/vnd.code.notebook.error', metadata); } - static stdout(value: string): NotebookCellOutputItem { - return NotebookCellOutputItem.text(value, 'application/vnd.code.notebook.stdout'); + static stdout(value: string, metadata?: { [key: string]: any }): NotebookCellOutputItem { + return NotebookCellOutputItem.text(value, 'application/vnd.code.notebook.stdout', metadata); } - static stderr(value: string): NotebookCellOutputItem { - return NotebookCellOutputItem.text(value, 'application/vnd.code.notebook.stderr'); + static stderr(value: string, metadata?: { [key: string]: any }): NotebookCellOutputItem { + return NotebookCellOutputItem.text(value, 'application/vnd.code.notebook.stderr', metadata); } - static bytes(value: Uint8Array, mime: string = 'application/octet-stream'): NotebookCellOutputItem { - return new NotebookCellOutputItem(mime, value); + static bytes(value: Uint8Array, mime: string = 'application/octet-stream', metadata?: { [key: string]: any }): NotebookCellOutputItem { + return new NotebookCellOutputItem(value, mime, metadata); } static #encoder = new TextEncoder(); - static text(value: string, mime: string = 'text/plain'): NotebookCellOutputItem { + static text(value: string, mime: string = 'text/plain', metadata?: { [key: string]: any }): NotebookCellOutputItem { const bytes = NotebookCellOutputItem.#encoder.encode(String(value)); - return new NotebookCellOutputItem(mime, bytes); + return new NotebookCellOutputItem(bytes, mime, metadata); } - static json(value: any, mime: string = 'application/json'): NotebookCellOutputItem { + static json(value: any, mime: string = 'application/json', metadata?: { [key: string]: any }): NotebookCellOutputItem { const rawStr = JSON.stringify(value, undefined, '\t'); - return NotebookCellOutputItem.text(rawStr, mime); + return NotebookCellOutputItem.text(rawStr, mime, metadata); } + /** @deprecated */ + public value: Uint8Array | unknown; // JSON'able + constructor( + public data: Uint8Array, public mime: string, - public value: Uint8Array | unknown, // JSON'able - public metadata?: Record + public metadata?: { [key: string]: any } ) { + if (!(data instanceof Uint8Array)) { + this.value = data; + } if (isFalsyOrWhitespace(this.mime)) { throw new Error('INVALID mime type, must not be empty or falsy'); } diff --git a/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts b/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts index e925a6fdf98..406d5a41951 100644 --- a/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts +++ b/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts @@ -192,6 +192,6 @@ export const notebooksExtensionPoint = ExtensionsRegistry.registerExtensionPoint export const notebookRendererExtensionPoint = ExtensionsRegistry.registerExtensionPoint( { - extensionPoint: 'notebookOutputRenderer', + extensionPoint: 'notebookRenderer', jsonSchema: notebookRendererContribution }); diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebook.css b/src/vs/workbench/contrib/notebook/browser/media/notebook.css index 012a0b71fa7..a1769e49bcb 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebook.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebook.css @@ -425,7 +425,7 @@ overflow: hidden; } -.monaco-workbench .notebookOverlay.cell-statusbar-hidden .cell-statusbar-container { +.monaco-workbench .notebookOverlay .cell-statusbar-hidden .cell-statusbar-container { display: none; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 18c20f8a83b..69f20ea18bc 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -30,7 +30,7 @@ import { NotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookEd import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { NotebookService } from 'vs/workbench/contrib/notebook/browser/notebookServiceImpl'; -import { CellKind, CellToolbarLocKey, CellToolbarVisibility, CellUri, DisplayOrderKey, UndoRedoPerCell, ExperimentalUseMarkdownRenderer, getCellUndoRedoComparisonKey, IResolvedNotebookEditorModel, NotebookDocumentBackupData, NotebookTextDiffEditorPreview, NotebookWorkingCopyTypeIdentifier, ShowCellStatusBarKey, CompactView, FocusIndicator, InsertToolbarPosition, GlobalToolbar, ConsolidatedOutputButton, ShowFoldingControls, DragAndDropEnabled } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, CellToolbarLocKey, CellToolbarVisibility, CellUri, DisplayOrderKey, UndoRedoPerCell, ExperimentalUseMarkdownRenderer, getCellUndoRedoComparisonKey, IResolvedNotebookEditorModel, NotebookDocumentBackupData, NotebookTextDiffEditorPreview, NotebookWorkingCopyTypeIdentifier, ShowCellStatusBarKey, CompactView, FocusIndicator, InsertToolbarPosition, GlobalToolbar, ConsolidatedOutputButton, ShowFoldingControls, DragAndDropEnabled, ShowCellStatusBarAfterExecuteKey } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; @@ -582,6 +582,12 @@ configurationRegistry.registerConfiguration({ default: true, tags: ['notebookLayout'] }, + [ShowCellStatusBarAfterExecuteKey]: { + description: nls.localize('notebook.showCellStatusbarAfterExecute.description', "Whether the cell status bar should be shown after the cell has been executed."), + type: 'boolean', + default: false, + tags: ['notebookLayout'] + }, [NotebookTextDiffEditorPreview]: { description: nls.localize('notebook.diff.enablePreview.description', "Whether to use the enhanced text diff editor for notebook."), type: 'boolean', diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index d99af644b75..a2c0b6f900a 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -235,6 +235,7 @@ export interface MarkdownCellLayoutInfo { readonly fontInfo: FontInfo | null; readonly editorWidth: number; readonly editorHeight: number; + readonly previewHeight: number; readonly bottomToolbarOffset: number; readonly totalHeight: number; } @@ -242,6 +243,8 @@ export interface MarkdownCellLayoutInfo { export interface MarkdownCellLayoutChangeEvent { font?: FontInfo; outerWidth?: number; + editorHeight?: number; + previewHeight?: number; totalHeight?: number; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index b9b697c5514..0218baf679a 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -511,9 +511,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor const cellToolbarLocation = this._notebookOptions.computeCellToolbarLocation(this.viewModel?.viewType); this._overlayContainer.classList.add(`cell-title-toolbar-${cellToolbarLocation}`); - const showCellStatusBar = this._notebookOptions.getLayoutConfiguration().showCellStatusBar; - this._overlayContainer.classList.toggle('cell-statusbar-hidden', !showCellStatusBar); - const cellToolbarInteraction = this._notebookOptions.getLayoutConfiguration().cellToolbarInteraction; let cellToolbarInteractionState = 'hover'; this._overlayContainer.classList.remove('cell-toolbar-hover'); @@ -555,6 +552,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor cellRunGutter, cellBottomMargin, codeCellLeftMargin, + markdownCellGutter, markdownCellLeftMargin, markdownCellBottomMargin, markdownCellTopMargin, @@ -565,7 +563,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor focusIndicator, insertToolbarPosition, insertToolbarAlignment, - fontSize + fontSize, + focusIndicatorLeftMargin } = this._notebookOptions.getLayoutConfiguration(); const styleSheets: string[] = []; @@ -643,12 +642,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor height: 100%; z-index: 10; } - - .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-left:before { - border-left: 1px solid transparent; - border-right: 1px solid transparent; - border-radius: 2px; - } `); // left and right border margins @@ -659,6 +652,14 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor .monaco-workbench .notebookOverlay .monaco-list.selection-multiple .monaco-list-row.code-cell-row.selected .cell-focus-indicator-right:before { top: 0px; height: 100%; }`); + + styleSheets.push(` + .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.focused .cell-focus-indicator-left:before, + .monaco-workbench .notebookOverlay .monaco-list .monaco-list-row.selected .cell-focus-indicator-left:before { + border-left: 3px solid transparent; + border-radius: 2px; + margin-left: ${focusIndicatorLeftMargin}px; + }`); } // between cell insert toolbar @@ -727,7 +728,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor styleSheets.push(`.monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .execution-count-label { left: ${codeCellLeftMargin}px; width: ${cellRunGutter}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(`.monaco-workbench .notebookOverlay > .cell-list-container .notebook-folding-indicator { left: ${(markdownCellLeftMargin - 20) / 2}px; }`); + styleSheets.push(`.monaco-workbench .notebookOverlay > .cell-list-container .notebook-folding-indicator { left: ${(markdownCellGutter - 20) / 2 + markdownCellLeftMargin}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, @@ -2484,10 +2485,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor const cell = this.getCellById(cellId); const layoutConfiguration = this._notebookOptions.getLayoutConfiguration(); if (cell && cell instanceof MarkdownCellViewModel) { - if (height + layoutConfiguration.bottomToolbarGap !== cell.layoutInfo.totalHeight) { - this._debug('updateMarkdownCellHeight', cell.handle, height + layoutConfiguration.bottomToolbarGap, isInit); - cell.renderedMarkdownHeight = height; - } + this._debug('updateMarkdownCellHeight', cell.handle, height + layoutConfiguration.bottomToolbarGap, isInit); + cell.renderedMarkdownHeight = height; } } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index 90c4f8bc43e..7e6311c80a3 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -349,13 +349,13 @@ export class NotebookService extends Disposable implements INotebookService { for (const extension of renderers) { 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`); + extension.collector.error(`Notebook renderer does not specify entry point`); continue; } const id = notebookContribution.id ?? notebookContribution.viewType; if (!id) { - console.error(`Notebook renderer from ${extension.description.identifier.value} is missing an 'id'`); + extension.collector.error(`Notebook renderer does not specify id-property`); continue; } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts index a1abbac5a74..c58a65e0cc9 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -521,6 +521,8 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR templateData.focusIndicatorBottom.style.top = `${indicatorPostion.bottomIndicatorTop}px`; templateData.focusIndicatorLeft.style.height = `${indicatorPostion.verticalIndicatorHeight}px`; templateData.focusIndicatorRight.style.height = `${indicatorPostion.verticalIndicatorHeight}px`; + + templateData.container.classList.toggle('cell-statusbar-hidden', element.getEditorStatusbarHeight() === 0); } private updateForHover(element: MarkdownCellViewModel, templateData: MarkdownCellRenderTemplate): void { @@ -852,7 +854,7 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende } } - private updateForInternalMetadata(element: CodeCellViewModel, templateData: CodeCellRenderTemplate, editorOptions: CellEditorOptions): void { + private updateForInternalMetadata(element: CodeCellViewModel, templateData: CodeCellRenderTemplate): void { if (!this.notebookEditor.hasModel()) { return; } @@ -894,6 +896,8 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende templateData.outputContainer.style.top = `${element.layoutInfo.outputContainerOffset}px`; templateData.outputShowMoreContainer.style.top = `${element.layoutInfo.outputShowMoreContainerOffset}px`; templateData.dragHandle.style.height = `${element.layoutInfo.totalHeight - layoutInfo.bottomToolbarGap}px`; + + templateData.container.classList.toggle('cell-statusbar-hidden', element.getEditorStatusbarHeight() === 0); } renderElement(element: CodeCellViewModel, index: number, templateData: CodeCellRenderTemplate, height: number | undefined): void { @@ -959,13 +963,14 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende this.updateForLayout(element, templateData); })); - this.updateForInternalMetadata(element, templateData, cellEditorOptions); + this.updateForInternalMetadata(element, templateData); this.updateForHover(element, templateData); this.updateForFocus(element, templateData); cellEditorOptions.setLineNumbers(element.lineNumbers); elementDisposables.add(element.onDidChangeState((e) => { if (e.internalMetadataChanged) { - this.updateForInternalMetadata(element, templateData, cellEditorOptions); + this.updateForInternalMetadata(element, templateData); + this.updateForLayout(element, templateData); } if (e.outputIsHoveredChanged) { diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts index 1b0270c2b6d..79574328b08 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts @@ -250,6 +250,7 @@ export class StatefulMarkdownCell extends Disposable { private updateFoldingIconShowClass() { const showFoldingIcon = this.notebookEditor.notebookOptions.getLayoutConfiguration().showFoldingControls; + this.templateData.foldingIndicator.classList.remove('mouseover', 'always'); this.templateData.foldingIndicator.classList.add(showFoldingIcon); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index f097895be8b..8250b75e1f3 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -468,8 +468,10 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re text(): string; json(): any; - bytes(): Uint8Array + data(): Uint8Array; blob(): Blob; + /** @deprecated */ + bytes(): Uint8Array; } interface IDestroyCellInfo { @@ -492,7 +494,7 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re postKernelMessage: (data: unknown) => postNotebookMessage('customKernelMessage', { message: data }), }; - const ttPolicy = window.trustedTypes?.createPolicy('notebookOutputRenderer', { + const ttPolicy = window.trustedTypes?.createPolicy('notebookRenderer', { createHTML: value => value, createScript: value => value, }); @@ -643,9 +645,10 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re mime: content.mimeType, value: content.value, metadata: content.metadata, - bytes() { + data() { return content.valueBytes; }, + bytes() { return this.data(); }, text() { return new TextDecoder().decode(content.valueBytes) || String(content.value); //todo@jrieken remove this once `value` is gone! @@ -1038,8 +1041,9 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re outputId: undefined, text() { return content; }, json() { return undefined; }, - bytes() { return new Uint8Array(); }, - blob() { return new Blob(); }, + bytes() { return this.data(); }, + data() { return new TextEncoder().encode(content); }, + blob() { return new Blob([this.data()], { type: this.mime }); }, }); } }(); diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts index c1ebfdb4c06..a768023c05a 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts @@ -144,10 +144,8 @@ export abstract class BaseCellViewModel extends Disposable { this._register(model.onDidChangeInternalMetadata(e => { this._onDidChangeState.fire({ internalMetadataChanged: true, runStateChanged: e.runStateChanged }); - })); - - this._register(this._viewContext.notebookOptions.onDidChangeOptions(e => { - if (e.cellStatusBarVisibility || e.insertToolbarPosition) { + if (e.runStateChanged || e.lastRunSuccessChanged) { + // Statusbar visibility may change this.layoutChange({}); } })); @@ -160,7 +158,17 @@ export abstract class BaseCellViewModel extends Disposable { } getEditorStatusbarHeight() { - return this._viewContext.notebookOptions.computeStatusBarHeight(); + return this.statusBarIsVisible() ? this._viewContext.notebookOptions.computeStatusBarHeight() : 0; + } + + private statusBarIsVisible(): boolean { + if (this._viewContext.notebookOptions.getLayoutConfiguration().showCellStatusBar) { + return true; + } else if (this._viewContext.notebookOptions.getLayoutConfiguration().showCellStatusBarAfterExecute) { + return typeof this.internalMetadata.lastRunSuccess === 'boolean' || this.internalMetadata.runState !== undefined; + } else { + return false; + } } abstract hasDynamicHeight(): boolean; diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts index e40a99e886a..fbb26df5f1b 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel.ts @@ -126,6 +126,12 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod } })); + this._register(this.viewContext.notebookOptions.onDidChangeOptions(e => { + if (e.cellStatusBarVisibility || e.cellStatusBarAfterExecuteVisibility || e.insertToolbarPosition) { + this.layoutChange({}); + } + })); + this._outputCollection = new Array(this.model.outputs.length); this._layoutInfo = { @@ -152,6 +158,7 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod const outputShowMoreContainerHeight = state.outputShowMoreContainerHeight ? state.outputShowMoreContainerHeight : this._layoutInfo.outputShowMoreContainerHeight; let outputTotalHeight = Math.max(this._outputMinHeight, this.metadata.outputCollapsed ? notebookLayoutConfiguration.collapsedIndicatorHeight : this._outputsTop!.getTotalValue()); + const originalLayout = this.layoutInfo; if (!this.metadata.inputCollapsed) { let newState: CodeCellLayoutState; let editorHeight: number; @@ -235,10 +242,7 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod }; } - if (state.editorHeight || state.outputHeight) { - state.totalHeight = true; - } - + state.totalHeight = this.layoutInfo.totalHeight !== originalLayout.totalHeight; state.source = source; this._fireOnDidChangeLayout(state); diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts index c0b94acf079..033d499e163 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel.ts @@ -27,33 +27,25 @@ export class MarkdownCellViewModel extends BaseCellViewModel implements ICellVie return this._layoutInfo; } + private _previewHeight = 0; + set renderedMarkdownHeight(newHeight: number) { if (this.getEditState() === CellEditState.Preview) { - const newTotalHeight = newHeight + this.viewContext.notebookOptions.getLayoutConfiguration().bottomToolbarGap; // BOTTOM_CELL_TOOLBAR_GAP; - this.totalHeight = newTotalHeight; + this._previewHeight = newHeight; + this._updateTotalHeight(this._previewHeight + this.viewContext.notebookOptions.getLayoutConfiguration().bottomToolbarGap); } } - private set totalHeight(newHeight: number) { - if (newHeight !== this.layoutInfo.totalHeight) { - this.layoutChange({ totalHeight: newHeight }); - } - } - - private get totalHeight() { - throw new Error('MarkdownCellViewModel.totalHeight is write only'); - } - private _editorHeight = 0; set editorHeight(newHeight: number) { this._editorHeight = newHeight; const layoutConfiguration = this.viewContext.notebookOptions.getLayoutConfiguration(); - this.totalHeight = this._editorHeight + this._updateTotalHeight(this._editorHeight + layoutConfiguration.markdownCellTopMargin // MARKDOWN_CELL_TOP_MARGIN + layoutConfiguration.markdownCellBottomMargin // MARKDOWN_CELL_BOTTOM_MARGIN + layoutConfiguration.bottomToolbarGap // BOTTOM_CELL_TOOLBAR_GAP - + this.viewContext.notebookOptions.computeStatusBarHeight(); + + this.viewContext.notebookOptions.computeStatusBarHeight()); } get editorHeight() { @@ -116,6 +108,7 @@ export class MarkdownCellViewModel extends BaseCellViewModel implements ICellVie this._layoutInfo = { editorHeight: 0, + previewHeight: 0, fontInfo: initialNotebookLayoutInfo?.fontInfo || null, editorWidth: initialNotebookLayoutInfo?.width ? this.viewContext.notebookOptions.computeMarkdownCellEditorWidth(initialNotebookLayoutInfo.width) @@ -133,6 +126,21 @@ export class MarkdownCellViewModel extends BaseCellViewModel implements ICellVie this._onDidHideInput.fire(); } })); + + this._register(this.viewContext.notebookOptions.onDidChangeOptions(e => { + if (e.cellStatusBarVisibility || e.cellStatusBarAfterExecuteVisibility || e.insertToolbarPosition) { + const layoutConfiguration = this.viewContext.notebookOptions.getLayoutConfiguration(); + if (this.getEditState() === CellEditState.Editing) { + this._updateTotalHeight(this._editorHeight + + layoutConfiguration.markdownCellTopMargin + + layoutConfiguration.markdownCellBottomMargin + + layoutConfiguration.bottomToolbarGap + + this.viewContext.notebookOptions.computeStatusBarHeight()); + } else { + this._updateTotalHeight(this._previewHeight + this.viewContext.notebookOptions.getLayoutConfiguration().bottomToolbarGap); + } + } + })); } /** @@ -151,6 +159,12 @@ export class MarkdownCellViewModel extends BaseCellViewModel implements ICellVie this._onDidChangeState.fire({ foldingStateChanged: true }); } + private _updateTotalHeight(newHeight: number) { + if (newHeight !== this.layoutInfo.totalHeight) { + this.layoutChange({ totalHeight: newHeight }); + } + } + layoutChange(state: MarkdownCellLayoutChangeEvent) { // recompute if (!this.metadata.inputCollapsed) { @@ -158,10 +172,12 @@ export class MarkdownCellViewModel extends BaseCellViewModel implements ICellVie ? this.viewContext.notebookOptions.computeMarkdownCellEditorWidth(state.outerWidth) : this._layoutInfo.editorWidth; const totalHeight = state.totalHeight === undefined ? this._layoutInfo.totalHeight : state.totalHeight; + const previewHeight = this._previewHeight; this._layoutInfo = { fontInfo: state.font || this._layoutInfo.fontInfo, editorWidth, + previewHeight, editorHeight: this._editorHeight, bottomToolbarOffset: this.viewContext.notebookOptions.computeBottomToolbarOffset(totalHeight), totalHeight @@ -178,6 +194,7 @@ export class MarkdownCellViewModel extends BaseCellViewModel implements ICellVie fontInfo: state.font || this._layoutInfo.fontInfo, editorWidth, editorHeight: this._editorHeight, + previewHeight: this._previewHeight, bottomToolbarOffset: this.viewContext.notebookOptions.computeBottomToolbarOffset(totalHeight), totalHeight }; @@ -193,6 +210,7 @@ export class MarkdownCellViewModel extends BaseCellViewModel implements ICellVie this._layoutInfo = { fontInfo: this._layoutInfo.fontInfo, editorWidth: this._layoutInfo.editorWidth, + previewHeight: this._layoutInfo.previewHeight, bottomToolbarOffset: this._layoutInfo.bottomToolbarOffset, totalHeight: totalHeight, editorHeight: this._editorHeight diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts index b963316e2f2..17db539b562 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts @@ -59,13 +59,14 @@ export class NotebookCellTextModel extends Disposable implements ICell { set internalMetadata(newInternalMetadata: NotebookCellInternalMetadata) { const runStateChanged = this._internalMetadata.runState !== newInternalMetadata.runState; + const lastRunSuccessChanged = this._internalMetadata.lastRunSuccess !== newInternalMetadata.lastRunSuccess; newInternalMetadata = { ...newInternalMetadata, ...{ runStartTimeAdjustment: computeRunStartTimeAdjustment(this._internalMetadata, newInternalMetadata) } }; this._internalMetadata = newInternalMetadata; this._hash = null; - this._onDidChangeInternalMetadata.fire({ runStateChanged }); + this._onDidChangeInternalMetadata.fire({ runStateChanged, lastRunSuccessChanged }); } get language() { diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index 9a6803d7544..e3441192d96 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -189,6 +189,7 @@ export interface ICellOutput { export interface CellInternalMetadataChangedEvent { readonly runStateChanged?: boolean; + readonly lastRunSuccessChanged?: boolean; } export interface ICell { @@ -903,6 +904,7 @@ export const DisplayOrderKey = 'notebook.displayOrder'; export const CellToolbarLocKey = 'notebook.cellToolbarLocation'; export const CellToolbarVisibility = 'notebook.cellToolbarVisibility'; export const ShowCellStatusBarKey = 'notebook.showCellStatusBar'; +export const ShowCellStatusBarAfterExecuteKey = 'notebook.showCellStatusBarAfterExecute'; export const NotebookTextDiffEditorPreview = 'notebook.diff.enablePreview'; export const ExperimentalUseMarkdownRenderer = 'notebook.experimental.useMarkdownRenderer'; export const ExperimentalInsertToolbarAlignment = 'notebook.experimental.insertToolbarAlignment'; diff --git a/src/vs/workbench/contrib/notebook/common/notebookOptions.ts b/src/vs/workbench/contrib/notebook/common/notebookOptions.ts index 56234848c49..3b395ed6a37 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookOptions.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookOptions.ts @@ -6,7 +6,7 @@ import { Emitter } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { CellToolbarLocKey, CellToolbarVisibility, CompactView, ConsolidatedOutputButton, DragAndDropEnabled, FocusIndicator, GlobalToolbar, ExperimentalInsertToolbarAlignment, InsertToolbarPosition, ShowFoldingControls, ShowCellStatusBarKey } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellToolbarLocKey, CellToolbarVisibility, CompactView, ConsolidatedOutputButton, DragAndDropEnabled, ExperimentalInsertToolbarAlignment, FocusIndicator, GlobalToolbar, InsertToolbarPosition, ShowCellStatusBarAfterExecuteKey, ShowCellStatusBarKey, ShowFoldingControls } from 'vs/workbench/contrib/notebook/common/notebookCommon'; const SCROLLABLE_ELEMENT_PADDING_TOP = 18; @@ -32,6 +32,7 @@ export interface NotebookLayoutConfiguration { cellOutputPadding: number; codeCellLeftMargin: number; markdownCellLeftMargin: number; + markdownCellGutter: number; markdownCellTopMargin: number; markdownCellBottomMargin: number; markdownPreviewPadding: number; @@ -43,6 +44,7 @@ export interface NotebookLayoutConfiguration { editorBottomPaddingWithoutStatusBar: number; collapsedIndicatorHeight: number; showCellStatusBar: boolean; + showCellStatusBarAfterExecute: boolean; cellStatusBarHeight: number; cellToolbarLocation: string | { [key: string]: string }; cellToolbarInteraction: string; @@ -55,10 +57,12 @@ export interface NotebookLayoutConfiguration { showFoldingControls: 'always' | 'mouseover'; dragAndDropEnabled: boolean; fontSize: number; + focusIndicatorLeftMargin: number; } interface NotebookOptionsChangeEvent { cellStatusBarVisibility?: boolean; + cellStatusBarAfterExecuteVisibility?: boolean; cellToolbarLocation?: boolean; cellToolbarInteraction?: boolean; editorTopPadding?: boolean; @@ -78,15 +82,19 @@ const defaultConfigConstants = { cellRunGutter: 32, markdownCellTopMargin: 8, markdownCellBottomMargin: 8, - markdownCellLeftMargin: 32, + markdownCellLeftMargin: 0, + markdownCellGutter: 32, + focusIndicatorLeftMargin: 4 }; const compactConfigConstants = { - codeCellLeftMargin: 0, + codeCellLeftMargin: 8, cellRunGutter: 32, markdownCellTopMargin: 6, markdownCellBottomMargin: 6, - markdownCellLeftMargin: 32, + markdownCellLeftMargin: 8, + markdownCellGutter: 32, + focusIndicatorLeftMargin: 4 }; export class NotebookOptions { @@ -97,6 +105,7 @@ export class NotebookOptions { constructor(private readonly configurationService: IConfigurationService) { const showCellStatusBar = this.configurationService.getValue(ShowCellStatusBarKey); + const showCellStatusBarAfterExecute = this.configurationService.getValue(ShowCellStatusBarAfterExecuteKey); const globalToolbar = this.configurationService.getValue(GlobalToolbar) ?? false; const consolidatedOutputButton = this.configurationService.getValue(ConsolidatedOutputButton) ?? true; const dragAndDropEnabled = this.configurationService.getValue(DragAndDropEnabled) ?? true; @@ -127,6 +136,7 @@ export class NotebookOptions { editorBottomPaddingWithoutStatusBar: 12, collapsedIndicatorHeight: 24, showCellStatusBar, + showCellStatusBarAfterExecute, globalToolbar, consolidatedOutputButton, dragAndDropEnabled, @@ -154,6 +164,7 @@ export class NotebookOptions { private _updateConfiguration(e: IConfigurationChangeEvent) { const cellStatusBarVisibility = e.affectsConfiguration(ShowCellStatusBarKey); + const cellStatusBarAfterExecuteVisibility = e.affectsConfiguration(ShowCellStatusBarAfterExecuteKey); const cellToolbarLocation = e.affectsConfiguration(CellToolbarLocKey); const cellToolbarInteraction = e.affectsConfiguration(CellToolbarVisibility); const compactView = e.affectsConfiguration(CompactView); @@ -168,6 +179,7 @@ export class NotebookOptions { if ( !cellStatusBarVisibility + && !cellStatusBarAfterExecuteVisibility && !cellToolbarLocation && !cellToolbarInteraction && !compactView @@ -188,6 +200,10 @@ export class NotebookOptions { configuration.showCellStatusBar = this.configurationService.getValue(ShowCellStatusBarKey); } + if (cellStatusBarAfterExecuteVisibility) { + configuration.showCellStatusBarAfterExecute = this.configurationService.getValue(ShowCellStatusBarAfterExecuteKey); + } + if (cellToolbarLocation) { configuration.cellToolbarLocation = this.configurationService.getValue(CellToolbarLocKey); } @@ -244,6 +260,7 @@ export class NotebookOptions { // trigger event this._onDidChangeOptions.fire({ cellStatusBarVisibility, + cellStatusBarAfterExecuteVisibility, cellToolbarLocation, cellToolbarInteraction, compactView, @@ -329,11 +346,7 @@ export class NotebookOptions { } computeStatusBarHeight(): number { - if (this._layoutConfiguration.showCellStatusBar) { - return this._layoutConfiguration.cellStatusBarHeight; - } else { - return 0; - } + return this._layoutConfiguration.cellStatusBarHeight; } computeCellToolbarLocation(viewType?: string): 'right' | 'left' | 'hidden' { @@ -384,7 +397,7 @@ export class NotebookOptions { outputNodePadding: this._layoutConfiguration.cellOutputPadding, outputNodeLeftPadding: this._layoutConfiguration.cellOutputPadding, previewNodePadding: this._layoutConfiguration.markdownPreviewPadding, - markdownLeftMargin: this._layoutConfiguration.markdownCellLeftMargin, + markdownLeftMargin: this._layoutConfiguration.markdownCellGutter + this._layoutConfiguration.markdownCellLeftMargin, leftMargin: this._layoutConfiguration.codeCellLeftMargin, rightMargin: this._layoutConfiguration.cellRightMargin, runGutter: this._layoutConfiguration.cellRunGutter, diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 1657bc1f187..1b29e99765e 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -92,7 +92,25 @@ export class SettingsEditor2 extends EditorPane { private static CONFIG_SCHEMA_UPDATE_DELAYER = 500; private static readonly SUGGESTIONS: string[] = [ - `@${MODIFIED_SETTING_TAG}`, '@tag:usesOnlineServices', '@tag:sync', `@tag:${REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG}`, `@${ID_SETTING_TAG}`, `@${EXTENSION_SETTING_TAG}`, `@${FEATURE_SETTING_TAG}scm`, `@${FEATURE_SETTING_TAG}explorer`, `@${FEATURE_SETTING_TAG}search`, `@${FEATURE_SETTING_TAG}debug`, `@${FEATURE_SETTING_TAG}extensions`, `@${FEATURE_SETTING_TAG}terminal`, `@${FEATURE_SETTING_TAG}task`, `@${FEATURE_SETTING_TAG}problems`, `@${FEATURE_SETTING_TAG}output`, `@${FEATURE_SETTING_TAG}comments`, `@${FEATURE_SETTING_TAG}remote`, `@${FEATURE_SETTING_TAG}timeline` + `@${MODIFIED_SETTING_TAG}`, + '@tag:usesOnlineServices', + '@tag:sync', + `@tag:${REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG}`, + `@${ID_SETTING_TAG}`, + `@${EXTENSION_SETTING_TAG}`, + `@${FEATURE_SETTING_TAG}scm`, + `@${FEATURE_SETTING_TAG}explorer`, + `@${FEATURE_SETTING_TAG}search`, + `@${FEATURE_SETTING_TAG}debug`, + `@${FEATURE_SETTING_TAG}extensions`, + `@${FEATURE_SETTING_TAG}terminal`, + `@${FEATURE_SETTING_TAG}task`, + `@${FEATURE_SETTING_TAG}problems`, + `@${FEATURE_SETTING_TAG}output`, + `@${FEATURE_SETTING_TAG}comments`, + `@${FEATURE_SETTING_TAG}remote`, + `@${FEATURE_SETTING_TAG}timeline`, + `@${FEATURE_SETTING_TAG}notebook`, ]; private static shouldSettingUpdateFast(type: SettingValueType | SettingValueType[]): boolean { diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts index 9a29509d851..581e95611bd 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts @@ -701,7 +701,7 @@ export class SearchResultModel extends SettingsTreeModel { const isRemote = !!this.environmentService.remoteAuthority; this.root.children = this.root.children - .filter(child => child instanceof SettingsTreeSettingElement && child.matchesAllTags(this._viewState.tagFilters) && child.matchesScope(this._viewState.settingsTarget, isRemote) && child.matchesAnyExtension(this._viewState.extensionFilters) && child.matchesAnyId(this._viewState.idFilters) && (this.containsValidFeature() ? child.matchesAnyFeature(this._viewState.featureFilters) : true)); + .filter(child => child instanceof SettingsTreeSettingElement && child.matchesAllTags(this._viewState.tagFilters) && child.matchesScope(this._viewState.settingsTarget, isRemote) && child.matchesAnyExtension(this._viewState.extensionFilters) && child.matchesAnyId(this._viewState.idFilters) && child.matchesAnyFeature(this._viewState.featureFilters)); if (this.newExtensionSearchResults && this.newExtensionSearchResults.filterMatches.length) { const resultExtensionIds = this.newExtensionSearchResults.filterMatches @@ -715,22 +715,6 @@ export class SearchResultModel extends SettingsTreeModel { } } - private containsValidFeature(): boolean { - if (!this._viewState.featureFilters || !this._viewState.featureFilters.size || !tocData.children) { - return false; - } - - const features = tocData.children.find(child => child.id === 'features'); - - if (features && features.children) { - return Array.from(this._viewState.featureFilters).some(filter => { - return features.children?.find(feature => 'features/' + filter === feature.id); - }); - } else { - return false; - } - } - private getFlatSettings(): ISetting[] { const flatSettings: ISetting[] = []; arrays.coalesce(this.getUniqueResults()) diff --git a/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts b/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts index 342ce7934eb..351b49194de 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts @@ -25,7 +25,7 @@ import { isWeb } from 'vs/base/common/platform'; import { once } from 'vs/base/common/functional'; import { truncate } from 'vs/base/common/strings'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { getVirtualWorkspaceLocation } from 'vs/platform/remote/common/remoteHosts'; +import { getRemoteName, getVirtualWorkspaceLocation, getVirtualWorkspaceScheme } from 'vs/platform/remote/common/remoteHosts'; import { getCodiconAriaLabel } from 'vs/base/common/codicons'; import { ILogService } from 'vs/platform/log/common/log'; import { ReloadWindowAction } from 'vs/workbench/browser/actions/windowActions'; @@ -53,6 +53,9 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr private remoteMenuActionsGroups: ActionGroup[] | undefined; private readonly remoteAuthority = this.environmentService.remoteAuthority; + + private virtualWorkspaceScheme: string | undefined = undefined; + private connectionState: 'initializing' | 'connected' | 'reconnecting' | 'disconnected' | undefined = undefined; private readonly connectionStateContextKey = new RawContextKey<'' | 'initializing' | 'disconnected' | 'connected'>('remoteConnectionState', '').bindTo(this.contextKeyService); @@ -80,6 +83,8 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr if (this.remoteAuthority) { this.connectionState = 'initializing'; this.connectionStateContextKey.set(this.connectionState); + } else { + this.updateVirtualWorkspaceScheme(); } this.registerActions(); @@ -107,7 +112,7 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr }); // Close Remote Connection - if (RemoteStatusIndicator.SHOW_CLOSE_REMOTE_COMMAND_ID && this.remoteAuthority) { + if (RemoteStatusIndicator.SHOW_CLOSE_REMOTE_COMMAND_ID) { registerAction2(class extends Action2 { constructor() { super({ @@ -117,17 +122,18 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr f1: true }); } - run = () => that.remoteAuthority && that.hostService.openWindow({ forceReuseWindow: true, remoteAuthority: null }); - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '6_close', - command: { - id: RemoteStatusIndicator.CLOSE_REMOTE_COMMAND_ID, - title: nls.localize({ key: 'miCloseRemote', comment: ['&& denotes a mnemonic'] }, "Close Re&&mote Connection") - }, - order: 3.5 + run = () => that.hostService.openWindow({ forceReuseWindow: true, remoteAuthority: null }); }); + if (this.remoteAuthority) { + MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '6_close', + command: { + id: RemoteStatusIndicator.CLOSE_REMOTE_COMMAND_ID, + title: nls.localize({ key: 'miCloseRemote', comment: ['&& denotes a mnemonic'] }, "Close Re&&mote Connection") + }, + order: 3.5 + }); + } } if (this.extensionGalleryService.isEnabled()) { @@ -196,10 +202,17 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr })); } } else { - this._register(this.workspaceContextService.onDidChangeWorkbenchState(() => this.updateRemoteStatusIndicator())); + this._register(this.workspaceContextService.onDidChangeWorkbenchState(() => { + this.updateVirtualWorkspaceScheme(); + this.updateRemoteStatusIndicator(); + })); } } + private updateVirtualWorkspaceScheme() { + this.virtualWorkspaceScheme = getVirtualWorkspaceScheme(this.workspaceContextService.getWorkspace()); + } + private async updateWhenInstalledExtensionsRegistered(): Promise { await this.extensionService.whenInstalledExtensionsRegistered(); @@ -280,13 +293,13 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr this.renderRemoteStatusIndicator(`$(remote) ${truncate(hostLabel, RemoteStatusIndicator.REMOTE_STATUS_LABEL_MAX_LENGTH)}`, nls.localize('host.tooltip', "Editing on {0}", hostLabel)); } return; - } - - // Workspace with label: indicate editing source - const workspaceLabel = this.getWorkspaceLabel(); - if (workspaceLabel) { - this.renderRemoteStatusIndicator(`$(remote) ${truncate(workspaceLabel, RemoteStatusIndicator.REMOTE_STATUS_LABEL_MAX_LENGTH)}`, nls.localize('workspace.tooltip', "Editing on {0}", workspaceLabel)); - return; + } else if (this.virtualWorkspaceScheme) { + // Workspace with label: indicate editing source + const workspaceLabel = this.getWorkspaceLabel(); + if (workspaceLabel) { + this.renderRemoteStatusIndicator(`$(remote) ${truncate(workspaceLabel, RemoteStatusIndicator.REMOTE_STATUS_LABEL_MAX_LENGTH)}`, nls.localize('workspace.tooltip', "Editing on {0}", workspaceLabel)); + return; + } } // Remote actions: offer menu @@ -342,11 +355,37 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr return undefined; }; + const matchCurrentRemote = () => { + if (this.remoteAuthority) { + return new RegExp(`^remote_\\d\\d_${getRemoteName(this.remoteAuthority)}_`); + } else if (this.virtualWorkspaceScheme) { + if (this.virtualWorkspaceScheme === 'vscode-vfs') { + return new RegExp(`^remote_\\d\\d_vfs_`); + } else { + return new RegExp(`^virtualfs_\\d\\d_${this.virtualWorkspaceScheme}_`); + } + } + return undefined; + }; + const computeItems = () => { - const actionGroups = this.getRemoteMenuActions(true); + let actionGroups = this.getRemoteMenuActions(true); const items: (IQuickPickItem | IQuickPickSeparator)[] = []; + const currentRemoteMatcher = matchCurrentRemote(); + if (currentRemoteMatcher) { + // commands for the current remote go first + actionGroups = actionGroups.sort((g1, g2) => { + const isCurrentRemote1 = currentRemoteMatcher.test(g1[0]); + const isCurrentRemote2 = currentRemoteMatcher.test(g2[0]); + if (isCurrentRemote1 !== isCurrentRemote2) { + return isCurrentRemote1 ? -1 : 1; + } + return g1[0].localeCompare(g2[0]); + }); + } + let lastCategoryName: string | undefined = undefined; for (let actionGroup of actionGroups) { @@ -371,25 +410,38 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr } } - if (RemoteStatusIndicator.SHOW_CLOSE_REMOTE_COMMAND_ID && this.remoteAuthority) { - if (items.length) { - items.push({ type: 'separator' }); - } + if (RemoteStatusIndicator.SHOW_CLOSE_REMOTE_COMMAND_ID) { + if (this.remoteAuthority) { + if (items.length) { + items.push({ type: 'separator' }); + } - items.push({ - type: 'item', - id: RemoteStatusIndicator.CLOSE_REMOTE_COMMAND_ID, - label: nls.localize('closeRemote.title', 'Close Remote Connection') - }); - - if (this.connectionState === 'disconnected') { items.push({ type: 'item', - id: ReloadWindowAction.ID, - label: nls.localize('reloadWindow', 'Reload Window') + id: RemoteStatusIndicator.CLOSE_REMOTE_COMMAND_ID, + label: nls.localize('closeRemoteConnection.title', 'Close Remote Connection') + }); + + if (this.connectionState === 'disconnected') { + items.push({ + type: 'item', + id: ReloadWindowAction.ID, + label: nls.localize('reloadWindow', 'Reload Window') + }); + } + } else if (this.virtualWorkspaceScheme) { + if (items.length) { + items.push({ type: 'separator' }); + } + + items.push({ + type: 'item', + id: RemoteStatusIndicator.CLOSE_REMOTE_COMMAND_ID, + label: nls.localize('closeRemoteWindow.title', 'Close Remote') }); } } + if (!this.remoteAuthority && this.extensionGalleryService.isEnabled()) { items.push({ type: 'separator' diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index 00c2d9bbc06..d0fb01b93d7 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -17,6 +17,7 @@ import type { WebglAddon as XTermWebglAddon } from 'xterm-addon-webgl'; import { ITerminalStatusList } from 'vs/workbench/contrib/terminal/browser/terminalStatusList'; import { ICompleteTerminalConfiguration } from 'vs/workbench/contrib/terminal/common/remoteTerminalChannel'; import { Orientation } from 'vs/base/browser/ui/splitview/splitview'; +import { IEditableData } from 'vs/workbench/common/views'; export const ITerminalService = createDecorator('terminalService'); export const ITerminalInstanceService = createDecorator('terminalInstanceService'); @@ -206,6 +207,8 @@ export interface ITerminalService { requestStartExtensionTerminal(proxy: ITerminalProcessExtHostProxy, cols: number, rows: number): Promise; isAttachedToTerminal(remoteTerm: IRemoteTerminalAttachTarget): boolean; + getEditableData(stat: ITerminalInstance): IEditableData | undefined; + setEditable(stat: ITerminalInstance, data: IEditableData | null): Promise; } export interface IRemoteTerminalService extends IOffProcessTerminalService { @@ -600,12 +603,13 @@ export interface ITerminalInstance { registerLinkProvider(provider: ITerminalExternalLinkProvider): IDisposable; /** - * Triggers a quick pick to rename this terminal. + * Sets the terminal name to the provided title or triggers a quick pick + * to take user input. */ - rename(): Promise; + rename(title?: string): Promise; /** - * Triggers a quick pick to rename this terminal. + * Triggers a quick pick to change the icon of this terminal. */ changeIcon(): Promise; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index af542818ef9..dac068f76df 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -23,7 +23,7 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ILabelService } from 'vs/platform/label/common/label'; import { IListService } from 'vs/platform/list/browser/listService'; -import { INotificationService } from 'vs/platform/notification/common/notification'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IPickOptions, IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { ILocalTerminalService, ITerminalProfile, TerminalSettingId, TitleEventSource } from 'vs/platform/terminal/common/terminal'; @@ -811,9 +811,30 @@ export function registerTerminalActions() { }); } async run(accessor: ServicesAccessor) { - return getSelectedInstances(accessor)?.[0].rename(); + const terminalService = accessor.get(ITerminalService); + const notificationService = accessor.get(INotificationService); + + const instance = getSelectedInstances(accessor)?.[0]; + if (!instance) { + return; + } + + await terminalService.setEditable(instance, { + validationMessage: value => validateTerminalName(value), + onFinish: async (value, success) => { + if (success) { + try { + await instance.rename(value); + } catch (e) { + notificationService.error(e); + } + } + await terminalService.setEditable(instance, null); + } + }); } }); + registerAction2(class extends Action2 { constructor() { super({ @@ -1830,3 +1851,14 @@ function focusNext(accessor: ServicesAccessor): void { const listService = accessor.get(IListService); listService.lastFocusedList?.focusNext(); } + +export function validateTerminalName(name: string): { content: string, severity: Severity } | null { + if (!name || name.trim().length === 0) { + return { + content: localize('emptyTerminalNameError', "A name must be provided."), + severity: Severity.Error + }; + } + + return null; +} diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index b9f8ed26f93..dbcd7c31b7e 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -775,7 +775,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { private _initDragAndDrop(container: HTMLElement) { this._dndObserver?.dispose(); - const dndController = new TerminalInstanceDropAndDropController(container); + const dndController = new TerminalInstanceDragAndDropController(container); dndController.onDropTerminal(e => this._onRequestAddInstanceToGroup.fire(e)); dndController.onDropFile(async path => { const preparedPath = await this._terminalInstanceService.preparePathForTerminalAsync(path, this.shellLaunchConfig.executable, this.title, this.shellType, this.isRemote); @@ -1789,13 +1789,15 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { return this._linkManager.registerExternalLinkProvider(this, provider); } - async rename() { - const name = await this._quickInputService.input({ - value: this.title, - prompt: nls.localize('workbench.action.terminal.rename.prompt', "Enter terminal name"), - }); - if (name) { - this.setTitle(name, TitleEventSource.Api); + async rename(title?: string) { + if (!title) { + title = await this._quickInputService.input({ + value: this.title, + prompt: nls.localize('workbench.action.terminal.rename.prompt', "Enter terminal name"), + }); + } + if (title) { + this.setTitle(title, TitleEventSource.Api); } } @@ -1843,7 +1845,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } } items.push({ type: 'separator' }); - const showAllColorsItem = { label: 'Show all colors' }; + const showAllColorsItem = { label: 'Reset to default' }; items.push(showAllColorsItem); styleElement.textContent = css; document.body.appendChild(styleElement); @@ -1870,7 +1872,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } } -class TerminalInstanceDropAndDropController extends Disposable implements IDragAndDropObserverCallbacks { +class TerminalInstanceDragAndDropController extends Disposable implements IDragAndDropObserverCallbacks { private _dropOverlay?: HTMLElement; private readonly _onDropFile = new Emitter(); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index c724ea70dcb..ce934e8dc6b 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -19,7 +19,7 @@ import { IKeyMods, IPickOptions, IQuickInputButton, IQuickInputService, IQuickPi import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ILocalTerminalService, IOffProcessTerminalService, IShellLaunchConfig, ITerminalLaunchError, ITerminalProfile, ITerminalProfileObject, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; -import { IViewDescriptorService, IViewsService, ViewContainerLocation } from 'vs/workbench/common/views'; +import { IEditableData, IViewDescriptorService, IViewsService, ViewContainerLocation } from 'vs/workbench/common/views'; import { IRemoteTerminalService, ITerminalExternalLinkProvider, ITerminalInstance, ITerminalService, ITerminalGroup, TerminalConnectionState } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { TerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminalInstance'; @@ -69,6 +69,8 @@ export class TerminalService implements ITerminalService { private _localTerminalsInitPromise: Promise | undefined; private _connectionState: TerminalConnectionState; + private _editable: { instance: ITerminalInstance, data: IEditableData } | undefined; + public get activeGroupIndex(): number { return this._activeGroupIndex; } public get terminalGroups(): ITerminalGroup[] { return this._terminalGroups; } public get isProcessSupportRegistered(): boolean { return !!this._processSupportContextKey.get(); } @@ -316,6 +318,25 @@ export class TerminalService implements ITerminalService { return activeInstance ? activeInstance : this.createTerminal(undefined); } + async setEditable(instance: ITerminalInstance, data?: IEditableData | null): Promise { + if (!data) { + this._editable = undefined; + } else { + this._editable = { instance: instance, data }; + } + const pane = this._viewsService.getActiveViewWithId(TERMINAL_VIEW_ID); + const isEditing = this._isEditable(instance); + pane?.terminalTabbedView?.setEditable(isEditing); + } + + private _isEditable(instance: ITerminalInstance | undefined): boolean { + return !!this._editable && (this._editable.instance === instance || !instance); + } + + getEditableData(instance: ITerminalInstance): IEditableData | undefined { + return this._editable && this._editable.instance === instance ? this._editable.data : undefined; + } + requestStartExtensionTerminal(proxy: ITerminalProcessExtHostProxy, cols: number, rows: number): Promise { // The initial request came from the extension host, no need to wait for it return new Promise(callback => { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts index 04fb6e4721a..69a0e7f6d62 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts @@ -288,7 +288,7 @@ export class TerminalTabbedView extends Disposable { const hasText = this._tabListElement.clientWidth > TerminalTabsListSizes.MidpointViewWidth; this._tabContainer.classList.toggle('has-text', hasText); this._terminalIsTabsNarrowContextKey.set(!hasText); - this._tabList.render(); + this._tabList.refresh(); } private _addSashListener() { @@ -469,6 +469,13 @@ export class TerminalTabbedView extends Disposable { ]; } + setEditable(isEditing: boolean): void { + if (!isEditing) { + this._tabList.domFocus(); + } + return this._tabList.refresh(); + } + focusTabs(): void { if (!this._shouldShowTabs()) { return; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts index 396ae88f448..6b9f9e5e683 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts @@ -26,7 +26,7 @@ import { DEFAULT_LABELS_CONTAINER, IResourceLabel, ResourceLabels } from 'vs/wor import { IDecorationsService } from 'vs/workbench/services/decorations/browser/decorations'; import { IHoverAction, IHoverService } from 'vs/workbench/services/hover/browser/hover'; import Severity from 'vs/base/common/severity'; -import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { IListDragAndDrop, IListDragOverReaction, IListRenderer, ListDragOverEffect } from 'vs/base/browser/ui/list/list'; import { DataTransfers, IDragAndDropData } from 'vs/base/browser/dnd'; import { disposableTimeout } from 'vs/base/common/async'; @@ -34,6 +34,13 @@ import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; import { URI } from 'vs/base/common/uri'; import { getColorClass, getIconId, getUriClasses } from 'vs/workbench/contrib/terminal/browser/terminalIcon'; import { Schemas } from 'vs/base/common/network'; +import { IEditableData } from 'vs/workbench/common/views'; +import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; +import { once } from 'vs/base/common/functional'; +import { attachInputBoxStyler } from 'vs/platform/theme/common/styler'; +import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { KeyCode } from 'vs/base/common/keyCodes'; const $ = DOM.$; @@ -89,13 +96,13 @@ export class TerminalTabList extends WorkbenchList { configurationService, keybindingService, ); - this._terminalService.onInstancesChanged(() => this.render()); - this._terminalService.onGroupsChanged(() => this.render()); - this._terminalService.onInstanceTitleChanged(() => this.render()); - this._terminalService.onInstanceIconChanged(() => this.render()); - this._terminalService.onInstancePrimaryStatusChanged(() => this.render()); - this._terminalService.onDidChangeConnectionState(() => this.render()); - this._themeService.onDidColorThemeChange(() => this.render()); + this._terminalService.onInstancesChanged(() => this.refresh()); + this._terminalService.onGroupsChanged(() => this.refresh()); + this._terminalService.onInstanceTitleChanged(() => this.refresh()); + this._terminalService.onInstanceIconChanged(() => this.refresh()); + this._terminalService.onInstancePrimaryStatusChanged(() => this.refresh()); + this._terminalService.onDidChangeConnectionState(() => this.refresh()); + this._themeService.onDidColorThemeChange(() => this.refresh()); this._terminalService.onActiveInstanceChanged(e => { if (e) { const i = this._terminalService.terminalInstances.indexOf(e); @@ -157,10 +164,10 @@ export class TerminalTabList extends WorkbenchList { this._decorationsProvider = instantiationService.createInstance(TerminalDecorationsProvider); _decorationsService.registerDecorationsProvider(this._decorationsProvider); } - this.render(); + this.refresh(); } - render(): void { + refresh(): void { this.splice(0, this.length, this._terminalService.terminalInstances); } @@ -182,7 +189,8 @@ class TerminalTabsRenderer implements IListRenderer 1) { const terminalIndex = group.terminalInstances.indexOf(instance); @@ -323,6 +341,84 @@ class TerminalTabsRenderer implements IListRenderer { + const message = editableData.validationMessage(value); + if (!message || message.severity !== Severity.Error) { + return null; + } + + return { + content: message.content, + formatContent: true, + type: MessageType.ERROR + }; + } + }, + ariaLabel: localize('terminalInputAriaLabel', "Type terminal name. Press Enter to confirm or Escape to cancel.") + }); + const styler = attachInputBoxStyler(inputBox, this._themeService); + + inputBox.value = value; + inputBox.focus(); + inputBox.select({ start: 0, end: value.length }); + + const done = once((success: boolean, finishEditing: boolean) => { + inputBox.element.style.display = 'none'; + const value = inputBox.value; + dispose(toDispose); + inputBox.element.remove(); + if (finishEditing) { + editableData.onFinish(value, success); + } + }); + + const showInputBoxNotification = () => { + if (inputBox.isInputValid()) { + const message = editableData.validationMessage(inputBox.value); + if (message) { + inputBox.showMessage({ + content: message.content, + formatContent: true, + type: message.severity === Severity.Info ? MessageType.INFO : message.severity === Severity.Warning ? MessageType.WARNING : MessageType.ERROR + }); + } else { + inputBox.hideMessage(); + } + } + }; + showInputBoxNotification(); + + const toDispose = [ + inputBox, + DOM.addStandardDisposableListener(inputBox.inputElement, DOM.EventType.KEY_DOWN, (e: IKeyboardEvent) => { + if (e.equals(KeyCode.Enter)) { + done(inputBox.isInputValid(), true); + } else if (e.equals(KeyCode.Escape)) { + done(false, true); + } + }), + DOM.addStandardDisposableListener(inputBox.inputElement, DOM.EventType.KEY_UP, (e: IKeyboardEvent) => { + showInputBoxNotification(); + }), + DOM.addDisposableListener(inputBox.inputElement, DOM.EventType.BLUR, () => { + done(inputBox.isInputValid(), true); + }), + label, + styler + ]; + + return toDisposable(() => { + done(false, false); + }); + } + disposeElement(instance: ITerminalInstance, index: number, templateData: ITerminalTabEntryTemplate): void { templateData.elementDispoables?.dispose(); templateData.elementDispoables = undefined; diff --git a/src/vs/workbench/contrib/terminal/test/node/terminalProfiles.test.ts b/src/vs/workbench/contrib/terminal/test/node/terminalProfiles.test.ts index 5e521205775..b16844d7758 100644 --- a/src/vs/workbench/contrib/terminal/test/node/terminalProfiles.test.ts +++ b/src/vs/workbench/contrib/terminal/test/node/terminalProfiles.test.ts @@ -115,36 +115,36 @@ suite('Workbench - TerminalProfiles', () => { } as ITestTerminalConfig) as ITerminalConfiguration; test('should prefer pwsh 7 to Windows PowerShell', async () => { - const fsProvider = createFsProvider([ + const expectedPaths = [ 'C:\\Program Files\\PowerShell\\7\\pwsh.exe', 'C:\\Sysnative\\WindowsPowerShell\\v1.0\\powershell.exe', 'C:\\System32\\WindowsPowerShell\\v1.0\\powershell.exe' - ]); - const profiles = await detectAvailableProfiles(false, buildTestSafeConfigProvider(pwshSourceConfig), fsProvider, undefined, undefined, undefined); + ]; + const profiles = await detectAvailableProfiles(false, buildTestSafeConfigProvider(pwshSourceConfig), undefined, undefined, undefined, expectedPaths); const expected = [ { profileName: 'PowerShell', path: 'C:\\Program Files\\PowerShell\\7\\pwsh.exe', isDefault: true } ]; profilesEqual(profiles, expected); }); test('should prefer pwsh 7 to pwsh 6', async () => { - const fsProvider = createFsProvider([ + const expectedPaths = [ 'C:\\Program Files\\PowerShell\\7\\pwsh.exe', 'C:\\Program Files\\PowerShell\\6\\pwsh.exe', 'C:\\Sysnative\\WindowsPowerShell\\v1.0\\powershell.exe', 'C:\\System32\\WindowsPowerShell\\v1.0\\powershell.exe' - ]); - const profiles = await detectAvailableProfiles(false, buildTestSafeConfigProvider(pwshSourceConfig), fsProvider, undefined, undefined, undefined); + ]; + const profiles = await detectAvailableProfiles(false, buildTestSafeConfigProvider(pwshSourceConfig), undefined, undefined, undefined, expectedPaths); const expected = [ { profileName: 'PowerShell', path: 'C:\\Program Files\\PowerShell\\7\\pwsh.exe', isDefault: true } ]; profilesEqual(profiles, expected); }); - test.skip('should fallback to Windows PowerShell', async () => { - const fsProvider = createFsProvider([ + test('should fallback to Windows PowerShell', async () => { + const expectedPaths = [ 'C:\\Windows\\Sysnative\\WindowsPowerShell\\v1.0\\powershell.exe', 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe' - ]); - const profiles = await detectAvailableProfiles(false, buildTestSafeConfigProvider(pwshSourceConfig), fsProvider, undefined, undefined, undefined); + ]; + const profiles = await detectAvailableProfiles(false, buildTestSafeConfigProvider(pwshSourceConfig), undefined, undefined, undefined, expectedPaths); strictEqual(profiles.length, 1); strictEqual(profiles[0].profileName, 'PowerShell'); }); @@ -228,7 +228,7 @@ suite('Workbench - TerminalProfiles', () => { }, async readFile(path: string): Promise { if (path !== '/etc/shells') { - fail('Unexected path'); + fail('Unexepected path'); } return Buffer.from(etcShellsContent); } diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css index d252616bc61..0600e81458a 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.css @@ -84,7 +84,7 @@ } .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideCategories { - padding: 12px; + padding: 24px; } .monaco-workbench .part.editor > .content .gettingStartedContainer.animationReady .gettingStartedSlide { @@ -557,14 +557,11 @@ } .monaco-workbench .part.editor > .content .gettingStartedContainer .button-link .codicon-arrow-small-right { - position: relative; - top: 3px; - left: 2px; + padding-left: 8px; } .monaco-workbench .part.editor > .content .gettingStartedContainer .button-link .codicon-check-all { - position: relative; - top: 3px; + padding-right: 8px; } .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlide .skip { @@ -606,6 +603,11 @@ display: none; } +.monaco-workbench .part.editor > .content .gettingStartedContainer .done-next-container { + display: flex; + padding: 8px 16px 16px; +} + .monaco-workbench .part.editor > .content .gettingStartedContainer .button-link { padding: 0; background: transparent; @@ -616,9 +618,13 @@ max-width: 100%; } +.monaco-workbench .part.editor > .content .gettingStartedContainer .done-next-container .button-link { + display: flex; + align-items: center; +} + .monaco-workbench .part.editor > .content .gettingStartedContainer .button-link.next { - position: absolute; - right: 0; + margin-left: auto; } .monaco-workbench .part.editor > .content .gettingStartedContainer .button-link:hover { @@ -645,7 +651,7 @@ } .monaco-workbench .part.editor > .content .gettingStartedContainer .getting-started-category .codicon { - top: 0; + top: 3px; } .monaco-workbench .part.editor > .content .getting-started-category .codicon::before{ diff --git a/src/vs/workbench/services/workspaces/common/workspaceTrust.ts b/src/vs/workbench/services/workspaces/common/workspaceTrust.ts index 00ece8fe9b5..8652288abae 100644 --- a/src/vs/workbench/services/workspaces/common/workspaceTrust.ts +++ b/src/vs/workbench/services/workspaces/common/workspaceTrust.ts @@ -202,6 +202,11 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork } getUriTrustInfo(uri: URI): IWorkspaceTrustUriInfo { + // Return trusted when workspace trust is disabled + if (!isWorkspaceTrustEnabled(this.configurationService)) { + return { trusted: true, uri }; + } + let resultState = false; let maxLength = -1; diff --git a/src/vs/workbench/test/browser/api/extHostTypeConverter.test.ts b/src/vs/workbench/test/browser/api/extHostTypeConverter.test.ts index 0cc72cdd1bc..324cdd3c071 100644 --- a/src/vs/workbench/test/browser/api/extHostTypeConverter.test.ts +++ b/src/vs/workbench/test/browser/api/extHostTypeConverter.test.ts @@ -5,7 +5,8 @@ import * as assert from 'assert'; -import { MarkdownString } from 'vs/workbench/api/common/extHostTypeConverters'; +import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; +import { MarkdownString, NotebookCellOutputItem } from 'vs/workbench/api/common/extHostTypeConverters'; import { isEmptyObject } from 'vs/base/common/types'; import { forEach } from 'vs/base/common/collections'; import { LogLevel as _MainLogLevel } from 'vs/platform/log/common/log'; @@ -81,4 +82,23 @@ suite('ExtHostTypeConverter', function () { } }); }); + + test('NotebookCellOutputItem', function () { + + const item = extHostTypes.NotebookCellOutputItem.text('Hello', 'foo/bar'); + + const dto = NotebookCellOutputItem.from(item); + + assert.strictEqual(dto.mime, 'foo/bar'); + assert.strictEqual(dto.metadata, undefined); + assert.strictEqual(dto.value, undefined); + assert.deepStrictEqual(dto.valueBytes, Array.from(new TextEncoder().encode('Hello'))); + + const item2 = NotebookCellOutputItem.to(dto); + + assert.strictEqual(item2.mime, item.mime); + assert.strictEqual(item2.metadata, item.metadata); + assert.strictEqual(item2.value, item.value); + assert.deepStrictEqual(item2.data, item.data); + }); }); diff --git a/src/vs/workbench/test/browser/api/extHostTypes.test.ts b/src/vs/workbench/test/browser/api/extHostTypes.test.ts index 353096359eb..ac2117b6745 100644 --- a/src/vs/workbench/test/browser/api/extHostTypes.test.ts +++ b/src/vs/workbench/test/browser/api/extHostTypes.test.ts @@ -696,28 +696,28 @@ suite('ExtHostTypes', function () { item = types.NotebookCellOutputItem.json(1); assert.strictEqual(item.mime, 'application/json'); - assert.deepStrictEqual(item.value, new TextEncoder().encode(JSON.stringify(1))); + assert.deepStrictEqual(item.data, new TextEncoder().encode(JSON.stringify(1))); item = types.NotebookCellOutputItem.json(1, 'foo'); assert.strictEqual(item.mime, 'foo'); - assert.deepStrictEqual(item.value, new TextEncoder().encode(JSON.stringify(1))); + assert.deepStrictEqual(item.data, new TextEncoder().encode(JSON.stringify(1))); item = types.NotebookCellOutputItem.json(true); assert.strictEqual(item.mime, 'application/json'); - assert.deepStrictEqual(item.value, new TextEncoder().encode(JSON.stringify(true))); + assert.deepStrictEqual(item.data, new TextEncoder().encode(JSON.stringify(true))); item = types.NotebookCellOutputItem.json([true, 1, 'ddd']); assert.strictEqual(item.mime, 'application/json'); - assert.deepStrictEqual(item.value, new TextEncoder().encode(JSON.stringify([true, 1, 'ddd'], undefined, '\t'))); + assert.deepStrictEqual(item.data, new TextEncoder().encode(JSON.stringify([true, 1, 'ddd'], undefined, '\t'))); // --- text item = types.NotebookCellOutputItem.text('Hęłlö'); assert.strictEqual(item.mime, 'text/plain'); - assert.deepStrictEqual(item.value, new TextEncoder().encode('Hęłlö')); + assert.deepStrictEqual(item.data, new TextEncoder().encode('Hęłlö')); item = types.NotebookCellOutputItem.text('Hęłlö', 'foo/bar'); assert.strictEqual(item.mime, 'foo/bar'); - assert.deepStrictEqual(item.value, new TextEncoder().encode('Hęłlö')); + assert.deepStrictEqual(item.data, new TextEncoder().encode('Hęłlö')); }); });