Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/microsoft/vscode.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSandeep Somavarapu <sasomava@microsoft.com>2022-09-13 21:35:14 +0300
committerSandeep Somavarapu <sasomava@microsoft.com>2022-09-13 21:35:14 +0300
commitbb54b84573a141744e3ab51db06f59b4757fc137 (patch)
tree3fe9212324c746d3d9f2bec19d50784eb1bf9032
parentd0d5cbd82f4944741a40ff2784dc86f296825a23 (diff)
parent28e52a46fe8df0c924c881e438e124c05f171b9c (diff)
Merge branch 'main' into sandy081/profilesStorageServicesandy081/profilesStorageService
-rw-r--r--.github/commands.json15
-rw-r--r--extensions/css-language-features/server/src/utils/documentContext.ts5
-rw-r--r--extensions/html-language-features/server/src/node/nodeFs.ts4
-rw-r--r--extensions/html-language-features/server/src/requests.ts78
-rw-r--r--extensions/html-language-features/server/src/utils/documentContext.ts7
-rw-r--r--extensions/ipynb/src/serializers.ts27
-rw-r--r--extensions/ipynb/src/streamCompressor.ts63
-rw-r--r--extensions/r/language-configuration.json5
-rw-r--r--extensions/typescript-language-features/src/languageFeatures/refactor.ts2
-rw-r--r--extensions/vscode-api-tests/src/singlefolder-tests/notebook.api.test.ts15
-rw-r--r--extensions/vscode-api-tests/src/singlefolder-tests/notebook.document.test.ts58
-rw-r--r--extensions/vscode-api-tests/src/singlefolder-tests/notebook.kernel.test.ts12
-rw-r--r--extensions/vscode-api-tests/src/singlefolder-tests/types.test.ts1
-rw-r--r--extensions/vscode-notebook-tests/src/extension.ts12
-rw-r--r--package.json14
-rw-r--r--remote/package.json14
-rw-r--r--remote/web/package.json8
-rw-r--r--remote/web/yarn.lock32
-rw-r--r--remote/yarn.lock56
-rw-r--r--src/main.js3
-rw-r--r--src/vs/base/browser/ui/iconLabel/iconLabels.ts4
-rw-r--r--src/vs/base/common/event.ts26
-rw-r--r--src/vs/base/common/platform.ts3
-rw-r--r--src/vs/base/node/languagePacks.js7
-rw-r--r--src/vs/base/parts/ipc/test/node/ipc.cp.integrationTest.ts13
-rw-r--r--src/vs/code/electron-main/app.ts70
-rw-r--r--src/vs/code/electron-main/main.ts5
-rw-r--r--src/vs/code/node/cli.ts4
-rw-r--r--src/vs/editor/browser/controller/textAreaHandler.ts12
-rw-r--r--src/vs/editor/browser/controller/textAreaState.ts1
-rw-r--r--src/vs/editor/common/model.ts4
-rw-r--r--src/vs/editor/common/model/textModel.ts4
-rw-r--r--src/vs/editor/common/model/tokenizationTextModelPart.ts5
-rw-r--r--src/vs/editor/common/services/model.ts2
-rw-r--r--src/vs/editor/common/services/modelService.ts10
-rw-r--r--src/vs/editor/common/textModelEvents.ts5
-rw-r--r--src/vs/editor/common/tokenizationTextModelPart.ts2
-rw-r--r--src/vs/editor/contrib/codeAction/browser/codeActionKeybindingResolver.ts90
-rw-r--r--src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts297
-rw-r--r--src/vs/editor/contrib/codeAction/browser/codeActionUi.ts4
-rw-r--r--src/vs/editor/contrib/codeAction/browser/media/action.css46
-rw-r--r--src/vs/editor/contrib/codeAction/test/browser/codeActionKeybindingResolver.test.ts6
-rw-r--r--src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts18
-rw-r--r--src/vs/editor/test/browser/controller/imeTester.ts4
-rw-r--r--src/vs/monaco.d.ts4
-rw-r--r--src/vs/platform/backup/electron-main/backup.ts3
-rw-r--r--src/vs/platform/backup/electron-main/backupMainService.ts33
-rw-r--r--src/vs/platform/backup/test/electron-main/backupMainService.test.ts8
-rw-r--r--src/vs/platform/contextkey/common/contextkeys.ts3
-rw-r--r--src/vs/platform/credentials/common/credentialsMainService.ts132
-rw-r--r--src/vs/platform/debug/electron-main/extensionHostDebugIpc.ts8
-rw-r--r--src/vs/platform/diagnostics/electron-main/diagnosticsMainService.ts64
-rw-r--r--src/vs/platform/environment/node/wait.ts2
-rw-r--r--src/vs/platform/instantiation/common/instantiationService.ts58
-rw-r--r--src/vs/platform/instantiation/test/common/instantiationService.test.ts66
-rw-r--r--src/vs/platform/instantiation/test/common/instantiationServiceMock.ts2
-rw-r--r--src/vs/platform/launch/electron-main/launchMainService.ts19
-rw-r--r--src/vs/platform/menubar/electron-main/menubar.ts8
-rw-r--r--src/vs/platform/native/electron-main/nativeHostMainService.ts18
-rw-r--r--src/vs/platform/product/common/product.ts2
-rw-r--r--src/vs/platform/terminal/common/terminal.ts2
-rw-r--r--src/vs/platform/terminal/node/ptyHostService.ts4
-rw-r--r--src/vs/platform/terminal/node/ptyService.ts10
-rw-r--r--src/vs/platform/userDataProfile/common/userDataProfile.ts37
-rw-r--r--src/vs/platform/userDataProfile/electron-main/userDataProfile.ts2
-rw-r--r--src/vs/platform/userDataProfile/electron-sandbox/userDataProfile.ts13
-rw-r--r--src/vs/platform/userDataProfile/test/common/userDataProfileService.test.ts67
-rw-r--r--src/vs/platform/userDataProfile/test/electron-main/userDataProfileMainService.test.ts4
-rw-r--r--src/vs/platform/userDataSync/common/abstractSynchronizer.ts8
-rw-r--r--src/vs/platform/userDataSync/common/globalStateSync.ts4
-rw-r--r--src/vs/platform/userDataSync/common/userDataSync.ts29
-rw-r--r--src/vs/platform/userDataSync/common/userDataSyncMachines.ts4
-rw-r--r--src/vs/platform/userDataSync/common/userDataSyncStoreService.ts118
-rw-r--r--src/vs/platform/userDataSync/test/common/userDataSyncClient.ts5
-rw-r--r--src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts1
-rw-r--r--src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts34
-rw-r--r--src/vs/platform/windows/electron-main/windowImpl.ts8
-rw-r--r--src/vs/platform/windows/electron-main/windows.ts7
-rw-r--r--src/vs/platform/windows/electron-main/windowsFinder.ts4
-rw-r--r--src/vs/platform/windows/electron-main/windowsMainService.ts160
-rw-r--r--src/vs/platform/windows/test/electron-main/windowsFinder.test.ts24
-rw-r--r--src/vs/platform/workspaces/electron-main/workspacesManagementMainService.ts103
-rw-r--r--src/vs/platform/workspaces/node/workspaces.ts18
-rw-r--r--src/vs/platform/workspaces/test/electron-main/workspaces.test.ts2
-rw-r--r--src/vs/platform/workspaces/test/electron-main/workspacesManagementMainService.test.ts122
-rw-r--r--src/vs/server/node/server.cli.ts4
-rw-r--r--src/vs/workbench/api/browser/mainThreadNotebook.ts26
-rw-r--r--src/vs/workbench/api/browser/mainThreadNotebookKernels.ts4
-rw-r--r--src/vs/workbench/api/browser/mainThreadSecretState.ts4
-rw-r--r--src/vs/workbench/api/browser/mainThreadTreeViews.ts2
-rw-r--r--src/vs/workbench/api/common/extHost.api.impl.ts2
-rw-r--r--src/vs/workbench/api/common/extHost.protocol.ts4
-rw-r--r--src/vs/workbench/api/common/extHostBulkEdits.ts3
-rw-r--r--src/vs/workbench/api/common/extHostFileSystemEventService.ts5
-rw-r--r--src/vs/workbench/api/common/extHostLanguageFeatures.ts8
-rw-r--r--src/vs/workbench/api/common/extHostNotebook.ts42
-rw-r--r--src/vs/workbench/api/common/extHostNotebookDocument.ts11
-rw-r--r--src/vs/workbench/api/common/extHostTerminalService.ts6
-rw-r--r--src/vs/workbench/api/common/extHostTreeViews.ts23
-rw-r--r--src/vs/workbench/api/common/extHostTypeConverters.ts7
-rw-r--r--src/vs/workbench/api/common/extHostTypes.ts14
-rw-r--r--src/vs/workbench/api/test/browser/extHostNotebook.test.ts9
-rw-r--r--src/vs/workbench/api/test/browser/extHostNotebookKernel.test.ts9
-rw-r--r--src/vs/workbench/browser/checkbox.ts25
-rw-r--r--src/vs/workbench/browser/parts/editor/editorActions.ts7
-rw-r--r--src/vs/workbench/browser/parts/editor/editorGroupView.ts7
-rw-r--r--src/vs/workbench/browser/parts/editor/editorStatus.ts6
-rw-r--r--src/vs/workbench/browser/parts/views/media/views.css2
-rw-r--r--src/vs/workbench/browser/parts/views/treeView.ts12
-rw-r--r--src/vs/workbench/common/editor/textEditorModel.ts25
-rw-r--r--src/vs/workbench/common/editor/textResourceEditorInput.ts4
-rw-r--r--src/vs/workbench/common/views.ts7
-rw-r--r--src/vs/workbench/contrib/debug/browser/repl.ts95
-rw-r--r--src/vs/workbench/contrib/debug/browser/replViewer.ts14
-rw-r--r--src/vs/workbench/contrib/debug/common/debug.ts14
-rw-r--r--src/vs/workbench/contrib/editSessions/browser/editSessionsStorageService.ts6
-rw-r--r--src/vs/workbench/contrib/extensions/browser/extensionEditor.ts12
-rw-r--r--src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts42
-rw-r--r--src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts42
-rw-r--r--src/vs/workbench/contrib/extensions/browser/extensionsViews.ts110
-rw-r--r--src/vs/workbench/contrib/extensions/common/extensionQuery.ts4
-rw-r--r--src/vs/workbench/contrib/extensions/common/extensions.ts2
-rw-r--r--src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts38
-rw-r--r--src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts4
-rw-r--r--src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts24
-rw-r--r--src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts4
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts4
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts10
-rw-r--r--src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts72
-rw-r--r--src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts11
-rw-r--r--src/vs/workbench/contrib/notebook/browser/view/renderers/stdOutErrorPreProcessor.ts56
-rw-r--r--src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts8
-rw-r--r--src/vs/workbench/contrib/notebook/common/notebookCommon.ts8
-rw-r--r--src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts4
-rw-r--r--src/vs/workbench/contrib/notebook/common/notebookKernelService.ts1
-rw-r--r--src/vs/workbench/contrib/notebook/common/notebookService.ts2
-rw-r--r--src/vs/workbench/contrib/performance/browser/performance.contribution.ts73
-rw-r--r--src/vs/workbench/contrib/snippets/browser/commands/fileTemplateSnippets.ts2
-rw-r--r--src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts66
-rw-r--r--src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts45
-rw-r--r--src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts6
-rw-r--r--src/vs/workbench/contrib/tasks/electron-sandbox/taskService.ts3
-rw-r--r--src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts4
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminal.ts6
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalInstance.ts8
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalService.ts27
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalView.ts4
-rw-r--r--src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts4
-rw-r--r--src/vs/workbench/contrib/terminal/common/terminal.ts2
-rw-r--r--src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts4
-rw-r--r--src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts4
-rw-r--r--src/vs/workbench/services/configuration/test/browser/configurationService.test.ts4
-rw-r--r--src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts2
-rw-r--r--src/vs/workbench/services/extensions/common/extensionsApiProposals.ts1
-rw-r--r--src/vs/workbench/services/languageDetection/common/languageDetectionWorkerService.ts2
-rw-r--r--src/vs/workbench/services/search/common/ignoreFile.ts8
-rw-r--r--src/vs/workbench/services/textfile/common/textFileEditorModel.ts9
-rw-r--r--src/vs/workbench/services/textfile/common/textfiles.ts2
-rw-r--r--src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts4
-rw-r--r--src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts14
-rw-r--r--src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts50
-rw-r--r--src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts2
-rw-r--r--src/vs/workbench/test/browser/workbenchTestServices.ts4
-rw-r--r--src/vscode-dts/vscode.d.ts89
-rw-r--r--src/vscode-dts/vscode.proposed.notebookContentProvider.d.ts36
-rw-r--r--src/vscode-dts/vscode.proposed.notebookLiveShare.d.ts6
-rw-r--r--src/vscode-dts/vscode.proposed.snippetWorkspaceEdit.d.ts85
-rw-r--r--src/vscode-dts/vscode.proposed.treeItemCheckbox.d.ts2
-rw-r--r--yarn.lock56
169 files changed, 2076 insertions, 1716 deletions
diff --git a/.github/commands.json b/.github/commands.json
index c429bff29f2..5ade58ab38a 100644
--- a/.github/commands.json
+++ b/.github/commands.json
@@ -86,21 +86,6 @@
},
{
"type": "label",
- "name": "notebook",
- "regex": "notebook.*",
- "assign": [
- "rebornix"
- ]
- },
- {
- "type": "label",
- "name": "notebook-triage",
- "regex": "notebook.*",
- "action": "updateLabels",
- "addLabel": "notebook-triage"
- },
- {
- "type": "label",
"name": "L10N",
"assign": [
"csigs",
diff --git a/extensions/css-language-features/server/src/utils/documentContext.ts b/extensions/css-language-features/server/src/utils/documentContext.ts
index 3defe4a445d..c9f46fb7578 100644
--- a/extensions/css-language-features/server/src/utils/documentContext.ts
+++ b/extensions/css-language-features/server/src/utils/documentContext.ts
@@ -30,8 +30,9 @@ export function getDocumentContext(documentUri: string, workspaceFolders: Worksp
return folderUri + ref.substring(1);
}
}
- base = base.substring(0, base.lastIndexOf('/') + 1);
- return Utils.resolvePath(URI.parse(base), ref).toString(true);
+ const baseUri = URI.parse(base);
+ const baseUriDir = baseUri.path.endsWith('/') ? baseUri : Utils.dirname(baseUri);
+ return Utils.resolvePath(baseUriDir, ref).toString(true);
},
};
}
diff --git a/extensions/html-language-features/server/src/node/nodeFs.ts b/extensions/html-language-features/server/src/node/nodeFs.ts
index 9bab4cf2913..edc9be776a6 100644
--- a/extensions/html-language-features/server/src/node/nodeFs.ts
+++ b/extensions/html-language-features/server/src/node/nodeFs.ts
@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { FileSystemProvider, getScheme } from '../requests';
+import { FileSystemProvider } from '../requests';
import { URI as Uri } from 'vscode-uri';
import * as fs from 'fs';
@@ -11,7 +11,7 @@ import { FileType } from 'vscode-css-languageservice';
export function getNodeFileFS(): FileSystemProvider {
function ensureFileUri(location: string) {
- if (getScheme(location) !== 'file') {
+ if (!location.startsWith('file:')) {
throw new Error('fileSystemProvider can only handle file URLs');
}
}
diff --git a/extensions/html-language-features/server/src/requests.ts b/extensions/html-language-features/server/src/requests.ts
index 3899cf9eff5..725f6f3b135 100644
--- a/extensions/html-language-features/server/src/requests.ts
+++ b/extensions/html-language-features/server/src/requests.ts
@@ -3,7 +3,6 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { URI } from 'vscode-uri';
import { RequestType, Connection } from 'vscode-languageserver';
import { RuntimeEnvironment } from './htmlServer';
@@ -77,80 +76,3 @@ export function getFileSystemProvider(handledSchemas: string[], connection: Conn
}
};
}
-
-export function getScheme(uri: string) {
- return uri.substr(0, uri.indexOf(':'));
-}
-
-export function dirname(uri: string) {
- const lastIndexOfSlash = uri.lastIndexOf('/');
- return lastIndexOfSlash !== -1 ? uri.substr(0, lastIndexOfSlash) : '';
-}
-
-export function basename(uri: string) {
- const lastIndexOfSlash = uri.lastIndexOf('/');
- return uri.substr(lastIndexOfSlash + 1);
-}
-
-
-const Slash = '/'.charCodeAt(0);
-const Dot = '.'.charCodeAt(0);
-
-export function extname(uri: string) {
- for (let i = uri.length - 1; i >= 0; i--) {
- const ch = uri.charCodeAt(i);
- if (ch === Dot) {
- if (i > 0 && uri.charCodeAt(i - 1) !== Slash) {
- return uri.substr(i);
- } else {
- break;
- }
- } else if (ch === Slash) {
- break;
- }
- }
- return '';
-}
-
-export function isAbsolutePath(path: string) {
- return path.charCodeAt(0) === Slash;
-}
-
-export function resolvePath(uriString: string, path: string): string {
- if (isAbsolutePath(path)) {
- const uri = URI.parse(uriString);
- const parts = path.split('/');
- return uri.with({ path: normalizePath(parts) }).toString();
- }
- return joinPath(uriString, path);
-}
-
-export function normalizePath(parts: string[]): string {
- const newParts: string[] = [];
- for (const part of parts) {
- if (part.length === 0 || part.length === 1 && part.charCodeAt(0) === Dot) {
- // ignore
- } else if (part.length === 2 && part.charCodeAt(0) === Dot && part.charCodeAt(1) === Dot) {
- newParts.pop();
- } else {
- newParts.push(part);
- }
- }
- if (parts.length > 1 && parts[parts.length - 1].length === 0) {
- newParts.push('');
- }
- let res = newParts.join('/');
- if (parts[0].length === 0) {
- res = '/' + res;
- }
- return res;
-}
-
-export function joinPath(uriString: string, ...paths: string[]): string {
- const uri = URI.parse(uriString);
- const parts = uri.path.split('/');
- for (const path of paths) {
- parts.push(...path.split('/'));
- }
- return uri.with({ path: normalizePath(parts) }).toString();
-}
diff --git a/extensions/html-language-features/server/src/utils/documentContext.ts b/extensions/html-language-features/server/src/utils/documentContext.ts
index 88e3f032885..9cf8ce9ea76 100644
--- a/extensions/html-language-features/server/src/utils/documentContext.ts
+++ b/extensions/html-language-features/server/src/utils/documentContext.ts
@@ -6,7 +6,7 @@
import { DocumentContext } from 'vscode-css-languageservice';
import { endsWith, startsWith } from '../utils/strings';
import { WorkspaceFolder } from 'vscode-languageserver';
-import { resolvePath } from '../requests';
+import { URI, Utils } from 'vscode-uri';
export function getDocumentContext(documentUri: string, workspaceFolders: WorkspaceFolder[]): DocumentContext {
function getRootFolder(): string | undefined {
@@ -34,8 +34,9 @@ export function getDocumentContext(documentUri: string, workspaceFolders: Worksp
return folderUri + ref.substr(1);
}
}
- base = base.substr(0, base.lastIndexOf('/') + 1);
- return resolvePath(base, ref);
+ const baseUri = URI.parse(base);
+ const baseUriDir = baseUri.path.endsWith('/') ? baseUri : Utils.dirname(baseUri);
+ return Utils.resolvePath(baseUriDir, ref).toString(true);
},
};
}
diff --git a/extensions/ipynb/src/serializers.ts b/extensions/ipynb/src/serializers.ts
index 455fb0d2745..21e97824f18 100644
--- a/extensions/ipynb/src/serializers.ts
+++ b/extensions/ipynb/src/serializers.ts
@@ -7,6 +7,7 @@ import type * as nbformat from '@jupyterlab/nbformat';
import { NotebookCell, NotebookCellData, NotebookCellKind, NotebookCellOutput } from 'vscode';
import { CellMetadata, CellOutputMetadata } from './common';
import { textMimeTypes } from './deserializers';
+import { compressOutputItemStreams } from './streamCompressor';
const textDecoder = new TextDecoder();
@@ -270,21 +271,17 @@ type JupyterOutput =
function convertStreamOutput(output: NotebookCellOutput): JupyterOutput {
const outputs: string[] = [];
- output.items
- .filter((opit) => opit.mime === CellOutputMimeTypes.stderr || opit.mime === CellOutputMimeTypes.stdout)
- .map((opit) => textDecoder.decode(opit.data))
- .forEach(value => {
- // Ensure each line is a seprate entry in an array (ending with \n).
- const lines = value.split('\n');
- // If the last item in `outputs` is not empty and the first item in `lines` is not empty, then concate them.
- // As they are part of the same line.
- if (outputs.length && lines.length && lines[0].length > 0) {
- outputs[outputs.length - 1] = `${outputs[outputs.length - 1]}${lines.shift()!}`;
- }
- for (const line of lines) {
- outputs.push(line);
- }
- });
+ const compressedStream = output.items.length ? new TextDecoder().decode(compressOutputItemStreams(output.items[0].mime, output.items)) : '';
+ // Ensure each line is a separate entry in an array (ending with \n).
+ const lines = compressedStream.split('\n');
+ // If the last item in `outputs` is not empty and the first item in `lines` is not empty, then concate them.
+ // As they are part of the same line.
+ if (outputs.length && lines.length && lines[0].length > 0) {
+ outputs[outputs.length - 1] = `${outputs[outputs.length - 1]}${lines.shift()!}`;
+ }
+ for (const line of lines) {
+ outputs.push(line);
+ }
for (let index = 0; index < (outputs.length - 1); index++) {
outputs[index] = `${outputs[index]}\n`;
diff --git a/extensions/ipynb/src/streamCompressor.ts b/extensions/ipynb/src/streamCompressor.ts
new file mode 100644
index 00000000000..cea3184e16c
--- /dev/null
+++ b/extensions/ipynb/src/streamCompressor.ts
@@ -0,0 +1,63 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import type { NotebookCellOutputItem } from 'vscode';
+
+
+/**
+ * Given a stream of individual stdout outputs, this function will return the compressed lines, escaping some of the common terminal escape codes.
+ * E.g. some terminal escape codes would result in the previous line getting cleared, such if we had 3 lines and
+ * last line contained such a code, then the result string would be just the first two lines.
+ */
+export function compressOutputItemStreams(mimeType: string, outputs: NotebookCellOutputItem[]) {
+ // return outputs.find(op => op.mime === mimeType)!.data.buffer;
+
+ const buffers: Uint8Array[] = [];
+ let startAppending = false;
+ // Pick the first set of outputs with the same mime type.
+ for (const output of outputs) {
+ if (output.mime === mimeType) {
+ if ((buffers.length === 0 || startAppending)) {
+ buffers.push(output.data);
+ startAppending = true;
+ }
+ } else if (startAppending) {
+ startAppending = false;
+ }
+ }
+ compressStreamBuffer(buffers);
+ const totalBytes = buffers.reduce((p, c) => p + c.byteLength, 0);
+ const combinedBuffer = new Uint8Array(totalBytes);
+ let offset = 0;
+ for (const buffer of buffers) {
+ combinedBuffer.set(buffer, offset);
+ offset = offset + buffer.byteLength;
+ }
+ return combinedBuffer;
+}
+const MOVE_CURSOR_1_LINE_COMMAND = `${String.fromCharCode(27)}[A`;
+const MOVE_CURSOR_1_LINE_COMMAND_BYTES = MOVE_CURSOR_1_LINE_COMMAND.split('').map(c => c.charCodeAt(0));
+const LINE_FEED = 10;
+function compressStreamBuffer(streams: Uint8Array[]) {
+ streams.forEach((stream, index) => {
+ if (index === 0 || stream.length < MOVE_CURSOR_1_LINE_COMMAND.length) {
+ return;
+ }
+
+ const previousStream = streams[index - 1];
+
+ // Remove the previous line if required.
+ const command = stream.subarray(0, MOVE_CURSOR_1_LINE_COMMAND.length);
+ if (command[0] === MOVE_CURSOR_1_LINE_COMMAND_BYTES[0] && command[1] === MOVE_CURSOR_1_LINE_COMMAND_BYTES[1] && command[2] === MOVE_CURSOR_1_LINE_COMMAND_BYTES[2]) {
+ const lastIndexOfLineFeed = previousStream.lastIndexOf(LINE_FEED);
+ if (lastIndexOfLineFeed === -1) {
+ return;
+ }
+ streams[index - 1] = previousStream.subarray(0, lastIndexOfLineFeed);
+ streams[index] = stream.subarray(MOVE_CURSOR_1_LINE_COMMAND.length);
+ }
+ });
+ return streams;
+}
diff --git a/extensions/r/language-configuration.json b/extensions/r/language-configuration.json
index dd691e2a6d4..3a2e2f34f5f 100644
--- a/extensions/r/language-configuration.json
+++ b/extensions/r/language-configuration.json
@@ -11,13 +11,16 @@
["{", "}"],
["[", "]"],
["(", ")"],
+ ["`", "`"],
{ "open": "\"", "close": "\"", "notIn": ["string"] },
- { "open": "'", "close": "'", "notIn": ["string"] }
+ { "open": "'", "close": "'", "notIn": ["string", "comment"] },
+ { "open": "%", "close": "%", "notIn": ["string", "comment"] }
],
"surroundingPairs": [
["{", "}"],
["[", "]"],
["(", ")"],
+ ["`", "`"],
["\"", "\""],
["'", "'"]
]
diff --git a/extensions/typescript-language-features/src/languageFeatures/refactor.ts b/extensions/typescript-language-features/src/languageFeatures/refactor.ts
index d5d1723533c..144c6654609 100644
--- a/extensions/typescript-language-features/src/languageFeatures/refactor.ts
+++ b/extensions/typescript-language-features/src/languageFeatures/refactor.ts
@@ -135,7 +135,7 @@ const Extract_Interface = Object.freeze<CodeActionKind>({
});
const Move_NewFile = Object.freeze<CodeActionKind>({
- kind: vscode.CodeActionKind.Refactor.append('move').append('newFile'),
+ kind: vscode.CodeActionKind.RefactorMove.append('newFile'),
matches: refactor => refactor.name.startsWith('Move to a new file')
});
diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.api.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.api.test.ts
index 7d4d4f9e7ed..e447db1326c 100644
--- a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.api.test.ts
+++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.api.test.ts
@@ -115,18 +115,6 @@ const apiTestContentProvider: vscode.NotebookContentProvider = {
};
return dto;
},
- saveNotebook: async (_document: vscode.NotebookDocument, _cancellation: vscode.CancellationToken) => {
- return;
- },
- saveNotebookAs: async (_targetResource: vscode.Uri, _document: vscode.NotebookDocument, _cancellation: vscode.CancellationToken) => {
- return;
- },
- backupNotebook: async (_document: vscode.NotebookDocument, _context: vscode.NotebookDocumentBackupContext, _cancellation: vscode.CancellationToken) => {
- return {
- id: '1',
- delete: () => { }
- };
- }
};
(vscode.env.uiKind === vscode.UIKind.Web ? suite.skip : suite)('Notebook API tests', function () {
@@ -244,7 +232,8 @@ const apiTestContentProvider: vscode.NotebookContentProvider = {
await closeAllEditors();
});
- test('#115855 onDidSaveNotebookDocument', async function () {
+ // TODO: Skipped due to notebook content provider removal
+ test.skip('#115855 onDidSaveNotebookDocument', async function () {
const resource = await createRandomNotebookFile();
const notebook = await vscode.workspace.openNotebookDocument(resource);
diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.document.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.document.test.ts
index ab11a4ee493..e89bf64a26a 100644
--- a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.document.test.ts
+++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.document.test.ts
@@ -26,15 +26,6 @@ suite('Notebook Document', function () {
[new vscode.NotebookCellData(vscode.NotebookCellKind.Code, uri.toString(), 'javascript')],
);
}
- async saveNotebook(_document: vscode.NotebookDocument, _cancellation: vscode.CancellationToken) {
- //
- }
- async saveNotebookAs(_targetResource: vscode.Uri, _document: vscode.NotebookDocument, _cancellation: vscode.CancellationToken) {
- //
- }
- async backupNotebook(_document: vscode.NotebookDocument, _context: vscode.NotebookDocumentBackupContext, _cancellation: vscode.CancellationToken) {
- return { id: '', delete() { } };
- }
};
const disposables: vscode.Disposable[] = [];
@@ -329,40 +320,6 @@ suite('Notebook Document', function () {
assert.ok(document.metadata.custom.extraNotebookMetadata, `Test metadata not found`);
});
- test('document save API', async function () {
- const uri = await utils.createRandomFile(undefined, undefined, '.nbdtest');
- const notebook = await vscode.workspace.openNotebookDocument(uri);
-
- assert.strictEqual(notebook.uri.toString(), uri.toString());
- assert.strictEqual(notebook.isDirty, false);
- assert.strictEqual(notebook.isUntitled, false);
- assert.strictEqual(notebook.cellCount, 1);
- assert.strictEqual(notebook.notebookType, 'notebook.nbdtest');
-
- const edit = new vscode.WorkspaceEdit();
- edit.set(notebook.uri, [vscode.NotebookEdit.replaceCells(new vscode.NotebookRange(0, 0), [{
- kind: vscode.NotebookCellKind.Markup,
- languageId: 'markdown',
- metadata: undefined,
- outputs: [],
- value: 'new_markdown'
- }, {
- kind: vscode.NotebookCellKind.Code,
- languageId: 'fooLang',
- metadata: undefined,
- outputs: [],
- value: 'new_code'
- }])]);
-
- const success = await vscode.workspace.applyEdit(edit);
- assert.strictEqual(success, true);
- assert.strictEqual(notebook.isDirty, true);
-
- await notebook.save();
- assert.strictEqual(notebook.isDirty, false);
- });
-
-
test('setTextDocumentLanguage for notebook cells', async function () {
const uri = await utils.createRandomFile(undefined, undefined, '.nbdtest');
@@ -395,21 +352,6 @@ suite('Notebook Document', function () {
assert.strictEqual(cellDoc.languageId, 'css');
});
- test('dirty state - complex', async function () {
- const resource = await utils.createRandomFile(undefined, undefined, '.nbdtest');
- const document = await vscode.workspace.openNotebookDocument(resource);
- assert.strictEqual(document.isDirty, false);
-
- const edit = new vscode.WorkspaceEdit();
- edit.set(document.uri, [vscode.NotebookEdit.replaceCells(new vscode.NotebookRange(0, document.cellCount), [])]);
- assert.ok(await vscode.workspace.applyEdit(edit));
-
- assert.strictEqual(document.isDirty, true);
-
- await document.save();
- assert.strictEqual(document.isDirty, false);
- });
-
test('dirty state - serializer', async function () {
const resource = await utils.createRandomFile(undefined, undefined, '.nbdserializer');
const document = await vscode.workspace.openNotebookDocument(resource);
diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.kernel.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.kernel.test.ts
index 0c4cff5cf4b..fa92d91e8a2 100644
--- a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.kernel.test.ts
+++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.kernel.test.ts
@@ -124,18 +124,6 @@ const apiTestContentProvider: vscode.NotebookContentProvider = {
]
};
return dto;
- },
- saveNotebook: async (_document: vscode.NotebookDocument, _cancellation: vscode.CancellationToken) => {
- return;
- },
- saveNotebookAs: async (_targetResource: vscode.Uri, _document: vscode.NotebookDocument, _cancellation: vscode.CancellationToken) => {
- return;
- },
- backupNotebook: async (_document: vscode.NotebookDocument, _context: vscode.NotebookDocumentBackupContext, _cancellation: vscode.CancellationToken) => {
- return {
- id: '1',
- delete: () => { }
- };
}
};
diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/types.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/types.test.ts
index aa22d484574..403b81454e9 100644
--- a/extensions/vscode-api-tests/src/singlefolder-tests/types.test.ts
+++ b/extensions/vscode-api-tests/src/singlefolder-tests/types.test.ts
@@ -20,6 +20,7 @@ suite('vscode API - types', () => {
assert.ok(vscode.CodeActionKind.Refactor instanceof vscode.CodeActionKind);
assert.ok(vscode.CodeActionKind.RefactorExtract instanceof vscode.CodeActionKind);
assert.ok(vscode.CodeActionKind.RefactorInline instanceof vscode.CodeActionKind);
+ assert.ok(vscode.CodeActionKind.RefactorMove instanceof vscode.CodeActionKind);
assert.ok(vscode.CodeActionKind.RefactorRewrite instanceof vscode.CodeActionKind);
assert.ok(vscode.CodeActionKind.Source instanceof vscode.CodeActionKind);
assert.ok(vscode.CodeActionKind.SourceOrganizeImports instanceof vscode.CodeActionKind);
diff --git a/extensions/vscode-notebook-tests/src/extension.ts b/extensions/vscode-notebook-tests/src/extension.ts
index 7160f07c1a1..4cd81a74756 100644
--- a/extensions/vscode-notebook-tests/src/extension.ts
+++ b/extensions/vscode-notebook-tests/src/extension.ts
@@ -43,18 +43,6 @@ export function activate(context: vscode.ExtensionContext): any {
};
return dto;
- },
- saveNotebook: async (_document: vscode.NotebookDocument, _cancellation: vscode.CancellationToken) => {
- return;
- },
- saveNotebookAs: async (_targetResource: vscode.Uri, _document: vscode.NotebookDocument, _cancellation: vscode.CancellationToken) => {
- return;
- },
- backupNotebook: async (_document: vscode.NotebookDocument, _context: vscode.NotebookDocumentBackupContext, _cancellation: vscode.CancellationToken) => {
- return {
- id: '1',
- delete: () => { }
- };
}
}));
diff --git a/package.json b/package.json
index ea705527d8c..7458844c9bf 100644
--- a/package.json
+++ b/package.json
@@ -86,13 +86,13 @@
"vscode-proxy-agent": "^0.12.0",
"vscode-regexpp": "^3.1.0",
"vscode-textmate": "7.0.1",
- "xterm": "5.0.0-beta.54",
- "xterm-addon-canvas": "0.2.0-beta.23",
- "xterm-addon-search": "0.10.0-beta.6",
- "xterm-addon-serialize": "0.8.0-beta.6",
+ "xterm": "5.0.0-beta.60",
+ "xterm-addon-canvas": "0.2.0-beta.26",
+ "xterm-addon-search": "0.10.0-beta.7",
+ "xterm-addon-serialize": "0.8.0-beta.7",
"xterm-addon-unicode11": "0.4.0-beta.5",
- "xterm-addon-webgl": "0.13.0-beta.49",
- "xterm-headless": "5.0.0-beta.5",
+ "xterm-addon-webgl": "0.13.0-beta.55",
+ "xterm-headless": "4.20.0-beta.74",
"yauzl": "^2.9.2",
"yazl": "^2.4.3"
},
@@ -230,7 +230,7 @@
"@vscode/windows-registry": "1.0.6",
"windows-foreground-love": "0.4.0",
"windows-mutex": "0.4.1",
- "windows-process-tree": "0.3.3"
+ "windows-process-tree": "0.3.4"
},
"resolutions": {
"elliptic": "^6.5.3",
diff --git a/remote/package.json b/remote/package.json
index d34e30223c7..5f6e845b83a 100644
--- a/remote/package.json
+++ b/remote/package.json
@@ -24,18 +24,18 @@
"vscode-proxy-agent": "^0.12.0",
"vscode-regexpp": "^3.1.0",
"vscode-textmate": "7.0.1",
- "xterm": "5.0.0-beta.54",
- "xterm-addon-canvas": "0.2.0-beta.23",
- "xterm-addon-search": "0.10.0-beta.6",
- "xterm-addon-serialize": "0.8.0-beta.6",
+ "xterm": "5.0.0-beta.60",
+ "xterm-addon-canvas": "0.2.0-beta.26",
+ "xterm-addon-search": "0.10.0-beta.7",
+ "xterm-addon-serialize": "0.8.0-beta.7",
"xterm-addon-unicode11": "0.4.0-beta.5",
- "xterm-addon-webgl": "0.13.0-beta.49",
- "xterm-headless": "5.0.0-beta.5",
+ "xterm-addon-webgl": "0.13.0-beta.55",
+ "xterm-headless": "4.20.0-beta.74",
"yauzl": "^2.9.2",
"yazl": "^2.4.3"
},
"optionalDependencies": {
"@vscode/windows-registry": "1.0.6",
- "windows-process-tree": "0.3.3"
+ "windows-process-tree": "0.3.4"
}
}
diff --git a/remote/web/package.json b/remote/web/package.json
index 428d0f52a05..c147bc2d260 100644
--- a/remote/web/package.json
+++ b/remote/web/package.json
@@ -11,10 +11,10 @@
"tas-client-umd": "0.1.6",
"vscode-oniguruma": "1.6.1",
"vscode-textmate": "7.0.1",
- "xterm": "5.0.0-beta.54",
- "xterm-addon-canvas": "0.2.0-beta.23",
- "xterm-addon-search": "0.10.0-beta.6",
+ "xterm": "5.0.0-beta.60",
+ "xterm-addon-canvas": "0.2.0-beta.26",
+ "xterm-addon-search": "0.10.0-beta.7",
"xterm-addon-unicode11": "0.4.0-beta.5",
- "xterm-addon-webgl": "0.13.0-beta.49"
+ "xterm-addon-webgl": "0.13.0-beta.55"
}
}
diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock
index b117c32ad58..6ca0789c3ad 100644
--- a/remote/web/yarn.lock
+++ b/remote/web/yarn.lock
@@ -68,27 +68,27 @@ vscode-textmate@7.0.1:
resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-7.0.1.tgz#8118a32b02735dccd14f893b495fa5389ad7de79"
integrity sha512-zQ5U/nuXAAMsh691FtV0wPz89nSkHbs+IQV8FDk+wew9BlSDhf4UmWGlWJfTR2Ti6xZv87Tj5fENzKf6Qk7aLw==
-xterm-addon-canvas@0.2.0-beta.23:
- version "0.2.0-beta.23"
- resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.2.0-beta.23.tgz#f5ee0db3b029ea705ef3c1228825c28ec6368b48"
- integrity sha512-414qLxMlOzC3LyAt1qHmvrcW2VIPAsFQkXTGcSzX42XCOTF4lA9Jf8ePVNgokQAyvlGK3j3K0y0d7lTTR5I/Zw==
+xterm-addon-canvas@0.2.0-beta.26:
+ version "0.2.0-beta.26"
+ resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.2.0-beta.26.tgz#db6d134177bac58d24e02d11c123f0cefb0e95b9"
+ integrity sha512-OZctolm/iUjSG11iYERJSu9ax2GBXe96ASYcHfJAeq19IMHadQvD3AWaJl25/MMChmvJ0qT1Q/+6p0ElgfV77Q==
-xterm-addon-search@0.10.0-beta.6:
- version "0.10.0-beta.6"
- resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.10.0-beta.6.tgz#a475d793a13b378f56b439b8c7eeeff2095831ae"
- integrity sha512-fDS0dbM/ZuVBfieWyXJgFvQwNk95rpVbaBRcVpUM9sM/R5+ePQr+uhcaicfuWAku7urP7P/QNnkeAkeQjf8E6w==
+xterm-addon-search@0.10.0-beta.7:
+ version "0.10.0-beta.7"
+ resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.10.0-beta.7.tgz#77812514c4aa84668d9e247a9172618ccd2517d7"
+ integrity sha512-58dFGbLQc3C0Iww/Jq65HcXC9/RL+57duY5+rijts6KBZqAlGQCN3f2ORFKRvJEQDTgxOcnK9o9welyKK+PQ3Q==
xterm-addon-unicode11@0.4.0-beta.5:
version "0.4.0-beta.5"
resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.4.0-beta.5.tgz#3900e66f10d2e506133b61d7421aab6878d32665"
integrity sha512-+g+fuxAd/tkCEJ/jhdnebXKtdPrhsu4VKWNnB/3qM35GbuGQOasmYFYnKL+HYZMpbQ6YqeZcXTVg/wnCTttz0g==
-xterm-addon-webgl@0.13.0-beta.49:
- version "0.13.0-beta.49"
- resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.13.0-beta.49.tgz#0cbffccccec06f5638ddc793ae7a0ff8ce0a891b"
- integrity sha512-c1/8hLrw3PuPAnyPVLNg8i2FDkyu5SkU654DPEEgKgHHeAh3sfil28LleBpPhpP24531i7XNt1LLHCGMJ+gkFw==
+xterm-addon-webgl@0.13.0-beta.55:
+ version "0.13.0-beta.55"
+ resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.13.0-beta.55.tgz#d116fbb8d2e2bbfa562876f90d1aeedf48cc7eb8"
+ integrity sha512-i595z+lcbJaxLM7WTk845440lfyc3RERn/yWqTql+gnoA1YoP3gAnl/qdluFrKndM8sQGWmCsz9qACANXRjLbA==
-xterm@5.0.0-beta.54:
- version "5.0.0-beta.54"
- resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.0.0-beta.54.tgz#2c353221f289af22327aae6318bc6422c636fd41"
- integrity sha512-wRzs1NbVCkZUzAqvglQcDVreT7RLLFkpdBi0oOLbZXgTaYr/Be93aCuuEjOVp7lnV0hi1gEP5K9Ugn621QffNw==
+xterm@5.0.0-beta.60:
+ version "5.0.0-beta.60"
+ resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.0.0-beta.60.tgz#1d16d6828f125c18c6d6e7db8769d1d848707a35"
+ integrity sha512-wkMXXfmwF9jIBtjSoEy7nyh54lDJz4wE0CuYzyBP/cjbTnjAkheeZcY9cJBlDRtP4NoZ7EhsA9GyXNeIrviiJg==
diff --git a/remote/yarn.lock b/remote/yarn.lock
index 0ac2187f123..12af52878b9 100644
--- a/remote/yarn.lock
+++ b/remote/yarn.lock
@@ -776,10 +776,10 @@ wide-align@^1.1.0:
dependencies:
string-width "^1.0.2 || 2 || 3 || 4"
-windows-process-tree@0.3.3:
- version "0.3.3"
- resolved "https://registry.yarnpkg.com/windows-process-tree/-/windows-process-tree-0.3.3.tgz#7c178815f02bf4cfbcac1f93b2f3a3cc10bc9245"
- integrity sha512-rkiAMP0AS27xikFyn7i4gPbOK16UdjY8X/C6eo37CnfNLqTvK2eEaT+Dh0e5xnvmlsi0lEKd60O+4ajzfDkq7A==
+windows-process-tree@0.3.4:
+ version "0.3.4"
+ resolved "https://registry.yarnpkg.com/windows-process-tree/-/windows-process-tree-0.3.4.tgz#6bc4b8010129c30ff95bcd333b9f94744dd3c4fb"
+ integrity sha512-rtSX73i9OnkDxSdBP9c1YBunEwheZdO/hjRwNk9uSoWqO92x0zDRGfIIK0MtUn8gZZD+2kPEVpj5MmfNl7JpJA==
dependencies:
nan "^2.13.2"
@@ -788,40 +788,40 @@ wrappy@1:
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
-xterm-addon-canvas@0.2.0-beta.23:
- version "0.2.0-beta.23"
- resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.2.0-beta.23.tgz#f5ee0db3b029ea705ef3c1228825c28ec6368b48"
- integrity sha512-414qLxMlOzC3LyAt1qHmvrcW2VIPAsFQkXTGcSzX42XCOTF4lA9Jf8ePVNgokQAyvlGK3j3K0y0d7lTTR5I/Zw==
+xterm-addon-canvas@0.2.0-beta.26:
+ version "0.2.0-beta.26"
+ resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.2.0-beta.26.tgz#db6d134177bac58d24e02d11c123f0cefb0e95b9"
+ integrity sha512-OZctolm/iUjSG11iYERJSu9ax2GBXe96ASYcHfJAeq19IMHadQvD3AWaJl25/MMChmvJ0qT1Q/+6p0ElgfV77Q==
-xterm-addon-search@0.10.0-beta.6:
- version "0.10.0-beta.6"
- resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.10.0-beta.6.tgz#a475d793a13b378f56b439b8c7eeeff2095831ae"
- integrity sha512-fDS0dbM/ZuVBfieWyXJgFvQwNk95rpVbaBRcVpUM9sM/R5+ePQr+uhcaicfuWAku7urP7P/QNnkeAkeQjf8E6w==
+xterm-addon-search@0.10.0-beta.7:
+ version "0.10.0-beta.7"
+ resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.10.0-beta.7.tgz#77812514c4aa84668d9e247a9172618ccd2517d7"
+ integrity sha512-58dFGbLQc3C0Iww/Jq65HcXC9/RL+57duY5+rijts6KBZqAlGQCN3f2ORFKRvJEQDTgxOcnK9o9welyKK+PQ3Q==
-xterm-addon-serialize@0.8.0-beta.6:
- version "0.8.0-beta.6"
- resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.8.0-beta.6.tgz#fe21a74a0ca3ecdf12843136115f074a431b3876"
- integrity sha512-hb3TRqvg36MW5H4ZnYjw4EHb55iZ4rOOuH+Hx4ZTBDI1pszPtryFqXbS93NBLKgsOqDovIDsH8fWvNfhPdGmsQ==
+xterm-addon-serialize@0.8.0-beta.7:
+ version "0.8.0-beta.7"
+ resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.8.0-beta.7.tgz#73a71834a687c825ff3d3c824229fbd856d1570f"
+ integrity sha512-cghmB/2DYwX4HvjGMWmbxYO3NrvgfYWrQt0QGb0oToZh1gOgoEkUxZVZiOl5WlqFYpI+jHXXX48XgfFONZ1rMA==
xterm-addon-unicode11@0.4.0-beta.5:
version "0.4.0-beta.5"
resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.4.0-beta.5.tgz#3900e66f10d2e506133b61d7421aab6878d32665"
integrity sha512-+g+fuxAd/tkCEJ/jhdnebXKtdPrhsu4VKWNnB/3qM35GbuGQOasmYFYnKL+HYZMpbQ6YqeZcXTVg/wnCTttz0g==
-xterm-addon-webgl@0.13.0-beta.49:
- version "0.13.0-beta.49"
- resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.13.0-beta.49.tgz#0cbffccccec06f5638ddc793ae7a0ff8ce0a891b"
- integrity sha512-c1/8hLrw3PuPAnyPVLNg8i2FDkyu5SkU654DPEEgKgHHeAh3sfil28LleBpPhpP24531i7XNt1LLHCGMJ+gkFw==
+xterm-addon-webgl@0.13.0-beta.55:
+ version "0.13.0-beta.55"
+ resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.13.0-beta.55.tgz#d116fbb8d2e2bbfa562876f90d1aeedf48cc7eb8"
+ integrity sha512-i595z+lcbJaxLM7WTk845440lfyc3RERn/yWqTql+gnoA1YoP3gAnl/qdluFrKndM8sQGWmCsz9qACANXRjLbA==
-xterm-headless@5.0.0-beta.5:
- version "5.0.0-beta.5"
- resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.0.0-beta.5.tgz#e29b6c5081f31f887122b7263ba996b0c46b3c22"
- integrity sha512-CMQ1+prBNF92oBMeZzc2rfTcmOaCGfwwSaoPYNTjyziZT6mZsEg7amajYkb0YAnqJ29MFm4kPGZbU78/dX4k2A==
+xterm-headless@4.20.0-beta.74:
+ version "4.20.0-beta.74"
+ resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.20.0-beta.74.tgz#1eade8cdfbf4389cadf0ae8b8b2cb536862323d2"
+ integrity sha512-WwHcSrnHGbqcRKJTDJgEJT4y4X5KPJxcMbi5RGj/T1FoXg/uYU23DO1RtvJV8ZnRKLbcY/Ru0wWf7ZGDrEk1DA==
-xterm@5.0.0-beta.54:
- version "5.0.0-beta.54"
- resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.0.0-beta.54.tgz#2c353221f289af22327aae6318bc6422c636fd41"
- integrity sha512-wRzs1NbVCkZUzAqvglQcDVreT7RLLFkpdBi0oOLbZXgTaYr/Be93aCuuEjOVp7lnV0hi1gEP5K9Ugn621QffNw==
+xterm@5.0.0-beta.60:
+ version "5.0.0-beta.60"
+ resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.0.0-beta.60.tgz#1d16d6828f125c18c6d6e7db8769d1d848707a35"
+ integrity sha512-wkMXXfmwF9jIBtjSoEy7nyh54lDJz4wE0CuYzyBP/cjbTnjAkheeZcY9cJBlDRtP4NoZ7EhsA9GyXNeIrviiJg==
yallist@^4.0.0:
version "4.0.0"
diff --git a/src/main.js b/src/main.js
index 73fe1be2ade..884120e01b0 100644
--- a/src/main.js
+++ b/src/main.js
@@ -56,8 +56,7 @@ perf.mark('code/willStartCrashReporter');
// * --disable-crash-reporter command line parameter is not set
//
// Disable crash reporting in all other cases.
-if (args['crash-reporter-directory'] ||
- (argvConfig['enable-crash-reporter'] && !args['disable-crash-reporter'])) {
+if (args['crash-reporter-directory'] || (argvConfig['enable-crash-reporter'] && !args['disable-crash-reporter'])) {
configureCrashReporter();
}
perf.mark('code/didStartCrashReporter');
diff --git a/src/vs/base/browser/ui/iconLabel/iconLabels.ts b/src/vs/base/browser/ui/iconLabel/iconLabels.ts
index 4fe1a5c79e8..5ecaddf78f1 100644
--- a/src/vs/base/browser/ui/iconLabel/iconLabels.ts
+++ b/src/vs/base/browser/ui/iconLabel/iconLabels.ts
@@ -14,7 +14,9 @@ export function renderLabelWithIcons(text: string): Array<HTMLSpanElement | stri
let textStart = 0, textStop = 0;
while ((match = labelWithIconsRegex.exec(text)) !== null) {
textStop = match.index || 0;
- elements.push(text.substring(textStart, textStop));
+ if (textStart < textStop) {
+ elements.push(text.substring(textStart, textStop));
+ }
textStart = (match.index || 0) + match[0].length;
const [, escaped, codicon] = match;
diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts
index 054cf4de2d4..e8d7a30dfc4 100644
--- a/src/vs/base/common/event.ts
+++ b/src/vs/base/common/event.ts
@@ -493,32 +493,36 @@ export interface EmitterOptions {
}
-class EventProfiling {
+export class EventProfiling {
+
+ static readonly all = new Set<EventProfiling>();
private static _idPool = 0;
- private _name: string;
+ readonly name: string;
+ public listenerCount: number = 0;
+ public invocationCount = 0;
+ public elapsedOverall = 0;
+ public durations: number[] = [];
+
private _stopWatch?: StopWatch;
- private _listenerCount: number = 0;
- private _invocationCount = 0;
- private _elapsedOverall = 0;
constructor(name: string) {
- this._name = `${name}_${EventProfiling._idPool++}`;
+ this.name = `${name}_${EventProfiling._idPool++}`;
+ EventProfiling.all.add(this);
}
start(listenerCount: number): void {
this._stopWatch = new StopWatch(true);
- this._listenerCount = listenerCount;
+ this.listenerCount = listenerCount;
}
stop(): void {
if (this._stopWatch) {
const elapsed = this._stopWatch.elapsed();
- this._elapsedOverall += elapsed;
- this._invocationCount += 1;
-
- console.info(`did FIRE ${this._name}: elapsed_ms: ${elapsed.toFixed(5)}, listener: ${this._listenerCount} (elapsed_overall: ${this._elapsedOverall.toFixed(2)}, invocations: ${this._invocationCount})`);
+ this.durations.push(elapsed);
+ this.elapsedOverall += elapsed;
+ this.invocationCount += 1;
this._stopWatch = undefined;
}
}
diff --git a/src/vs/base/common/platform.ts b/src/vs/base/common/platform.ts
index 5fd52a6fa76..506ee713281 100644
--- a/src/vs/base/common/platform.ts
+++ b/src/vs/base/common/platform.ts
@@ -15,6 +15,7 @@ let _isWeb = false;
let _isElectron = false;
let _isIOS = false;
let _isCI = false;
+let _isMobile = false;
let _locale: string | undefined = undefined;
let _language: string = LANGUAGE_DEFAULT;
let _translationsConfigFile: string | undefined = undefined;
@@ -79,6 +80,7 @@ if (typeof navigator === 'object' && !isElectronRenderer) {
_isMacintosh = _userAgent.indexOf('Macintosh') >= 0;
_isIOS = (_userAgent.indexOf('Macintosh') >= 0 || _userAgent.indexOf('iPad') >= 0 || _userAgent.indexOf('iPhone') >= 0) && !!navigator.maxTouchPoints && navigator.maxTouchPoints > 0;
_isLinux = _userAgent.indexOf('Linux') >= 0;
+ _isMobile = _userAgent?.indexOf('Mobi') >= 0;
_isWeb = true;
const configuredLocale = nls.getConfiguredDefaultLocale(
@@ -157,6 +159,7 @@ export const isElectron = _isElectron;
export const isWeb = _isWeb;
export const isWebWorker = (_isWeb && typeof globals.importScripts === 'function');
export const isIOS = _isIOS;
+export const isMobile = _isMobile;
/**
* Whether we run inside a CI environment, such as
* GH actions or Azure Pipelines.
diff --git a/src/vs/base/node/languagePacks.js b/src/vs/base/node/languagePacks.js
index 9a554301a27..006cd42d707 100644
--- a/src/vs/base/node/languagePacks.js
+++ b/src/vs/base/node/languagePacks.js
@@ -10,12 +10,11 @@
'use strict';
/**
- * @param {NodeRequire} nodeRequire
* @param {typeof import('path')} path
* @param {typeof import('fs')} fs
* @param {typeof import('../common/performance')} perf
*/
- function factory(nodeRequire, path, fs, perf) {
+ function factory(path, fs, perf) {
/**
* @param {string} file
@@ -248,12 +247,12 @@
if (typeof define === 'function') {
// amd
- define(['require', 'path', 'fs', 'vs/base/common/performance'], function (require, /** @type {typeof import('path')} */ path, /** @type {typeof import('fs')} */ fs, /** @type {typeof import('../common/performance')} */ perf) { return factory(require.__$__nodeRequire, path, fs, perf); });
+ define(['path', 'fs', 'vs/base/common/performance'], function (/** @type {typeof import('path')} */ path, /** @type {typeof import('fs')} */ fs, /** @type {typeof import('../common/performance')} */ perf) { return factory(path, fs, perf); });
} else if (typeof module === 'object' && typeof module.exports === 'object') {
const path = require('path');
const fs = require('fs');
const perf = require('../common/performance');
- module.exports = factory(require, path, fs, perf);
+ module.exports = factory(path, fs, perf);
} else {
throw new Error('Unknown context');
}
diff --git a/src/vs/base/parts/ipc/test/node/ipc.cp.integrationTest.ts b/src/vs/base/parts/ipc/test/node/ipc.cp.integrationTest.ts
index 227751e86c3..386580ae5a6 100644
--- a/src/vs/base/parts/ipc/test/node/ipc.cp.integrationTest.ts
+++ b/src/vs/base/parts/ipc/test/node/ipc.cp.integrationTest.ts
@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
+import { timeout } from 'vs/base/common/async';
import { Client } from 'vs/base/parts/ipc/node/ipc.cp';
import { getPathFromAmdModule } from 'vs/base/test/node/testUtils';
import { TestServiceClient } from './testService';
@@ -15,7 +16,9 @@ function createClient(): Client {
});
}
-suite('IPC, Child Process', () => {
+suite('IPC, Child Process', function () {
+ this.timeout(10000);
+
test('createChannel', () => {
const client = createClient();
const channel = client.getChannel('test');
@@ -45,10 +48,12 @@ suite('IPC, Child Process', () => {
});
});
- const request = service.marco();
- const result = Promise.all([request, event]);
+ return timeout(100).then(() => {
+ const request = service.marco();
+ const result = Promise.all([request, event]);
- return result.finally(() => client.dispose());
+ return result.finally(() => client.dispose());
+ });
});
test('event dispose', () => {
diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts
index 68053657b4c..2e57bc75e9f 100644
--- a/src/vs/code/electron-main/app.ts
+++ b/src/vs/code/electron-main/app.ts
@@ -5,7 +5,6 @@
import { app, BrowserWindow, dialog, protocol, session, Session, systemPreferences, WebFrameMain } from 'electron';
import { validatedIpcMain } from 'vs/base/parts/ipc/electron-main/ipcMain';
-import { statSync } from 'fs';
import { hostname, release } from 'os';
import { VSBuffer } from 'vs/base/common/buffer';
import { toErrorMessage } from 'vs/base/common/errorMessage';
@@ -109,9 +108,8 @@ import { ExtensionsProfileScannerService, IExtensionsProfileScannerService } fro
import { IExtensionsScannerService } from 'vs/platform/extensionManagement/common/extensionsScannerService';
import { ExtensionsScannerService } from 'vs/platform/extensionManagement/node/extensionsScannerService';
import { UserDataTransientProfilesHandler } from 'vs/platform/userDataProfile/electron-main/userDataTransientProfilesHandler';
-import { RunOnceScheduler, runWhenIdle } from 'vs/base/common/async';
-import { IUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile';
import { ProfileStorageChangesListenerChannel } from 'vs/platform/userDataSync/electron-main/userDataSyncProfilesStorageIpc';
+import { Promises, RunOnceScheduler, runWhenIdle } from 'vs/base/common/async';
/**
* The main VS Code application. There will only ever be one instance,
@@ -328,12 +326,12 @@ export class CodeApplication extends Disposable {
});
// macOS dock activate
- app.on('activate', (event, hasVisibleWindows) => {
+ app.on('activate', async (event, hasVisibleWindows) => {
this.logService.trace('app#activate');
// Mac only event: open new window when we get activated
if (!hasVisibleWindows) {
- this.windowsMainService?.openEmptyWindow({ context: OpenContext.DOCK });
+ await this.windowsMainService?.openEmptyWindow({ context: OpenContext.DOCK });
}
});
@@ -365,7 +363,7 @@ export class CodeApplication extends Disposable {
event.preventDefault();
// Keep in array because more might come!
- macOpenFileURIs.push(this.getWindowOpenableFromPathSync(path));
+ macOpenFileURIs.push(hasWorkspaceFileExtension(path) ? { workspaceUri: URI.file(path) } : { fileUri: URI.file(path) });
// Clear previous handler if any
if (runningTimeout !== undefined) {
@@ -374,8 +372,8 @@ export class CodeApplication extends Disposable {
}
// Handle paths delayed in case more are coming!
- runningTimeout = setTimeout(() => {
- this.windowsMainService?.open({
+ runningTimeout = setTimeout(async () => {
+ await this.windowsMainService?.open({
context: OpenContext.DOCK /* can also be opening from finder while app is running */,
cli: this.environmentMainService.args,
urisToOpen: macOpenFileURIs,
@@ -388,8 +386,8 @@ export class CodeApplication extends Disposable {
}, 100);
});
- app.on('new-window-for-tab', () => {
- this.windowsMainService?.openEmptyWindow({ context: OpenContext.DESKTOP }); //macOS native tab "+" button
+ app.on('new-window-for-tab', async () => {
+ await this.windowsMainService?.openEmptyWindow({ context: OpenContext.DESKTOP }); //macOS native tab "+" button
});
//#region Bootstrap IPC Handlers
@@ -539,15 +537,11 @@ export class CodeApplication extends Disposable {
// Setup Handlers
this.setUpHandlers(appInstantiationService);
- // Ensure profile exists when passed in from CLI
- const profilePromise = this.userDataProfilesMainService.checkAndCreateProfileFromCli(this.environmentMainService.args);
- const profile = profilePromise ? await profilePromise : undefined;
-
// Init Channels
appInstantiationService.invokeFunction(accessor => this.initChannels(accessor, mainProcessElectronServer, sharedProcessClient));
// Open Windows
- appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, profile, mainProcessElectronServer));
+ await appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, mainProcessElectronServer));
// Post Open Windows Tasks
appInstantiationService.invokeFunction(accessor => this.afterWindowOpen(accessor, sharedProcess));
@@ -633,7 +627,8 @@ export class CodeApplication extends Disposable {
services.set(IWindowsMainService, new SyncDescriptor(WindowsMainService, [machineId, this.userEnv], false));
// Dialogs
- services.set(IDialogMainService, new SyncDescriptor(DialogMainService, undefined, true));
+ const dialogMainService = new DialogMainService(this.logService);
+ services.set(IDialogMainService, dialogMainService);
// Launch
services.set(ILaunchMainService, new SyncDescriptor(LaunchMainService, undefined, false /* proxied to other processes */));
@@ -660,10 +655,6 @@ export class CodeApplication extends Disposable {
// Webview Manager
services.set(IWebviewManagerService, new SyncDescriptor(WebviewMainService));
- // Workspaces
- services.set(IWorkspacesService, new SyncDescriptor(WorkspacesMainService, undefined, false /* proxied to other processes */));
- services.set(IWorkspacesManagementMainService, new SyncDescriptor(WorkspacesManagementMainService, undefined, true));
- services.set(IWorkspacesHistoryMainService, new SyncDescriptor(WorkspacesHistoryMainService, undefined, false));
// Menubar
services.set(IMenubarMainService, new SyncDescriptor(MenubarMainService));
@@ -691,6 +682,12 @@ export class CodeApplication extends Disposable {
const backupMainService = new BackupMainService(this.environmentMainService, this.configurationService, this.logService, this.stateMainService);
services.set(IBackupMainService, backupMainService);
+ // Workspaces
+ const workspacesManagementMainService = new WorkspacesManagementMainService(this.environmentMainService, this.logService, this.userDataProfilesMainService, backupMainService, dialogMainService, this.productService);
+ services.set(IWorkspacesManagementMainService, workspacesManagementMainService);
+ services.set(IWorkspacesService, new SyncDescriptor(WorkspacesMainService, undefined, false /* proxied to other processes */));
+ services.set(IWorkspacesHistoryMainService, new SyncDescriptor(WorkspacesHistoryMainService, undefined, false));
+
// URL handling
services.set(IURLService, new SyncDescriptor(NativeURLService, undefined, false /* proxied to other processes */));
@@ -713,7 +710,10 @@ export class CodeApplication extends Disposable {
services.set(IExtensionsScannerService, new SyncDescriptor(ExtensionsScannerService, undefined, true));
// Init services that require it
- await backupMainService.initialize();
+ await Promises.settled([
+ backupMainService.initialize(),
+ workspacesManagementMainService.initialize()
+ ]);
return this.mainInstantiationService.createChild(services);
}
@@ -834,7 +834,7 @@ export class CodeApplication extends Disposable {
mainProcessElectronServer.registerChannel(ipcExtensionHostStarterChannelName, extensionHostStarterChannel);
}
- private openFirstWindow(accessor: ServicesAccessor, profile: IUserDataProfile | undefined, mainProcessElectronServer: ElectronIPCServer): ICodeWindow[] {
+ private async openFirstWindow(accessor: ServicesAccessor, mainProcessElectronServer: ElectronIPCServer): Promise<ICodeWindow[]> {
const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService);
const urlService = accessor.get(IURLService);
const nativeHostMainService = accessor.get(INativeHostMainService);
@@ -922,7 +922,7 @@ export class CodeApplication extends Disposable {
const windowOpenableFromProtocolLink = app.getWindowOpenableFromProtocolLink(uri);
logService.trace('app#handleURL: windowOpenableFromProtocolLink = ', windowOpenableFromProtocolLink);
if (windowOpenableFromProtocolLink) {
- const [window] = windowsMainService.open({
+ const [window] = await windowsMainService.open({
context: OpenContext.API,
cli: { ...environmentService.args },
urisToOpen: [windowOpenableFromProtocolLink],
@@ -937,7 +937,7 @@ export class CodeApplication extends Disposable {
}
if (shouldOpenInNewWindow) {
- const [window] = windowsMainService.open({
+ const [window] = await windowsMainService.open({
context: OpenContext.API,
cli: { ...environmentService.args },
forceNewWindow: true,
@@ -994,6 +994,9 @@ export class CodeApplication extends Disposable {
});
}
+ // Ensure profile exists when passed in from CLI
+ const profile = await this.userDataProfilesMainService.checkAndCreateProfileFromCli(this.environmentMainService.args);
+
// Start without file/folder arguments
if (!hasCliArgs && !hasFolderURIs && !hasFileURIs) {
@@ -1017,7 +1020,7 @@ export class CodeApplication extends Disposable {
return windowsMainService.open({
context: OpenContext.DOCK,
cli: args,
- urisToOpen: macOpenFiles.map(file => this.getWindowOpenableFromPathSync(file)),
+ urisToOpen: macOpenFiles.map(path => (hasWorkspaceFileExtension(path) ? { workspaceUri: URI.file(path) } : { fileUri: URI.file(path) })),
noRecentEntry,
waitMarkerFileURI,
initialStartup: true,
@@ -1109,23 +1112,6 @@ export class CodeApplication extends Disposable {
return undefined;
}
- private getWindowOpenableFromPathSync(path: string): IWindowOpenable {
- try {
- const fileStat = statSync(path);
- if (fileStat.isDirectory()) {
- return { folderUri: URI.file(path) };
- }
-
- if (hasWorkspaceFileExtension(path)) {
- return { workspaceUri: URI.file(path) };
- }
- } catch (error) {
- // ignore errors
- }
-
- return { fileUri: URI.file(path) };
- }
-
private afterWindowOpen(accessor: ServicesAccessor, sharedProcess: SharedProcess): void {
const telemetryService = accessor.get(ITelemetryService);
const updateService = accessor.get(IUpdateService);
diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts
index d2e59149d7d..e358efbb545 100644
--- a/src/vs/code/electron-main/main.ts
+++ b/src/vs/code/electron-main/main.ts
@@ -34,7 +34,7 @@ import { DiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsServ
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { EnvironmentMainService, IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
import { addArg, parseMainProcessArgv } from 'vs/platform/environment/node/argvHelper';
-import { createWaitMarkerFile } from 'vs/platform/environment/node/wait';
+import { createWaitMarkerFileSync } from 'vs/platform/environment/node/wait';
import { IFileService } from 'vs/platform/files/common/files';
import { FileService } from 'vs/platform/files/common/fileService';
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
@@ -469,8 +469,9 @@ class CodeMain {
//
// Note: we are not doing this if the wait marker has been already
// added as argument. This can happen if Code was started from CLI.
+
if (args.wait && !args.waitMarkerFilePath) {
- const waitMarkerFilePath = createWaitMarkerFile(args.verbose);
+ const waitMarkerFilePath = createWaitMarkerFileSync(args.verbose);
if (waitMarkerFilePath) {
addArg(process.argv, '--waitMarkerFilePath', waitMarkerFilePath);
args.waitMarkerFilePath = waitMarkerFilePath;
diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts
index 190d64038e9..daae8a10f16 100644
--- a/src/vs/code/node/cli.ts
+++ b/src/vs/code/node/cli.ts
@@ -19,7 +19,7 @@ import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { buildHelpMessage, buildVersionMessage, OPTIONS } from 'vs/platform/environment/node/argv';
import { addArg, parseCLIProcessArgv } from 'vs/platform/environment/node/argvHelper';
import { getStdinFilePath, hasStdinWithoutTty, readFromStdin, stdinDataListener } from 'vs/platform/environment/node/stdin';
-import { createWaitMarkerFile } from 'vs/platform/environment/node/wait';
+import { createWaitMarkerFileSync } from 'vs/platform/environment/node/wait';
import product from 'vs/platform/product/common/product';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { randomPath } from 'vs/base/common/extpath';
@@ -220,7 +220,7 @@ export async function main(argv: string[]): Promise<any> {
// is closed and then exit the waiting process.
let waitMarkerFilePath: string | undefined;
if (args.wait) {
- waitMarkerFilePath = createWaitMarkerFile(verbose);
+ waitMarkerFilePath = createWaitMarkerFileSync(verbose);
if (waitMarkerFilePath) {
addArg(argv, '--waitMarkerFilePath', waitMarkerFilePath);
}
diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts
index 8ce775febd3..33623ac6b3c 100644
--- a/src/vs/editor/browser/controller/textAreaHandler.ts
+++ b/src/vs/editor/browser/controller/textAreaHandler.ts
@@ -195,6 +195,9 @@ export class TextAreaHandler extends ViewPart {
},
getValueInRange: (range: Range, eol: EndOfLinePreference): string => {
return this._context.viewModel.getValueInRange(range, eol);
+ },
+ getValueLengthInRange: (range: Range): number => {
+ return this._context.viewModel.model.getValueLengthInRange(range);
}
};
@@ -242,6 +245,15 @@ export class TextAreaHandler extends ViewPart {
return new TextAreaState(textBefore, textBefore.length, textBefore.length, position, position);
}
}
+ // on macOS, write current selection into textarea will allow system text services pick selected text,
+ // but we still want to limit the amount of text given Chromium handles very poorly text even of a few
+ // thousand chars
+ // (https://github.com/microsoft/vscode/issues/27799)
+ const LIMIT_CHARS = 500;
+ if (platform.isMacintosh && !selection.isEmpty() && simpleModel.getValueLengthInRange(selection) < LIMIT_CHARS) {
+ const text = simpleModel.getValueInRange(selection, EndOfLinePreference.TextDefined);
+ return new TextAreaState(text, 0, text.length, selection.getStartPosition(), selection.getEndPosition());
+ }
// on Safari, document.execCommand('cut') and document.execCommand('copy') will just not work
// if the textarea has no content selected. So if there is an editor selection, ensure something
diff --git a/src/vs/editor/browser/controller/textAreaState.ts b/src/vs/editor/browser/controller/textAreaState.ts
index 97b381b66c4..0953b7a0594 100644
--- a/src/vs/editor/browser/controller/textAreaState.ts
+++ b/src/vs/editor/browser/controller/textAreaState.ts
@@ -23,6 +23,7 @@ export interface ISimpleModel {
getLineCount(): number;
getLineMaxColumn(lineNumber: number): number;
getValueInRange(range: Range, eol: EndOfLinePreference): string;
+ getValueLengthInRange(range: Range): number;
}
export interface ITypeData {
diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts
index bf2cc58e639..5cefe83dfc8 100644
--- a/src/vs/editor/common/model.ts
+++ b/src/vs/editor/common/model.ts
@@ -849,9 +849,11 @@ export interface ITextModel {
/**
* Set the current language mode associated with the model.
+ * @param languageId The new language.
+ * @param source The source of the call that set the language.
* @internal
*/
- setMode(languageId: string): void;
+ setMode(languageId: string, source?: string): void;
/**
* Returns the real (inner-most) language mode at a given position.
diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts
index f099642e294..102dc04c973 100644
--- a/src/vs/editor/common/model/textModel.ts
+++ b/src/vs/editor/common/model/textModel.ts
@@ -1907,8 +1907,8 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
return this.tokenization.getLanguageId();
}
- public setMode(languageId: string): void {
- this.tokenization.setLanguageId(languageId);
+ public setMode(languageId: string, source?: string): void {
+ this.tokenization.setLanguageId(languageId, source);
}
public getLanguageIdAtPosition(lineNumber: number, column: number): string {
diff --git a/src/vs/editor/common/model/tokenizationTextModelPart.ts b/src/vs/editor/common/model/tokenizationTextModelPart.ts
index cc2cf81f02b..378dcb56589 100644
--- a/src/vs/editor/common/model/tokenizationTextModelPart.ts
+++ b/src/vs/editor/common/model/tokenizationTextModelPart.ts
@@ -485,7 +485,7 @@ export class TokenizationTextModelPart extends TextModelPart implements ITokeniz
return lineTokens.getLanguageId(lineTokens.findTokenIndexAtOffset(position.column - 1));
}
- public setLanguageId(languageId: string): void {
+ public setLanguageId(languageId: string, source: string = 'api'): void {
if (this._languageId === languageId) {
// There's nothing to do
return;
@@ -493,7 +493,8 @@ export class TokenizationTextModelPart extends TextModelPart implements ITokeniz
const e: IModelLanguageChangedEvent = {
oldLanguage: this._languageId,
- newLanguage: languageId
+ newLanguage: languageId,
+ source
};
this._languageId = languageId;
diff --git a/src/vs/editor/common/services/model.ts b/src/vs/editor/common/services/model.ts
index 037ac0ca270..88cc4606c7e 100644
--- a/src/vs/editor/common/services/model.ts
+++ b/src/vs/editor/common/services/model.ts
@@ -22,7 +22,7 @@ export interface IModelService {
updateModel(model: ITextModel, value: string | ITextBufferFactory): void;
- setMode(model: ITextModel, languageSelection: ILanguageSelection): void;
+ setMode(model: ITextModel, languageSelection: ILanguageSelection, source?: string): void;
destroyModel(resource: URI): void;
diff --git a/src/vs/editor/common/services/modelService.ts b/src/vs/editor/common/services/modelService.ts
index aba06a6a51f..c03821b7b0c 100644
--- a/src/vs/editor/common/services/modelService.ts
+++ b/src/vs/editor/common/services/modelService.ts
@@ -91,11 +91,11 @@ class ModelData implements IDisposable {
this._disposeLanguageSelection();
}
- public setLanguage(languageSelection: ILanguageSelection): void {
+ public setLanguage(languageSelection: ILanguageSelection, source?: string): void {
this._disposeLanguageSelection();
this._languageSelection = languageSelection;
- this._languageSelectionListener = this._languageSelection.onDidChange(() => this.model.setMode(languageSelection.languageId));
- this.model.setMode(languageSelection.languageId);
+ this._languageSelectionListener = this._languageSelection.onDidChange(() => this.model.setMode(languageSelection.languageId, source));
+ this.model.setMode(languageSelection.languageId, source);
}
}
@@ -516,7 +516,7 @@ export class ModelService extends Disposable implements IModelService {
return modelData.model;
}
- public setMode(model: ITextModel, languageSelection: ILanguageSelection): void {
+ public setMode(model: ITextModel, languageSelection: ILanguageSelection, source?: string): void {
if (!languageSelection) {
return;
}
@@ -524,7 +524,7 @@ export class ModelService extends Disposable implements IModelService {
if (!modelData) {
return;
}
- modelData.setLanguage(languageSelection);
+ modelData.setLanguage(languageSelection, source);
}
public destroyModel(resource: URI): void {
diff --git a/src/vs/editor/common/textModelEvents.ts b/src/vs/editor/common/textModelEvents.ts
index aefa7dca03b..a2f38b12900 100644
--- a/src/vs/editor/common/textModelEvents.ts
+++ b/src/vs/editor/common/textModelEvents.ts
@@ -19,6 +19,11 @@ export interface IModelLanguageChangedEvent {
* New language
*/
readonly newLanguage: string;
+
+ /**
+ * Source of the call that caused the event.
+ */
+ readonly source: string;
}
/**
diff --git a/src/vs/editor/common/tokenizationTextModelPart.ts b/src/vs/editor/common/tokenizationTextModelPart.ts
index 6c8e7c322ed..bade56184c8 100644
--- a/src/vs/editor/common/tokenizationTextModelPart.ts
+++ b/src/vs/editor/common/tokenizationTextModelPart.ts
@@ -95,7 +95,7 @@ export interface ITokenizationTextModelPart {
getLanguageId(): string;
getLanguageIdAtPosition(lineNumber: number, column: number): string;
- setLanguageId(languageId: string): void;
+ setLanguageId(languageId: string, source?: string): void;
readonly backgroundTokenizationState: BackgroundTokenizationState;
readonly onBackgroundTokenizationStateChanged: Event<void>;
diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionKeybindingResolver.ts b/src/vs/editor/contrib/codeAction/browser/codeActionKeybindingResolver.ts
new file mode 100644
index 00000000000..b65d75bc5b2
--- /dev/null
+++ b/src/vs/editor/contrib/codeAction/browser/codeActionKeybindingResolver.ts
@@ -0,0 +1,90 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { ResolvedKeybinding } from 'vs/base/common/keybindings';
+import { Lazy } from 'vs/base/common/lazy';
+import { CodeAction } from 'vs/editor/common/languages';
+import { codeActionCommandId, fixAllCommandId, organizeImportsCommandId, refactorCommandId, sourceActionCommandId } from 'vs/editor/contrib/codeAction/browser/codeAction';
+import { CodeActionAutoApply, CodeActionCommandArgs, CodeActionKind } from 'vs/editor/contrib/codeAction/browser/types';
+import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
+
+export interface ResolveCodeActionKeybinding {
+ readonly kind: CodeActionKind;
+ readonly preferred: boolean;
+ readonly resolvedKeybinding: ResolvedKeybinding;
+}
+
+export class CodeActionKeybindingResolver {
+ private static readonly codeActionCommands: readonly string[] = [
+ refactorCommandId,
+ codeActionCommandId,
+ sourceActionCommandId,
+ organizeImportsCommandId,
+ fixAllCommandId
+ ];
+
+ constructor(
+ private readonly keybindingService: IKeybindingService
+ ) { }
+
+ public getResolver(): (action: CodeAction) => ResolvedKeybinding | undefined {
+ // Lazy since we may not actually ever read the value
+ const allCodeActionBindings = new Lazy<readonly ResolveCodeActionKeybinding[]>(() => this.keybindingService.getKeybindings()
+ .filter(item => CodeActionKeybindingResolver.codeActionCommands.indexOf(item.command!) >= 0)
+ .filter(item => item.resolvedKeybinding)
+ .map((item): ResolveCodeActionKeybinding => {
+ // Special case these commands since they come built-in with VS Code and don't use 'commandArgs'
+ let commandArgs = item.commandArgs;
+ if (item.command === organizeImportsCommandId) {
+ commandArgs = { kind: CodeActionKind.SourceOrganizeImports.value };
+ } else if (item.command === fixAllCommandId) {
+ commandArgs = { kind: CodeActionKind.SourceFixAll.value };
+ }
+
+ return {
+ resolvedKeybinding: item.resolvedKeybinding!,
+ ...CodeActionCommandArgs.fromUser(commandArgs, {
+ kind: CodeActionKind.None,
+ apply: CodeActionAutoApply.Never
+ })
+ };
+ }));
+
+ return (action) => {
+ if (action.kind) {
+ const binding = this.bestKeybindingForCodeAction(action, allCodeActionBindings.getValue());
+ return binding?.resolvedKeybinding;
+ }
+ return undefined;
+ };
+ }
+
+ private bestKeybindingForCodeAction(
+ action: CodeAction,
+ candidates: readonly ResolveCodeActionKeybinding[]
+ ): ResolveCodeActionKeybinding | undefined {
+ if (!action.kind) {
+ return undefined;
+ }
+ const kind = new CodeActionKind(action.kind);
+
+ return candidates
+ .filter(candidate => candidate.kind.contains(kind))
+ .filter(candidate => {
+ if (candidate.preferred) {
+ // If the candidate keybinding only applies to preferred actions, the this action must also be preferred
+ return action.isPreferred;
+ }
+ return true;
+ })
+ .reduceRight((currentBest, candidate) => {
+ if (!currentBest) {
+ return candidate;
+ }
+ // Select the more specific binding
+ return currentBest.kind.contains(candidate.kind) ? candidate : currentBest;
+ }, undefined as ResolveCodeActionKeybinding | undefined);
+ }
+}
diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts
index c01c0f51c43..f51c051b10b 100644
--- a/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts
+++ b/src/vs/editor/contrib/codeAction/browser/codeActionMenu.ts
@@ -12,17 +12,15 @@ import { IListEvent, IListMouseEvent, IListRenderer } from 'vs/base/browser/ui/l
import { List } from 'vs/base/browser/ui/list/listWidget';
import { IAction } from 'vs/base/common/actions';
import { Codicon } from 'vs/base/common/codicons';
-import { ResolvedKeybinding } from 'vs/base/common/keybindings';
-import { Lazy } from 'vs/base/common/lazy';
import { Disposable, DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
import { OS } from 'vs/base/common/platform';
import 'vs/css!./media/action';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
-import { CodeAction, Command } from 'vs/editor/common/languages';
+import { Command } from 'vs/editor/common/languages';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
-import { codeActionCommandId, CodeActionItem, CodeActionSet, fixAllCommandId, organizeImportsCommandId, refactorCommandId, sourceActionCommandId } from 'vs/editor/contrib/codeAction/browser/codeAction';
-import { CodeActionAutoApply, CodeActionCommandArgs, CodeActionKind, CodeActionTrigger, CodeActionTriggerSource } from 'vs/editor/contrib/codeAction/browser/types';
+import { CodeActionItem, CodeActionSet } from 'vs/editor/contrib/codeAction/browser/codeAction';
+import { CodeActionKind, CodeActionTrigger, CodeActionTriggerSource } from 'vs/editor/contrib/codeAction/browser/types';
import 'vs/editor/contrib/symbolIcons/browser/symbolIcons'; // The codicon symbol colors are defined here and must be loaded to get colors
import { localize } from 'vs/nls';
import { ICommandService } from 'vs/platform/commands/common/commands';
@@ -31,6 +29,7 @@ import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/cont
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
+import { CodeActionKeybindingResolver } from './codeActionKeybindingResolver';
export const Context = {
Visible: new RawContextKey<boolean>('codeActionMenuVisible', false, localize('codeActionMenuVisible', "Whether the code action list widget is visible"))
@@ -43,16 +42,6 @@ interface CodeActionWidgetDelegate {
onSelectCodeAction: (action: CodeActionItem, trigger: CodeActionTrigger) => Promise<any>;
}
-interface ResolveCodeActionKeybinding {
- readonly kind: CodeActionKind;
- readonly preferred: boolean;
- readonly resolvedKeybinding: ResolvedKeybinding;
-}
-
-function stripNewlines(str: string): string {
- return str.replace(/\r\n|\r|\n/g, ' ');
-}
-
export interface CodeActionShowOptions {
readonly includeDisabledActions: boolean;
readonly fromLightbulb?: boolean;
@@ -66,13 +55,12 @@ enum CodeActionListItemKind {
interface CodeActionListItemCodeAction {
readonly kind: CodeActionListItemKind.CodeAction;
readonly action: CodeActionItem;
- readonly index: number;
+ readonly group: CodeActionGroup;
}
interface CodeActionListItemHeader {
readonly kind: CodeActionListItemKind.Header;
- readonly headerTitle: string;
- readonly index: number;
+ readonly group: CodeActionGroup;
}
type ICodeActionMenuItem = CodeActionListItemCodeAction | CodeActionListItemHeader;
@@ -84,6 +72,28 @@ interface ICodeActionMenuTemplateData {
readonly keybinding: KeybindingLabel;
}
+function stripNewlines(str: string): string {
+ return str.replace(/\r\n|\r|\n/g, ' ');
+}
+
+interface CodeActionGroup {
+ readonly kind: CodeActionKind;
+ readonly title: string;
+ readonly icon: Codicon;
+ readonly iconColor?: string;
+}
+
+const uncategorizedCodeActionGroup = Object.freeze<CodeActionGroup>({ kind: CodeActionKind.Empty, title: localize('codeAction.widget.id.more', 'More Actions...'), icon: Codicon.lightBulb, iconColor: 'var(--vscode-editorLightBulb-foreground)' });
+
+const codeActionGroups = Object.freeze<CodeActionGroup[]>([
+ { kind: CodeActionKind.QuickFix, title: localize('codeAction.widget.id.quickfix', 'Quick Fix...'), icon: Codicon.lightBulb, },
+ { kind: CodeActionKind.Extract, title: localize('codeAction.widget.id.extract', 'Extract...'), icon: Codicon.wrench, },
+ { kind: CodeActionKind.Convert, title: localize('codeAction.widget.id.convert', 'Convert...'), icon: Codicon.zap, iconColor: 'var(--vscode-editorLightBulbAutoFix-foreground)' },
+ { kind: CodeActionKind.SurroundWith, title: localize('codeAction.widget.id.surround', 'Surround With...'), icon: Codicon.symbolArray, },
+ { kind: CodeActionKind.Source, title: localize('codeAction.widget.id.source', 'Source Action...'), icon: Codicon.lightBulb, iconColor: 'var(--vscode-editorLightBulb-foreground)' },
+ uncategorizedCodeActionGroup,
+]);
+
class CodeActionItemRenderer implements IListRenderer<CodeActionListItemCodeAction, ICodeActionMenuTemplateData> {
constructor(
private readonly keybindingResolver: CodeActionKeybindingResolver,
@@ -109,22 +119,8 @@ class CodeActionItemRenderer implements IListRenderer<CodeActionListItemCodeActi
}
renderElement(element: CodeActionListItemCodeAction, _index: number, data: ICodeActionMenuTemplateData): void {
- // Icons and Label modification based on group
- const kind = element.action.action.kind ? new CodeActionKind(element.action.action.kind) : CodeActionKind.None;
- if (CodeActionKind.SurroundWith.contains(kind)) {
- data.icon.className = Codicon.symbolArray.classNames;
- } else if (CodeActionKind.Extract.contains(kind)) {
- data.icon.className = Codicon.wrench.classNames;
- } else if (CodeActionKind.Convert.contains(kind)) {
- data.icon.className = Codicon.zap.classNames;
- data.icon.style.color = `var(--vscode-editorLightBulbAutoFix-foreground)`;
- } else if (CodeActionKind.QuickFix.contains(kind)) {
- data.icon.className = Codicon.lightBulb.classNames;
- data.icon.style.color = `var(--vscode-editorLightBulb-foreground)`;
- } else {
- data.icon.className = Codicon.lightBulb.classNames;
- data.icon.style.color = `var(--vscode-editorLightBulb-foreground)`;
- }
+ data.icon.className = element.group.icon.classNames;
+ data.icon.style.color = element.group.iconColor ?? '';
data.text.textContent = stripNewlines(element.action.action.title);
@@ -136,21 +132,11 @@ class CodeActionItemRenderer implements IListRenderer<CodeActionListItemCodeActi
dom.show(data.keybinding.element);
}
- // Check if action has disabled reason
if (element.action.action.disabled) {
data.container.title = element.action.action.disabled;
- } else {
- const updateLabel = () => {
- data.container.title = localize({ key: 'label', comment: ['placeholders are keybindings, e.g "F2 to Apply, Shift+F2 to Preview"'] }, "{0} to Apply, {1} to Preview", this.keybindingService.lookupKeybinding(acceptSelectedCodeActionCommand)?.getLabel(), this.keybindingService.lookupKeybinding(previewSelectedCodeActionCommand)?.getLabel());
- };
- updateLabel();
- }
-
- if (element.action.action.disabled) {
data.container.classList.add('option-disabled');
- data.container.style.backgroundColor = 'transparent !important';
- data.icon.style.opacity = '0.4';
} else {
+ data.container.title = localize({ key: 'label', comment: ['placeholders are keybindings, e.g "F2 to Apply, Shift+F2 to Preview"'] }, "{0} to Apply, {1} to Preview", this.keybindingService.lookupKeybinding(acceptSelectedCodeActionCommand)?.getLabel(), this.keybindingService.lookupKeybinding(previewSelectedCodeActionCommand)?.getLabel());
data.container.classList.remove('option-disabled');
}
}
@@ -179,7 +165,7 @@ class HeaderRenderer implements IListRenderer<CodeActionListItemHeader, HeaderTe
}
renderElement(element: CodeActionListItemHeader, _index: number, templateData: HeaderTemplateData): void {
- templateData.text.textContent = element.headerTitle;
+ templateData.text.textContent = element.group.title;
}
disposeTemplate(_templateData: HeaderTemplateData): void {
@@ -197,9 +183,6 @@ class CodeActionList extends Disposable {
private readonly list: List<ICodeActionMenuItem>;
private readonly allMenuItems: ICodeActionMenuItem[];
- private readonly viewItems: readonly CodeActionListItemCodeAction[];
- private focusedEnabledItem?: number;
- private currSelectedItem?: number;
constructor(
codeActions: readonly CodeActionItem[],
@@ -220,7 +203,6 @@ class CodeActionList extends Disposable {
new HeaderRenderer(),
], {
keyboardSupport: false,
- mouseSupport: false,
accessibilityProvider: {
getAriaLabel: element => {
if (element.kind === CodeActionListItemKind.CodeAction) {
@@ -244,14 +226,9 @@ class CodeActionList extends Disposable {
this._register(this.list.onDidChangeSelection(e => this.onListSelection(e)));
this.allMenuItems = this.toMenuItems(codeActions, showHeaders);
- this.viewItems = this.allMenuItems.filter(item => item.kind === CodeActionListItemKind.CodeAction && !item.action.action.disabled) as CodeActionListItemCodeAction[];
this.list.splice(0, this.list.length, this.allMenuItems);
- if (this.viewItems.length >= 1) {
- this.focusedEnabledItem = 0;
- this.currSelectedItem = this.viewItems[0].index;
- this.list.setFocus([this.currSelectedItem]);
- }
+ this.focusNext();
}
public layout(minWidth: number): number {
@@ -284,126 +261,75 @@ class CodeActionList extends Disposable {
}
public focusPrevious() {
- if (typeof this.focusedEnabledItem === 'undefined') {
- this.focusedEnabledItem = this.viewItems[0].index;
- } else if (this.viewItems.length < 1) {
- return;
- }
-
- const startIndex = this.focusedEnabledItem;
- let item: ICodeActionMenuItem;
-
- do {
- this.focusedEnabledItem = this.focusedEnabledItem - 1;
- if (this.focusedEnabledItem < 0) {
- this.focusedEnabledItem = this.viewItems.length - 1;
- }
- item = this.viewItems[this.focusedEnabledItem];
- this.list.setFocus([item.index]);
- this.currSelectedItem = item.index;
- } while (this.focusedEnabledItem !== startIndex && item.action.action.disabled);
+ this.list.focusPrevious(1, true, undefined, element => element.kind === CodeActionListItemKind.CodeAction && !element.action.action.disabled);
}
public focusNext() {
- if (typeof this.focusedEnabledItem === 'undefined') {
- this.focusedEnabledItem = this.viewItems.length - 1;
- } else if (this.viewItems.length < 1) {
+ this.list.focusNext(1, true, undefined, element => element.kind === CodeActionListItemKind.CodeAction && !element.action.action.disabled);
+ }
+
+ public acceptSelected() {
+ const focused = this.list.getFocus();
+ if (focused.length === 0) {
return;
}
- const startIndex = this.focusedEnabledItem;
- let item: ICodeActionMenuItem;
+ const focusIndex = focused[0];
+ const element = this.list.element(focusIndex);
+ if (element.kind !== CodeActionListItemKind.CodeAction || element.action.action.disabled) {
+ return;
+ }
- do {
- this.focusedEnabledItem = (this.focusedEnabledItem + 1) % this.viewItems.length;
- item = this.viewItems[this.focusedEnabledItem];
- this.list.setFocus([item.index]);
- this.currSelectedItem = item.index;
- } while (this.focusedEnabledItem !== startIndex && item.action.action.disabled);
+ this.list.setSelection([focusIndex]);
}
- public onEnterSet() {
- if (typeof this.currSelectedItem === 'number') {
- this.list.setSelection([this.currSelectedItem]);
+ private onListSelection(e: IListEvent<ICodeActionMenuItem>): void {
+ if (!e.elements.length) {
+ return;
}
- }
- private onListSelection(e: IListEvent<ICodeActionMenuItem>): void {
- for (const element of e.elements) {
- if (element.kind === CodeActionListItemKind.CodeAction && !element.action.action.disabled) {
- this.onDidSelect(element.action);
- }
+ const element = e.elements[0];
+ if (element.kind === CodeActionListItemKind.CodeAction && !element.action.action.disabled) {
+ this.onDidSelect(element.action);
+ } else {
+ this.list.setSelection([]);
}
}
private onListHover(e: IListMouseEvent<ICodeActionMenuItem>): void {
- if (!e.element) {
- this.currSelectedItem = undefined;
- this.list.setFocus([]);
- } else {
- if (e.element.kind === CodeActionListItemKind.CodeAction && !e.element.action.action.disabled) {
- this.list.setFocus([e.element.index]);
- this.focusedEnabledItem = this.viewItems.indexOf(e.element);
- this.currSelectedItem = e.element.index;
- } else {
- this.currSelectedItem = undefined;
- this.list.setFocus([e.element.index]);
- }
- }
+ this.list.setFocus(typeof e.index === 'number' ? [e.index] : []);
}
private onListClick(e: IListMouseEvent<ICodeActionMenuItem>): void {
if (e.element && e.element.kind === CodeActionListItemKind.CodeAction && e.element.action.action.disabled) {
- this.currSelectedItem = undefined;
this.list.setFocus([]);
}
}
private toMenuItems(inputCodeActions: readonly CodeActionItem[], showHeaders: boolean): ICodeActionMenuItem[] {
if (!showHeaders) {
- return inputCodeActions.map((action, index): ICodeActionMenuItem => ({ kind: CodeActionListItemKind.CodeAction, action, index }));
+ return inputCodeActions.map((action): ICodeActionMenuItem => ({ kind: CodeActionListItemKind.CodeAction, action, group: uncategorizedCodeActionGroup }));
}
- // Groups code actions by their kind
- const quickfixGroup: CodeActionItem[] = [];
- const extractGroup: CodeActionItem[] = [];
- const convertGroup: CodeActionItem[] = [];
- const surroundGroup: CodeActionItem[] = [];
- const sourceGroup: CodeActionItem[] = [];
- const otherGroup: CodeActionItem[] = [];
+ // Group code actions
+ const menuEntries = codeActionGroups.map(group => ({ group, actions: [] as CodeActionItem[] }));
for (const action of inputCodeActions) {
const kind = action.action.kind ? new CodeActionKind(action.action.kind) : CodeActionKind.None;
- if (CodeActionKind.SurroundWith.contains(kind)) {
- surroundGroup.push(action);
- } else if (CodeActionKind.QuickFix.contains(kind)) {
- quickfixGroup.push(action);
- } else if (CodeActionKind.Extract.contains(kind)) {
- extractGroup.push(action);
- } else if (CodeActionKind.Convert.contains(kind)) {
- convertGroup.push(action);
- } else if (CodeActionKind.Source.contains(kind)) {
- sourceGroup.push(action);
- } else {
- otherGroup.push(action);
+ for (const menuEntry of menuEntries) {
+ if (menuEntry.group.kind.contains(kind)) {
+ menuEntry.actions.push(action);
+ break;
+ }
}
}
- const menuEntries: ReadonlyArray<{ title: string; actions: CodeActionItem[] }> = [
- { title: localize('codeAction.widget.id.quickfix', 'Quick Fix...'), actions: quickfixGroup },
- { title: localize('codeAction.widget.id.extract', 'Extract...'), actions: extractGroup },
- { title: localize('codeAction.widget.id.convert', 'Convert...'), actions: convertGroup },
- { title: localize('codeAction.widget.id.surround', 'Surround With...'), actions: surroundGroup },
- { title: localize('codeAction.widget.id.source', 'Source Action...'), actions: sourceGroup },
- { title: localize('codeAction.widget.id.more', 'More Actions...'), actions: otherGroup },
- ];
-
const allMenuItems: ICodeActionMenuItem[] = [];
for (const menuEntry of menuEntries) {
if (menuEntry.actions.length) {
- allMenuItems.push({ kind: CodeActionListItemKind.Header, headerTitle: menuEntry.title, index: allMenuItems.length });
+ allMenuItems.push({ kind: CodeActionListItemKind.Header, group: menuEntry.group });
for (const action of menuEntry.actions) {
- allMenuItems.push({ kind: CodeActionListItemKind.CodeAction, action, index: allMenuItems.length });
+ allMenuItems.push({ kind: CodeActionListItemKind.CodeAction, action, group: menuEntry.group });
}
}
}
@@ -431,7 +357,8 @@ export class CodeActionMenu extends Disposable implements IEditorContribution {
readonly anchor: IAnchor;
readonly codeActions: CodeActionSet;
};
- private _ctxMenuWidgetVisible: IContextKey<boolean>;
+
+ private readonly _ctxMenuWidgetVisible: IContextKey<boolean>;
constructor(
private readonly _editor: ICodeEditor,
@@ -487,11 +414,11 @@ export class CodeActionMenu extends Disposable implements IEditorContribution {
this.codeActionList.value?.focusNext();
}
- public onEnterSet() {
- this.codeActionList.value?.onEnterSet();
+ public acceptSelected() {
+ this.codeActionList.value?.acceptSelected();
}
- public hideCodeActionWidget() {
+ public hide() {
this._ctxMenuWidgetVisible.reset();
this.codeActionList.clear();
this._contextViewService.hideContextView();
@@ -513,7 +440,7 @@ export class CodeActionMenu extends Disposable implements IEditorContribution {
showingCodeActions,
this.shouldShowHeaders(),
action => {
- this.hideCodeActionWidget();
+ this.hide();
this._delegate.onSelectCodeAction(action, trigger);
},
this._keybindingService);
@@ -563,12 +490,10 @@ export class CodeActionMenu extends Disposable implements IEditorContribution {
const width = this.codeActionList.value.layout(actionBarWidth);
widget.style.width = `${width}px`;
- renderDisposables.add(this._editor.onDidLayoutChange(() => this.hideCodeActionWidget()));
+ renderDisposables.add(this._editor.onDidLayoutChange(() => this.hide()));
const focusTracker = renderDisposables.add(dom.trackFocus(element));
- renderDisposables.add(focusTracker.onDidBlur(() => {
- this.hideCodeActionWidget();
- }));
+ renderDisposables.add(focusTracker.onDidBlur(() => this.hide()));
this._ctxMenuWidgetVisible.set(true);
@@ -581,7 +506,7 @@ export class CodeActionMenu extends Disposable implements IEditorContribution {
private toggleShowDisabled(newShowDisabled: boolean): void {
const previouslyShowingActions = this.currentShowingContext;
- this.hideCodeActionWidget();
+ this.hide();
showDisabled = newShowDisabled;
@@ -676,77 +601,3 @@ export class CodeActionMenu extends Disposable implements IEditorContribution {
}));
}
}
-
-export class CodeActionKeybindingResolver {
- private static readonly codeActionCommands: readonly string[] = [
- refactorCommandId,
- codeActionCommandId,
- sourceActionCommandId,
- organizeImportsCommandId,
- fixAllCommandId
- ];
-
- constructor(
- private readonly keybindingService: IKeybindingService,
- ) { }
-
- public getResolver(): (action: CodeAction) => ResolvedKeybinding | undefined {
- // Lazy since we may not actually ever read the value
- const allCodeActionBindings = new Lazy<readonly ResolveCodeActionKeybinding[]>(() =>
- this.keybindingService.getKeybindings()
- .filter(item => CodeActionKeybindingResolver.codeActionCommands.indexOf(item.command!) >= 0)
- .filter(item => item.resolvedKeybinding)
- .map((item): ResolveCodeActionKeybinding => {
- // Special case these commands since they come built-in with VS Code and don't use 'commandArgs'
- let commandArgs = item.commandArgs;
- if (item.command === organizeImportsCommandId) {
- commandArgs = { kind: CodeActionKind.SourceOrganizeImports.value };
- } else if (item.command === fixAllCommandId) {
- commandArgs = { kind: CodeActionKind.SourceFixAll.value };
- }
-
- return {
- resolvedKeybinding: item.resolvedKeybinding!,
- ...CodeActionCommandArgs.fromUser(commandArgs, {
- kind: CodeActionKind.None,
- apply: CodeActionAutoApply.Never
- })
- };
- }));
-
- return (action) => {
- if (action.kind) {
- const binding = this.bestKeybindingForCodeAction(action, allCodeActionBindings.getValue());
- return binding?.resolvedKeybinding;
- }
- return undefined;
- };
- }
-
- private bestKeybindingForCodeAction(
- action: CodeAction,
- candidates: readonly ResolveCodeActionKeybinding[],
- ): ResolveCodeActionKeybinding | undefined {
- if (!action.kind) {
- return undefined;
- }
- const kind = new CodeActionKind(action.kind);
-
- return candidates
- .filter(candidate => candidate.kind.contains(kind))
- .filter(candidate => {
- if (candidate.preferred) {
- // If the candidate keybinding only applies to preferred actions, the this action must also be preferred
- return action.isPreferred;
- }
- return true;
- })
- .reduceRight((currentBest, candidate) => {
- if (!currentBest) {
- return candidate;
- }
- // Select the more specific binding
- return currentBest.kind.contains(candidate.kind) ? candidate : currentBest;
- }, undefined as ResolveCodeActionKeybinding | undefined);
- }
-}
diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts b/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts
index 5d692619870..d5af03fc557 100644
--- a/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts
+++ b/src/vs/editor/contrib/codeAction/browser/codeActionUi.ts
@@ -66,11 +66,11 @@ export class CodeActionUi extends Disposable {
}
public hideCodeActionWidget() {
- this._codeActionWidget.rawValue?.hideCodeActionWidget();
+ this._codeActionWidget.rawValue?.hide();
}
public onEnter() {
- this._codeActionWidget.rawValue?.onEnterSet();
+ this._codeActionWidget.rawValue?.acceptSelected();
}
public onPreviewEnter() {
diff --git a/src/vs/editor/contrib/codeAction/browser/media/action.css b/src/vs/editor/contrib/codeAction/browser/media/action.css
index a2ed5cc2470..2561c7f4f04 100644
--- a/src/vs/editor/contrib/codeAction/browser/media/action.css
+++ b/src/vs/editor/contrib/codeAction/browser/media/action.css
@@ -3,21 +3,6 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-.codeActionWidget .monaco-list:not(.element-focused):focus:before {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- z-index: 5; /* make sure we are on top of the tree items */
- content: "";
- pointer-events: none; /* enable click through */
- outline: 0 solid !important; /* we still need to handle the empty tree or no focus item case */
- outline-width: 0 !important;
- outline-style: none;
- outline-offset: 0;
-}
-
.codeActionWidget {
font-size: 13px;
border-radius: 0;
@@ -26,11 +11,11 @@
z-index: 40;
display: block;
width: 100%;
- border: 1px solid var(--vscode-menu-separatorBackground) !important;
+ border: 1px solid var(--vscode-editorWidget-border) !important;
border-color: none;
- background-color: var(--vscode-menu-background);
- color: var(--vscode-menu-foreground);
- box-shadow: rgb(0,0,0, 16%) 0 2px 8px;
+ background-color: var(--vscode-editorWidget-background);
+ color: var(--vscode--editorWidget-foreground);
+ box-shadow: var(--vscode-widget-shadow) 0 2px 8px;
}
.codeActionWidget .monaco-list {
@@ -57,26 +42,15 @@
width: 100%;
}
-.codeActionWidget .monaco-list .monaco-list-row.code-action:hover:not(.option-disabled),
-.codeActionWidget .monaco-list .monaco-list-row.code-action.focused:not(.option-disabled) {
- background-color: var(--vscode-list-hoverBackground) !important;
- color: var(--vscode-list-activeSelectionForeground) !important;
-}
-
-.codeActionWidget .monaco-list .monaco-list-row.code-action:hover:not(.option-disabled) {
- background-color: var(--vscode-list-hoverBackground) !important;
- outline: 1px dashed var(--vscode-menu-selectionBorder, transparent);
- outline-offset: -1px;
-}
-
.codeActionWidget .monaco-list .monaco-list-row.code-action.focused:not(.option-disabled) {
- background-color: var(--vscode-menu-selectionBackground) !important;
+ background-color: var(--vscode-quickInputList-focusBackground);
+ color: var(--vscode-quickInputList-focusForeground);
outline: 1px solid var(--vscode-menu-selectionBorder, transparent);
outline-offset: -1px;
}
.codeActionWidget .monaco-list-row.group-header {
- color: var(--vscode-textLink-activeForeground);
+ color: var(--vscode-pickerGroup-foreground);
font-weight: bold;
}
@@ -92,7 +66,7 @@
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
- background-color: var(--vscode-menu-background) !important;
+ background-color: transparent !important;
outline: 0 solid !important;
}
@@ -106,6 +80,10 @@
color: var(--vscode-disabledForeground);
}
+.codeActionWidget .monaco-list-row.code-action.option-disabled .codicon {
+ opacity: 0.4;
+}
+
.codeActionWidget .monaco-list-row.code-action:not(.option-disabled) .codicon {
color: inherit;
}
diff --git a/src/vs/editor/contrib/codeAction/test/browser/codeActionKeybindingResolver.test.ts b/src/vs/editor/contrib/codeAction/test/browser/codeActionKeybindingResolver.test.ts
index 27138dce76f..f25921dc01f 100644
--- a/src/vs/editor/contrib/codeAction/test/browser/codeActionKeybindingResolver.test.ts
+++ b/src/vs/editor/contrib/codeAction/test/browser/codeActionKeybindingResolver.test.ts
@@ -4,15 +4,15 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
-import { KeyCode } from 'vs/base/common/keyCodes';
import { ChordKeybinding, SimpleKeybinding } from 'vs/base/common/keybindings';
+import { KeyCode } from 'vs/base/common/keyCodes';
import { OperatingSystem } from 'vs/base/common/platform';
import { organizeImportsCommandId, refactorCommandId } from 'vs/editor/contrib/codeAction/browser/codeAction';
-import { CodeActionKeybindingResolver } from 'vs/editor/contrib/codeAction/browser/codeActionMenu';
+import { CodeActionKeybindingResolver } from 'vs/editor/contrib/codeAction/browser/codeActionKeybindingResolver';
import { CodeActionKind } from 'vs/editor/contrib/codeAction/browser/types';
+import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem';
import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding';
-import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
suite('CodeActionKeybindingResolver', () => {
const refactorKeybinding = createCodeActionKeybinding(
diff --git a/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts b/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts
index 87d5a93b21e..cc7d0b8c67a 100644
--- a/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts
+++ b/src/vs/editor/contrib/gotoSymbol/browser/goToCommands.ts
@@ -19,7 +19,7 @@ import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeE
import { EditorOption, GoToLocationValues } from 'vs/editor/common/config/editorOptions';
import * as corePosition from 'vs/editor/common/core/position';
import { IRange, Range } from 'vs/editor/common/core/range';
-import { IEditorAction, ScrollType } from 'vs/editor/common/editorCommon';
+import { ScrollType } from 'vs/editor/common/editorCommon';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { ITextModel } from 'vs/editor/common/model';
import { isLocationLink, Location, LocationLink } from 'vs/editor/common/languages';
@@ -56,9 +56,6 @@ export interface SymbolNavigationActionConfig {
muteMessage: boolean;
}
-
-
-
export class SymbolNavigationAnchor {
static is(thing: any): thing is SymbolNavigationAnchor {
@@ -79,7 +76,7 @@ export class SymbolNavigationAnchor {
export abstract class SymbolNavigationAction extends EditorAction2 {
- private static _allSymbolNavigationCommands = new Set<string>();
+ private static _allSymbolNavigationCommands = new Map<string, SymbolNavigationAction>();
private static _activeAlternativeCommands = new Set<string>();
readonly configuration: SymbolNavigationActionConfig;
@@ -101,7 +98,7 @@ export abstract class SymbolNavigationAction extends EditorAction2 {
constructor(configuration: SymbolNavigationActionConfig, opts: IAction2Options) {
super(SymbolNavigationAction.aaa(opts));
this.configuration = configuration;
- SymbolNavigationAction._allSymbolNavigationCommands.add(opts.id);
+ SymbolNavigationAction._allSymbolNavigationCommands.set(opts.id, this);
}
override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, arg?: SymbolNavigationAnchor | unknown, range?: Range): Promise<void> {
@@ -113,6 +110,7 @@ export abstract class SymbolNavigationAction extends EditorAction2 {
const progressService = accessor.get(IEditorProgressService);
const symbolNavService = accessor.get(ISymbolNavigationService);
const languageFeaturesService = accessor.get(ILanguageFeaturesService);
+ const instaService = accessor.get(IInstantiationService);
const model = editor.getModel();
const position = editor.getPosition();
@@ -128,11 +126,11 @@ export abstract class SymbolNavigationAction extends EditorAction2 {
alert(references.ariaMessage);
- let altAction: IEditorAction | null | undefined;
+ let altAction: SymbolNavigationAction | null | undefined;
if (references.referenceAt(model.uri, position)) {
const altActionId = this._getAlternativeCommand(editor);
if (!SymbolNavigationAction._activeAlternativeCommands.has(altActionId) && SymbolNavigationAction._allSymbolNavigationCommands.has(altActionId)) {
- altAction = editor.getAction(altActionId);
+ altAction = SymbolNavigationAction._allSymbolNavigationCommands.get(altActionId)!;
}
}
@@ -147,9 +145,9 @@ export abstract class SymbolNavigationAction extends EditorAction2 {
} else if (referenceCount === 1 && altAction) {
// already at the only result, run alternative
SymbolNavigationAction._activeAlternativeCommands.add(this.desc.id);
- altAction.run().finally(() => {
+ instaService.invokeFunction((accessor) => altAction!.runEditorCommand(accessor, editor, arg, range).finally(() => {
SymbolNavigationAction._activeAlternativeCommands.delete(this.desc.id);
- });
+ }));
} else {
// normal results handling
diff --git a/src/vs/editor/test/browser/controller/imeTester.ts b/src/vs/editor/test/browser/controller/imeTester.ts
index 11ce1dc78c9..e0e94e35664 100644
--- a/src/vs/editor/test/browser/controller/imeTester.ts
+++ b/src/vs/editor/test/browser/controller/imeTester.ts
@@ -34,6 +34,10 @@ class SingleLineTestModel implements ISimpleModel {
return this._line.substring(range.startColumn - 1, range.endColumn - 1);
}
+ getValueLengthInRange(range: Range): number {
+ return this.getValueInRange(range, EndOfLinePreference.TextDefined).length;
+ }
+
getModelLineContent(lineNumber: number): string {
return this._line;
}
diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts
index 2259a4f522d..8736a56f0b9 100644
--- a/src/vs/monaco.d.ts
+++ b/src/vs/monaco.d.ts
@@ -2634,6 +2634,10 @@ declare namespace monaco.editor {
* New language
*/
readonly newLanguage: string;
+ /**
+ * Source of the call that caused the event.
+ */
+ readonly source: string;
}
/**
diff --git a/src/vs/platform/backup/electron-main/backup.ts b/src/vs/platform/backup/electron-main/backup.ts
index a729ef372a4..81ab8756fee 100644
--- a/src/vs/platform/backup/electron-main/backup.ts
+++ b/src/vs/platform/backup/electron-main/backup.ts
@@ -17,7 +17,8 @@ export interface IBackupMainService {
getEmptyWindowBackups(): IEmptyWindowBackupInfo[];
- registerWorkspaceBackup(workspaceInfo: IWorkspaceBackupInfo, migrateFrom?: string): string;
+ registerWorkspaceBackup(workspaceInfo: IWorkspaceBackupInfo): string;
+ registerWorkspaceBackup(workspaceInfo: IWorkspaceBackupInfo, migrateFrom: string): Promise<string>;
registerFolderBackup(folderInfo: IFolderBackupInfo): string;
registerEmptyWindowBackup(emptyWindowInfo: IEmptyWindowBackupInfo): string;
diff --git a/src/vs/platform/backup/electron-main/backupMainService.ts b/src/vs/platform/backup/electron-main/backupMainService.ts
index 4b38dd8688a..ed16c44eee1 100644
--- a/src/vs/platform/backup/electron-main/backupMainService.ts
+++ b/src/vs/platform/backup/electron-main/backupMainService.ts
@@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import { createHash } from 'crypto';
-import * as fs from 'fs';
import { isEqual } from 'vs/base/common/extpath';
import { Schemas } from 'vs/base/common/network';
import { join } from 'vs/base/common/path';
@@ -134,7 +133,9 @@ export class BackupMainService implements IBackupMainService {
return this.emptyWindows.slice(0); // return a copy
}
- registerWorkspaceBackup(workspaceInfo: IWorkspaceBackupInfo, migrateFrom?: string): string {
+ registerWorkspaceBackup(workspaceInfo: IWorkspaceBackupInfo): string;
+ registerWorkspaceBackup(workspaceInfo: IWorkspaceBackupInfo, migrateFrom: string): Promise<string>;
+ registerWorkspaceBackup(workspaceInfo: IWorkspaceBackupInfo, migrateFrom?: string): string | Promise<string> {
if (!this.workspaces.some(workspace => workspaceInfo.workspace.id === workspace.workspace.id)) {
this.workspaces.push(workspaceInfo);
this.storeWorkspacesMetadata();
@@ -143,23 +144,23 @@ export class BackupMainService implements IBackupMainService {
const backupPath = join(this.backupHome, workspaceInfo.workspace.id);
if (migrateFrom) {
- this.moveBackupFolderSync(backupPath, migrateFrom);
+ return this.moveBackupFolder(backupPath, migrateFrom).then(() => backupPath);
}
return backupPath;
}
- private moveBackupFolderSync(backupPath: string, moveFromPath: string): void {
+ private async moveBackupFolder(backupPath: string, moveFromPath: string): Promise<void> {
// Target exists: make sure to convert existing backups to empty window backups
- if (fs.existsSync(backupPath)) {
- this.convertToEmptyWindowBackupSync(backupPath);
+ if (await Promises.exists(backupPath)) {
+ await this.convertToEmptyWindowBackup(backupPath);
}
// When we have data to migrate from, move it over to the target location
- if (fs.existsSync(moveFromPath)) {
+ if (await Promises.exists(moveFromPath)) {
try {
- fs.renameSync(moveFromPath, backupPath);
+ await Promises.rename(moveFromPath, backupPath);
} catch (error) {
this.logService.error(`Backup: Could not move backup folder to new location: ${error.toString()}`);
}
@@ -324,22 +325,6 @@ export class BackupMainService implements IBackupMainService {
return true;
}
- private convertToEmptyWindowBackupSync(backupPath: string): boolean {
- const newEmptyWindowBackupInfo = this.prepareNewEmptyWindowBackup();
-
- // Rename backupPath to new empty window backup path
- const newEmptyWindowBackupPath = join(this.backupHome, newEmptyWindowBackupInfo.backupFolder);
- try {
- fs.renameSync(backupPath, newEmptyWindowBackupPath);
- } catch (error) {
- this.logService.error(`Backup: Could not rename backup folder: ${error.toString()}`);
- return false;
- }
- this.emptyWindows.push(newEmptyWindowBackupInfo);
-
- return true;
- }
-
async getDirtyWorkspaces(): Promise<Array<IWorkspaceBackupInfo | IFolderBackupInfo>> {
const dirtyWorkspaces: Array<IWorkspaceBackupInfo | IFolderBackupInfo> = [];
diff --git a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts
index 6a239744151..1f2e1729c22 100644
--- a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts
+++ b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts
@@ -328,13 +328,13 @@ flakySuite('BackupMainService', () => {
assert.strictEqual(service.getEmptyWindowBackups().length, 1);
});
- test('service supports to migrate backup data from another location', () => {
+ test('service supports to migrate backup data from another location', async () => {
const backupPathToMigrate = service.toBackupPath(fooFile);
fs.mkdirSync(backupPathToMigrate);
fs.writeFileSync(path.join(backupPathToMigrate, 'backup.txt'), 'Some Data');
service.registerFolderBackup(toFolderBackupInfo(URI.file(backupPathToMigrate)));
- const workspaceBackupPath = service.registerWorkspaceBackup(toWorkspaceBackupInfo(barFile.fsPath), backupPathToMigrate);
+ const workspaceBackupPath = await service.registerWorkspaceBackup(toWorkspaceBackupInfo(barFile.fsPath), backupPathToMigrate);
assert.ok(fs.existsSync(workspaceBackupPath));
assert.ok(fs.existsSync(path.join(workspaceBackupPath, 'backup.txt')));
@@ -344,7 +344,7 @@ flakySuite('BackupMainService', () => {
assert.strictEqual(0, emptyBackups.length);
});
- test('service backup migration makes sure to preserve existing backups', () => {
+ test('service backup migration makes sure to preserve existing backups', async () => {
const backupPathToMigrate = service.toBackupPath(fooFile);
fs.mkdirSync(backupPathToMigrate);
fs.writeFileSync(path.join(backupPathToMigrate, 'backup.txt'), 'Some Data');
@@ -355,7 +355,7 @@ flakySuite('BackupMainService', () => {
fs.writeFileSync(path.join(backupPathToPreserve, 'backup.txt'), 'Some Data');
service.registerFolderBackup(toFolderBackupInfo(URI.file(backupPathToPreserve)));
- const workspaceBackupPath = service.registerWorkspaceBackup(toWorkspaceBackupInfo(barFile.fsPath), backupPathToMigrate);
+ const workspaceBackupPath = await service.registerWorkspaceBackup(toWorkspaceBackupInfo(barFile.fsPath), backupPathToMigrate);
assert.ok(fs.existsSync(workspaceBackupPath));
assert.ok(fs.existsSync(path.join(workspaceBackupPath, 'backup.txt')));
diff --git a/src/vs/platform/contextkey/common/contextkeys.ts b/src/vs/platform/contextkey/common/contextkeys.ts
index de968735c0a..296245b4608 100644
--- a/src/vs/platform/contextkey/common/contextkeys.ts
+++ b/src/vs/platform/contextkey/common/contextkeys.ts
@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { isIOS, isLinux, isMacintosh, isWeb, isWindows } from 'vs/base/common/platform';
+import { isIOS, isLinux, isMacintosh, isMobile, isWeb, isWindows } from 'vs/base/common/platform';
import { localize } from 'vs/nls';
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
@@ -14,6 +14,7 @@ export const IsWindowsContext = new RawContextKey<boolean>('isWindows', isWindow
export const IsWebContext = new RawContextKey<boolean>('isWeb', isWeb, localize('isWeb', "Whether the platform is a web browser"));
export const IsMacNativeContext = new RawContextKey<boolean>('isMacNative', isMacintosh && !isWeb, localize('isMacNative', "Whether the operating system is macOS on a non-browser platform"));
export const IsIOSContext = new RawContextKey<boolean>('isIOS', isIOS, localize('isIOS', "Whether the operating system is iOS"));
+export const IsMobileContext = new RawContextKey<boolean>('isMobile', isMobile, localize('isMobile', "Whether the platform is a mobile web browser"));
export const IsDevelopmentContext = new RawContextKey<boolean>('isDevelopment', false, true);
export const ProductQualityContext = new RawContextKey<string>('productQualityType', '', localize('productQualityType', "Quality type of VS Code"));
diff --git a/src/vs/platform/credentials/common/credentialsMainService.ts b/src/vs/platform/credentials/common/credentialsMainService.ts
index c9615fe27d8..bc140432faf 100644
--- a/src/vs/platform/credentials/common/credentialsMainService.ts
+++ b/src/vs/platform/credentials/common/credentialsMainService.ts
@@ -8,6 +8,7 @@ import { Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { ILogService } from 'vs/platform/log/common/log';
import { isWindows } from 'vs/base/common/platform';
+import { retry } from 'vs/base/common/async';
interface ChunkedPassword {
content: string;
@@ -46,6 +47,7 @@ export abstract class BaseCredentialsMainService extends Disposable implements I
//#endregion
async getPassword(service: string, account: string): Promise<string | null> {
+ this.logService.trace('Getting password from keytar:', service, account);
let keytar: KeytarModule;
try {
keytar = await this.withKeytar();
@@ -54,33 +56,48 @@ export abstract class BaseCredentialsMainService extends Disposable implements I
return null;
}
- const password = await keytar.getPassword(service, account);
- if (password) {
- try {
- let { content, hasNextChunk }: ChunkedPassword = JSON.parse(password);
- if (!content || !hasNextChunk) {
- return password;
- }
+ const password = await retry(() => keytar.getPassword(service, account), 50, 3);
+ if (!password) {
+ this.logService.trace('Did not get a password from keytar for account:', account);
+ return password;
+ }
- let index = 1;
- while (hasNextChunk) {
- const nextChunk = await keytar.getPassword(service, `${account}-${index}`);
- const result: ChunkedPassword = JSON.parse(nextChunk!);
- content += result.content;
- hasNextChunk = result.hasNextChunk;
- index++;
- }
+ let content: string | undefined;
+ let hasNextChunk: boolean | undefined;
+ try {
+ const parsed: ChunkedPassword = JSON.parse(password);
+ content = parsed.content;
+ hasNextChunk = parsed.hasNextChunk;
+ } catch {
+ // Ignore this similar to how we ignore parse errors in the delete
+ // because on non-windows this will not be a JSON string.
+ }
- return content;
- } catch {
- return password;
- }
+ if (!content || !hasNextChunk) {
+ this.logService.trace('Got password from keytar for account:', account);
+ return password;
}
- return password;
+ try {
+ let index = 1;
+ while (hasNextChunk) {
+ const nextChunk = await retry(() => keytar.getPassword(service, `${account}-${index}`), 50, 3);
+ const result: ChunkedPassword = JSON.parse(nextChunk!);
+ content += result.content;
+ hasNextChunk = result.hasNextChunk;
+ index++;
+ }
+
+ this.logService.trace(`Got ${index}-chunked password from keytar for account:`, account);
+ return content;
+ } catch (e) {
+ this.logService.error(e);
+ return password;
+ }
}
async setPassword(service: string, account: string, password: string): Promise<void> {
+ this.logService.trace('Setting password using keytar:', service, account);
let keytar: KeytarModule;
try {
keytar = await this.withKeytar();
@@ -89,28 +106,6 @@ export abstract class BaseCredentialsMainService extends Disposable implements I
throw e;
}
- const MAX_SET_ATTEMPTS = 3;
-
- // Sometimes Keytar has a problem talking to the keychain on the OS. To be more resilient, we retry a few times.
- const setPasswordWithRetry = async (service: string, account: string, password: string) => {
- let attempts = 0;
- let error: any;
- while (attempts < MAX_SET_ATTEMPTS) {
- try {
- await keytar.setPassword(service, account, password);
- return;
- } catch (e) {
- error = e;
- this.logService.warn('Error attempting to set a password: ', e?.message ?? e);
- attempts++;
- await new Promise(resolve => setTimeout(resolve, 200));
- }
- }
-
- // throw last error
- throw error;
- };
-
if (isWindows && password.length > BaseCredentialsMainService.MAX_PASSWORD_LENGTH) {
let index = 0;
let chunk = 0;
@@ -124,19 +119,21 @@ export abstract class BaseCredentialsMainService extends Disposable implements I
content: passwordChunk,
hasNextChunk: hasNextChunk
};
-
- await setPasswordWithRetry(service, chunk ? `${account}-${chunk}` : account, JSON.stringify(content));
+ await retry(() => keytar.setPassword(service, chunk ? `${account}-${chunk}` : account, JSON.stringify(content)), 50, 3);
chunk++;
}
+ this.logService.trace(`Got${chunk ? ` ${chunk}-chunked` : ''} password from keytar for account:`, account);
} else {
- await setPasswordWithRetry(service, account, password);
+ await retry(() => keytar.setPassword(service, account, password), 50, 3);
+ this.logService.trace('Got password from keytar for account:', account);
}
this._onDidChangePassword.fire({ service, account });
}
async deletePassword(service: string, account: string): Promise<boolean> {
+ this.logService.trace('Deleting password using keytar:', service, account);
let keytar: KeytarModule;
try {
keytar = await this.withKeytar();
@@ -147,14 +144,30 @@ export abstract class BaseCredentialsMainService extends Disposable implements I
const password = await keytar.getPassword(service, account);
if (!password) {
+ this.logService.trace('Did not get a password to delete from keytar for account:', account);
return false;
}
- const didDelete = await keytar.deletePassword(service, account);
+
+ let content: string | undefined;
+ let hasNextChunk: boolean | undefined;
try {
- let { content, hasNextChunk }: ChunkedPassword = JSON.parse(password);
- if (content && hasNextChunk) {
+ const possibleChunk = JSON.parse(password);
+ content = possibleChunk.content;
+ hasNextChunk = possibleChunk.hasNextChunk;
+ } catch {
+ // When the password is saved the entire JSON payload is encrypted then stored, thus the result from getPassword might not be valid JSON
+ // https://github.com/microsoft/vscode/blob/c22cb87311b5eb1a3bf5600d18733f7485355dc0/src/vs/workbench/api/browser/mainThreadSecretState.ts#L83
+ // However in the chunked case we JSONify each chunk after encryption so for the chunked case we do expect valid JSON here
+ // https://github.com/microsoft/vscode/blob/708cb0c507d656b760f9d08115b8ebaf8964fd73/src/vs/platform/credentials/common/credentialsMainService.ts#L128
+ // Empty catch here just as in getPassword because we expect to handle both JSON cases and non JSON cases here it's not an error case to fail to parse
+ // https://github.com/microsoft/vscode/blob/708cb0c507d656b760f9d08115b8ebaf8964fd73/src/vs/platform/credentials/common/credentialsMainService.ts#L76
+ }
+
+ let index = 0;
+ if (content && hasNextChunk) {
+ try {
// need to delete additional chunks
- let index = 1;
+ index++;
while (hasNextChunk) {
const accountWithIndex = `${account}-${index}`;
const nextChunk = await keytar.getPassword(service, accountWithIndex);
@@ -164,21 +177,20 @@ export abstract class BaseCredentialsMainService extends Disposable implements I
hasNextChunk = result.hasNextChunk;
index++;
}
+ } catch (e) {
+ this.logService.error(e);
}
- } catch {
- // When the password is saved the entire JSON payload is encrypted then stored, thus the result from getPassword might not be valid JSON
- // https://github.com/microsoft/vscode/blob/c22cb87311b5eb1a3bf5600d18733f7485355dc0/src/vs/workbench/api/browser/mainThreadSecretState.ts#L83
- // However in the chunked case we JSONify each chunk after encryption so for the chunked case we do expect valid JSON here
- // https://github.com/microsoft/vscode/blob/708cb0c507d656b760f9d08115b8ebaf8964fd73/src/vs/platform/credentials/common/credentialsMainService.ts#L128
- // Empty catch here just as in getPassword because we expect to handle both JSON cases and non JSON cases here it's not an error case to fail to parse
- // https://github.com/microsoft/vscode/blob/708cb0c507d656b760f9d08115b8ebaf8964fd73/src/vs/platform/credentials/common/credentialsMainService.ts#L76
}
- if (didDelete) {
+ // Delete the first account to determine deletion success
+ if (await keytar.deletePassword(service, account)) {
this._onDidChangePassword.fire({ service, account });
+ this.logService.trace(`Deleted${index ? ` ${index}-chunked` : ''} password from keytar for account:`, account);
+ return true;
}
- return didDelete;
+ this.logService.trace(`Keytar failed to delete${index ? ` ${index}-chunked` : ''} password for account:`, account);
+ return false;
}
async findPassword(service: string): Promise<string | null> {
@@ -190,7 +202,7 @@ export abstract class BaseCredentialsMainService extends Disposable implements I
return null;
}
- return keytar.findPassword(service);
+ return await keytar.findPassword(service);
}
async findCredentials(service: string): Promise<Array<{ account: string; password: string }>> {
@@ -202,7 +214,7 @@ export abstract class BaseCredentialsMainService extends Disposable implements I
return [];
}
- return keytar.findCredentials(service);
+ return await keytar.findCredentials(service);
}
public clear(): Promise<void> {
diff --git a/src/vs/platform/debug/electron-main/extensionHostDebugIpc.ts b/src/vs/platform/debug/electron-main/extensionHostDebugIpc.ts
index 0b14a28f7fe..63adb055852 100644
--- a/src/vs/platform/debug/electron-main/extensionHostDebugIpc.ts
+++ b/src/vs/platform/debug/electron-main/extensionHostDebugIpc.ts
@@ -36,14 +36,10 @@ export class ElectronExtensionHostDebugBroadcastChannel<TContext> extends Extens
return { success: false };
}
- // Ensure profile exists when passed in from args
- const profilePromise = this.userDataProfilesMainService.checkAndCreateProfileFromCli(pargs);
- const profile = profilePromise ? await profilePromise : undefined;
-
- const [codeWindow] = this.windowsMainService.openExtensionDevelopmentHostWindow(extDevPaths, {
+ const [codeWindow] = await this.windowsMainService.openExtensionDevelopmentHostWindow(extDevPaths, {
context: OpenContext.API,
cli: pargs,
- profile
+ profile: await this.userDataProfilesMainService.checkAndCreateProfileFromCli(pargs)
});
if (!debugRenderer) {
diff --git a/src/vs/platform/diagnostics/electron-main/diagnosticsMainService.ts b/src/vs/platform/diagnostics/electron-main/diagnosticsMainService.ts
index 4d618da59dc..3d10b4b8a15 100644
--- a/src/vs/platform/diagnostics/electron-main/diagnosticsMainService.ts
+++ b/src/vs/platform/diagnostics/electron-main/diagnosticsMainService.ts
@@ -42,33 +42,33 @@ export class DiagnosticsMainService implements IDiagnosticsMainService {
async getRemoteDiagnostics(options: IRemoteDiagnosticOptions): Promise<(IRemoteDiagnosticInfo | IRemoteDiagnosticError)[]> {
const windows = this.windowsMainService.getWindows();
- const diagnostics: Array<IDiagnosticInfo | IRemoteDiagnosticError | undefined> = await Promise.all(windows.map(window => {
- return new Promise<IDiagnosticInfo | IRemoteDiagnosticError | undefined>((resolve) => {
- const remoteAuthority = window.remoteAuthority;
- if (remoteAuthority) {
- const replyChannel = `vscode:getDiagnosticInfoResponse${window.id}`;
- const args: IDiagnosticInfoOptions = {
- includeProcesses: options.includeProcesses,
- folders: options.includeWorkspaceMetadata ? this.getFolderURIs(window) : undefined
- };
-
- window.sendWhenReady('vscode:getDiagnosticInfo', CancellationToken.None, { replyChannel, args });
-
- validatedIpcMain.once(replyChannel, (_: IpcEvent, data: IRemoteDiagnosticInfo) => {
- // No data is returned if getting the connection fails.
- if (!data) {
- resolve({ hostName: remoteAuthority, errorMessage: `Unable to resolve connection to '${remoteAuthority}'.` });
- }
-
- resolve(data);
- });
-
- setTimeout(() => {
- resolve({ hostName: remoteAuthority, errorMessage: `Connection to '${remoteAuthority}' could not be established` });
- }, 5000);
- } else {
- resolve(undefined);
- }
+ const diagnostics: Array<IDiagnosticInfo | IRemoteDiagnosticError | undefined> = await Promise.all(windows.map(async window => {
+ const remoteAuthority = window.remoteAuthority;
+ if (!remoteAuthority) {
+ return undefined;
+ }
+
+ const replyChannel = `vscode:getDiagnosticInfoResponse${window.id}`;
+ const args: IDiagnosticInfoOptions = {
+ includeProcesses: options.includeProcesses,
+ folders: options.includeWorkspaceMetadata ? await this.getFolderURIs(window) : undefined
+ };
+
+ return new Promise<IDiagnosticInfo | IRemoteDiagnosticError>(resolve => {
+ window.sendWhenReady('vscode:getDiagnosticInfo', CancellationToken.None, { replyChannel, args });
+
+ validatedIpcMain.once(replyChannel, (_: IpcEvent, data: IRemoteDiagnosticInfo) => {
+ // No data is returned if getting the connection fails.
+ if (!data) {
+ resolve({ hostName: remoteAuthority, errorMessage: `Unable to resolve connection to '${remoteAuthority}'.` });
+ }
+
+ resolve(data);
+ });
+
+ setTimeout(() => {
+ resolve({ hostName: remoteAuthority, errorMessage: `Connection to '${remoteAuthority}' could not be established` });
+ }, 5000);
});
}));
@@ -82,7 +82,7 @@ export class DiagnosticsMainService implements IDiagnosticsMainService {
for (const window of BrowserWindow.getAllWindows()) {
const codeWindow = this.windowsMainService.getWindowById(window.id);
if (codeWindow) {
- windows.push(this.codeWindowToInfo(codeWindow));
+ windows.push(await this.codeWindowToInfo(codeWindow));
} else {
windows.push(this.browserWindowToInfo(window));
}
@@ -97,8 +97,8 @@ export class DiagnosticsMainService implements IDiagnosticsMainService {
};
}
- private codeWindowToInfo(window: ICodeWindow): IWindowDiagnostics {
- const folderURIs = this.getFolderURIs(window);
+ private async codeWindowToInfo(window: ICodeWindow): Promise<IWindowDiagnostics> {
+ const folderURIs = await this.getFolderURIs(window);
const win = assertIsDefined(window.win);
return this.browserWindowToInfo(win, folderURIs, window.remoteAuthority);
@@ -113,14 +113,14 @@ export class DiagnosticsMainService implements IDiagnosticsMainService {
};
}
- private getFolderURIs(window: ICodeWindow): URI[] {
+ private async getFolderURIs(window: ICodeWindow): Promise<URI[]> {
const folderURIs: URI[] = [];
const workspace = window.openedWorkspace;
if (isSingleFolderWorkspaceIdentifier(workspace)) {
folderURIs.push(workspace.uri);
} else if (isWorkspaceIdentifier(workspace)) {
- const resolvedWorkspace = this.workspacesManagementMainService.resolveLocalWorkspaceSync(workspace.configPath); // workspace folders can only be shown for local (resolved) workspaces
+ const resolvedWorkspace = await this.workspacesManagementMainService.resolveLocalWorkspace(workspace.configPath); // workspace folders can only be shown for local (resolved) workspaces
if (resolvedWorkspace) {
const rootFolders = resolvedWorkspace.folders;
rootFolders.forEach(root => {
diff --git a/src/vs/platform/environment/node/wait.ts b/src/vs/platform/environment/node/wait.ts
index 0be07ad2266..793d8e0467e 100644
--- a/src/vs/platform/environment/node/wait.ts
+++ b/src/vs/platform/environment/node/wait.ts
@@ -7,7 +7,7 @@ import { writeFileSync } from 'fs';
import { tmpdir } from 'os';
import { randomPath } from 'vs/base/common/extpath';
-export function createWaitMarkerFile(verbose?: boolean): string | undefined {
+export function createWaitMarkerFileSync(verbose?: boolean): string | undefined {
const randomWaitMarkerPath = randomPath(tmpdir());
try {
diff --git a/src/vs/platform/instantiation/common/instantiationService.ts b/src/vs/platform/instantiation/common/instantiationService.ts
index 1f6bba77e24..21908bbcff0 100644
--- a/src/vs/platform/instantiation/common/instantiationService.ts
+++ b/src/vs/platform/instantiation/common/instantiationService.ts
@@ -11,7 +11,9 @@ import { IInstantiationService, ServiceIdentifier, ServicesAccessor, _util } fro
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
// TRACING
-const _enableTracing = false;
+const _enableAllTracing = false
+ // || "TRUE" // DO NOT CHECK IN!
+ ;
class CyclicDependencyError extends Error {
constructor(graph: Graph<any>) {
@@ -24,24 +26,26 @@ export class InstantiationService implements IInstantiationService {
declare readonly _serviceBrand: undefined;
- private readonly _services: ServiceCollection;
- private readonly _strict: boolean;
- private readonly _parent?: InstantiationService;
+ readonly _globalGraph?: Graph<string>;
+ private _globalGraphImplicitDependency?: string;
- constructor(services: ServiceCollection = new ServiceCollection(), strict: boolean = false, parent?: InstantiationService) {
- this._services = services;
- this._strict = strict;
- this._parent = parent;
+ constructor(
+ private readonly _services: ServiceCollection = new ServiceCollection(),
+ private readonly _strict: boolean = false,
+ private readonly _parent?: InstantiationService,
+ private readonly _enableTracing: boolean = _enableAllTracing
+ ) {
this._services.set(IInstantiationService, this);
+ this._globalGraph = _enableTracing ? _parent?._globalGraph ?? new Graph(e => e) : undefined;
}
createChild(services: ServiceCollection): IInstantiationService {
- return new InstantiationService(services, this._strict, this);
+ return new InstantiationService(services, this._strict, this, this._enableTracing);
}
invokeFunction<R, TS extends any[] = []>(fn: (accessor: ServicesAccessor, ...args: TS) => R, ...args: TS): R {
- const _trace = Trace.traceInvocation(fn);
+ const _trace = Trace.traceInvocation(this._enableTracing, fn);
let _done = false;
try {
const accessor: ServicesAccessor = {
@@ -69,10 +73,10 @@ export class InstantiationService implements IInstantiationService {
let _trace: Trace;
let result: any;
if (ctorOrDescriptor instanceof SyncDescriptor) {
- _trace = Trace.traceCreation(ctorOrDescriptor.ctor);
+ _trace = Trace.traceCreation(this._enableTracing, ctorOrDescriptor.ctor);
result = this._createInstance(ctorOrDescriptor.ctor, ctorOrDescriptor.staticArguments.concat(rest), _trace);
} else {
- _trace = Trace.traceCreation(ctorOrDescriptor);
+ _trace = Trace.traceCreation(this._enableTracing, ctorOrDescriptor);
result = this._createInstance(ctorOrDescriptor, rest, _trace);
}
_trace.stop();
@@ -130,6 +134,9 @@ export class InstantiationService implements IInstantiationService {
}
protected _getOrCreateServiceInstance<T>(id: ServiceIdentifier<T>, _trace: Trace): T {
+ if (this._globalGraph && this._globalGraphImplicitDependency) {
+ this._globalGraph.insertEdge(this._globalGraphImplicitDependency, String(id));
+ }
const thing = this._getServiceInstanceOrDescriptor(id);
if (thing instanceof SyncDescriptor) {
return this._safeCreateAndCacheServiceInstance(id, thing, _trace.branch(id, true));
@@ -178,6 +185,9 @@ export class InstantiationService implements IInstantiationService {
this._throwIfStrict(`[createInstance] ${id} depends on ${dependency.id} which is NOT registered.`, true);
}
+ // take note of all service dependencies
+ this._globalGraph?.insertEdge(String(item.id), String(dependency.id));
+
if (instanceOrDesc instanceof SyncDescriptor) {
const d = { id: dependency.id, desc: instanceOrDesc, _trace: item._trace.branch(dependency.id, true) };
graph.insertEdge(item, d);
@@ -216,7 +226,7 @@ export class InstantiationService implements IInstantiationService {
private _createServiceInstanceWithOwner<T>(id: ServiceIdentifier<T>, ctor: any, args: any[] = [], supportsDelayedInstantiation: boolean, _trace: Trace): T {
if (this._services.get(id) instanceof SyncDescriptor) {
- return this._createServiceInstance(ctor, args, supportsDelayedInstantiation, _trace);
+ return this._createServiceInstance(id, ctor, args, supportsDelayedInstantiation, _trace);
} else if (this._parent) {
return this._parent._createServiceInstanceWithOwner(id, ctor, args, supportsDelayedInstantiation, _trace);
} else {
@@ -224,16 +234,22 @@ export class InstantiationService implements IInstantiationService {
}
}
- private _createServiceInstance<T>(ctor: any, args: any[] = [], _supportsDelayedInstantiation: boolean, _trace: Trace): T {
- if (!_supportsDelayedInstantiation) {
+ private _createServiceInstance<T>(id: ServiceIdentifier<T>, ctor: any, args: any[] = [], supportsDelayedInstantiation: boolean, _trace: Trace): T {
+ if (!supportsDelayedInstantiation) {
// eager instantiation
return this._createInstance(ctor, args, _trace);
} else {
+ const child = new InstantiationService(undefined, this._strict, this, this._enableTracing);
+ child._globalGraphImplicitDependency = String(id);
+
// Return a proxy object that's backed by an idle value. That
// strategy is to instantiate services in our idle time or when actually
// needed but not when injected into a consumer
- const idle = new IdleValue<any>(() => this._createInstance<T>(ctor, args, _trace));
+ const idle = new IdleValue<any>(() => {
+ const result = child._createInstance<T>(ctor, args, _trace);
+ return result;
+ });
return <T>new Proxy(Object.create(null), {
get(target: any, key: PropertyKey): any {
if (key in target) {
@@ -274,17 +290,19 @@ const enum TraceType {
export class Trace {
+ static all = new Set<string>();
+
private static readonly _None = new class extends Trace {
constructor() { super(-1, null); }
override stop() { }
override branch() { return this; }
};
- static traceInvocation(ctor: any): Trace {
- return !_enableTracing ? Trace._None : new Trace(TraceType.Invocation, ctor.name || (ctor.toString() as string).substring(0, 42).replace(/\n/g, ''));
+ static traceInvocation(_enableTracing: boolean, ctor: any): Trace {
+ return !_enableTracing ? Trace._None : new Trace(TraceType.Invocation, ctor.name || new Error().stack!.split('\n').slice(3, 4).join('\n'));
}
- static traceCreation(ctor: any): Trace {
+ static traceCreation(_enableTracing: boolean, ctor: any): Trace {
return !_enableTracing ? Trace._None : new Trace(TraceType.Creation, ctor.name);
}
@@ -334,7 +352,7 @@ export class Trace {
];
if (dur > 2 || causedCreation) {
- console.log(lines.join('\n'));
+ Trace.all.add(lines.join('\n'));
}
}
}
diff --git a/src/vs/platform/instantiation/test/common/instantiationService.test.ts b/src/vs/platform/instantiation/test/common/instantiationService.test.ts
index 90bd049d0ca..a737b46533c 100644
--- a/src/vs/platform/instantiation/test/common/instantiationService.test.ts
+++ b/src/vs/platform/instantiation/test/common/instantiationService.test.ts
@@ -393,4 +393,70 @@ suite('Instantiation Service', () => {
assert.ok(obj);
});
+ test('Sync/Async dependency loop', async function () {
+
+ const A = createDecorator<A>('A');
+ const B = createDecorator<B>('B');
+ interface A { _serviceBrand: undefined; doIt(): void }
+ interface B { _serviceBrand: undefined; b(): boolean }
+
+ class BConsumer {
+ constructor(@B readonly b: B) {
+
+ }
+ doIt() {
+ return this.b.b();
+ }
+ }
+
+ class AService implements A {
+ _serviceBrand: undefined;
+ prop: BConsumer;
+ constructor(@IInstantiationService insta: IInstantiationService) {
+ this.prop = insta.createInstance(BConsumer);
+ }
+ doIt() {
+ return this.prop.doIt();
+ }
+ }
+
+ class BService implements B {
+ _serviceBrand: undefined;
+ constructor(@A a: A) {
+ assert.ok(a);
+ }
+ b() { return true; }
+ }
+
+ // SYNC -> explodes AImpl -> [insta:BConsumer] -> BImpl -> AImpl
+ {
+ const insta1 = new InstantiationService(new ServiceCollection(
+ [A, new SyncDescriptor(AService)],
+ [B, new SyncDescriptor(BService)],
+ ), true, undefined, true);
+
+ try {
+ insta1.invokeFunction(accessor => accessor.get(A));
+ assert.ok(false);
+
+ } catch (error) {
+ assert.ok(error instanceof Error);
+ assert.ok(error.message.includes('RECURSIVELY'));
+ }
+ }
+
+ // ASYNC -> doesn't explode but cycle is tracked
+ {
+ const insta2 = new InstantiationService(new ServiceCollection(
+ [A, new SyncDescriptor(AService, undefined, true)],
+ [B, new SyncDescriptor(BService, undefined)],
+ ), true, undefined, true);
+
+ const a = insta2.invokeFunction(accessor => accessor.get(A));
+ a.doIt();
+
+ const cycle = insta2._globalGraph?.findCycleSlow();
+ assert.strictEqual(cycle, 'A -> B -> A');
+ }
+ });
});
diff --git a/src/vs/platform/instantiation/test/common/instantiationServiceMock.ts b/src/vs/platform/instantiation/test/common/instantiationServiceMock.ts
index 993fbfe9643..504f2600de8 100644
--- a/src/vs/platform/instantiation/test/common/instantiationServiceMock.ts
+++ b/src/vs/platform/instantiation/test/common/instantiationServiceMock.ts
@@ -28,7 +28,7 @@ export class TestInstantiationService extends InstantiationService {
}
public get<T>(service: ServiceIdentifier<T>): T {
- return super._getOrCreateServiceInstance(service, Trace.traceCreation(TestInstantiationService));
+ return super._getOrCreateServiceInstance(service, Trace.traceCreation(false, TestInstantiationService));
}
public set<T>(service: ServiceIdentifier<T>, instance: T): T {
diff --git a/src/vs/platform/launch/electron-main/launchMainService.ts b/src/vs/platform/launch/electron-main/launchMainService.ts
index f5730717f2c..54dc0b65e1b 100644
--- a/src/vs/platform/launch/electron-main/launchMainService.ts
+++ b/src/vs/platform/launch/electron-main/launchMainService.ts
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { app } from 'electron';
-import { coalesce } from 'vs/base/common/arrays';
+import { coalesce, firstOrDefault } from 'vs/base/common/arrays';
import { IProcessEnvironment, isMacintosh } from 'vs/base/common/platform';
import { URI } from 'vs/base/common/uri';
import { whenDeleted } from 'vs/base/node/pfs';
@@ -71,8 +71,10 @@ export class LaunchMainService implements ILaunchMainService {
// Create a window if there is none
if (this.windowsMainService.getWindowCount() === 0) {
- const window = this.windowsMainService.openEmptyWindow({ context: OpenContext.DESKTOP })[0];
- whenWindowReady = window.ready();
+ const window = firstOrDefault(await this.windowsMainService.openEmptyWindow({ context: OpenContext.DESKTOP }));
+ if (window) {
+ whenWindowReady = window.ready();
+ }
}
// Make sure a window is open, ready to receive the url event
@@ -116,8 +118,7 @@ export class LaunchMainService implements ILaunchMainService {
const remoteAuthority = args.remote || undefined;
// Ensure profile exists when passed in from CLI
- const profilePromise = this.userDataProfilesMainService.checkAndCreateProfileFromCli(args);
- const profile = profilePromise ? await profilePromise : undefined;
+ const profile = await this.userDataProfilesMainService.checkAndCreateProfileFromCli(args);
const baseConfig: IOpenConfiguration = {
context,
@@ -130,7 +131,7 @@ export class LaunchMainService implements ILaunchMainService {
// Special case extension development
if (!!args.extensionDevelopmentPath) {
- this.windowsMainService.openExtensionDevelopmentHostWindow(args.extensionDevelopmentPath, baseConfig);
+ await this.windowsMainService.openExtensionDevelopmentHostWindow(args.extensionDevelopmentPath, baseConfig);
}
// Start without file/folder arguments
@@ -165,7 +166,7 @@ export class LaunchMainService implements ILaunchMainService {
// Open new Window
if (openNewWindow) {
- usedWindows = this.windowsMainService.open({
+ usedWindows = await this.windowsMainService.open({
...baseConfig,
forceNewWindow: true,
forceEmpty: true
@@ -180,7 +181,7 @@ export class LaunchMainService implements ILaunchMainService {
usedWindows = [lastActive];
} else {
- usedWindows = this.windowsMainService.open({
+ usedWindows = await this.windowsMainService.open({
...baseConfig,
forceEmpty: true
});
@@ -190,7 +191,7 @@ export class LaunchMainService implements ILaunchMainService {
// Start with file/folder arguments
else {
- usedWindows = this.windowsMainService.open({
+ usedWindows = await this.windowsMainService.open({
...baseConfig,
forceNewWindow: args['new-window'],
preferNewWindow: !args['reuse-window'] && !args.wait,
diff --git a/src/vs/platform/menubar/electron-main/menubar.ts b/src/vs/platform/menubar/electron-main/menubar.ts
index 50b6febdf85..b61c05ea574 100644
--- a/src/vs/platform/menubar/electron-main/menubar.ts
+++ b/src/vs/platform/menubar/electron-main/menubar.ts
@@ -532,19 +532,19 @@ export class Menubar {
return new MenuItem(this.likeAction(commandId, {
label: item.label,
- click: (menuItem, win, event) => {
+ click: async (menuItem, win, event) => {
const openInNewWindow = this.isOptionClick(event);
- const success = this.windowsMainService.open({
+ const success = (await this.windowsMainService.open({
context: OpenContext.MENU,
cli: this.environmentMainService.args,
urisToOpen: [openable],
forceNewWindow: openInNewWindow,
gotoLineMode: false,
remoteAuthority: item.remoteAuthority
- }).length > 0;
+ })).length > 0;
if (!success) {
- this.workspacesHistoryMainService.removeRecentlyOpened([revivedUri]);
+ await this.workspacesHistoryMainService.removeRecentlyOpened([revivedUri]);
}
}
}, false));
diff --git a/src/vs/platform/native/electron-main/nativeHostMainService.ts b/src/vs/platform/native/electron-main/nativeHostMainService.ts
index 84d42eb4877..d5da363f058 100644
--- a/src/vs/platform/native/electron-main/nativeHostMainService.ts
+++ b/src/vs/platform/native/electron-main/nativeHostMainService.ts
@@ -146,7 +146,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
private async doOpenWindow(windowId: number | undefined, toOpen: IWindowOpenable[], options: IOpenWindowOptions = Object.create(null)): Promise<void> {
if (toOpen.length > 0) {
- this.windowsMainService.open({
+ await this.windowsMainService.open({
context: OpenContext.API,
contextWindowId: windowId,
urisToOpen: toOpen,
@@ -166,7 +166,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
}
private async doOpenEmptyWindow(windowId: number | undefined, options?: IOpenEmptyWindowOptions): Promise<void> {
- this.windowsMainService.openEmptyWindow({
+ await this.windowsMainService.openEmptyWindow({
context: OpenContext.API,
contextWindowId: windowId
}, options);
@@ -384,33 +384,33 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
async pickFileFolderAndOpen(windowId: number | undefined, options: INativeOpenDialogOptions): Promise<void> {
const paths = await this.dialogMainService.pickFileFolder(options);
if (paths) {
- this.doOpenPicked(await Promise.all(paths.map(async path => (await SymlinkSupport.existsDirectory(path)) ? { folderUri: URI.file(path) } : { fileUri: URI.file(path) })), options, windowId);
+ await this.doOpenPicked(await Promise.all(paths.map(async path => (await SymlinkSupport.existsDirectory(path)) ? { folderUri: URI.file(path) } : { fileUri: URI.file(path) })), options, windowId);
}
}
async pickFolderAndOpen(windowId: number | undefined, options: INativeOpenDialogOptions): Promise<void> {
const paths = await this.dialogMainService.pickFolder(options);
if (paths) {
- this.doOpenPicked(paths.map(path => ({ folderUri: URI.file(path) })), options, windowId);
+ await this.doOpenPicked(paths.map(path => ({ folderUri: URI.file(path) })), options, windowId);
}
}
async pickFileAndOpen(windowId: number | undefined, options: INativeOpenDialogOptions): Promise<void> {
const paths = await this.dialogMainService.pickFile(options);
if (paths) {
- this.doOpenPicked(paths.map(path => ({ fileUri: URI.file(path) })), options, windowId);
+ await this.doOpenPicked(paths.map(path => ({ fileUri: URI.file(path) })), options, windowId);
}
}
async pickWorkspaceAndOpen(windowId: number | undefined, options: INativeOpenDialogOptions): Promise<void> {
const paths = await this.dialogMainService.pickWorkspace(options);
if (paths) {
- this.doOpenPicked(paths.map(path => ({ workspaceUri: URI.file(path) })), options, windowId);
+ await this.doOpenPicked(paths.map(path => ({ workspaceUri: URI.file(path) })), options, windowId);
}
}
- private doOpenPicked(openable: IWindowOpenable[], options: INativeOpenDialogOptions, windowId: number | undefined): void {
- this.windowsMainService.open({
+ private async doOpenPicked(openable: IWindowOpenable[], options: INativeOpenDialogOptions, windowId: number | undefined): Promise<void> {
+ await this.windowsMainService.open({
context: OpenContext.DIALOG,
contextWindowId: windowId,
cli: this.environmentMainService.args,
@@ -623,7 +623,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
//#region macOS Touchbar
async newWindowTab(): Promise<void> {
- this.windowsMainService.open({
+ await this.windowsMainService.open({
context: OpenContext.API,
cli: this.environmentMainService.args,
forceNewTabbedWindow: true,
diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts
index 7e63a164e2c..f0808b12783 100644
--- a/src/vs/platform/product/common/product.ts
+++ b/src/vs/platform/product/common/product.ts
@@ -58,7 +58,7 @@ else {
// Running out of sources
if (Object.keys(product).length === 0) {
Object.assign(product, {
- version: '1.67.0-dev',
+ version: '1.72.0-dev',
nameShort: 'Code - OSS Dev',
nameLong: 'Code - OSS Dev',
applicationName: 'code-oss',
diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts
index 82327bc1660..9f78d7db571 100644
--- a/src/vs/platform/terminal/common/terminal.ts
+++ b/src/vs/platform/terminal/common/terminal.ts
@@ -317,7 +317,7 @@ export interface IPtyService extends IPtyHostController {
/** Confirm the process is _not_ an orphan. */
orphanQuestionReply(id: number): Promise<void>;
updateTitle(id: number, title: string, titleSource: TitleEventSource): Promise<void>;
- updateIcon(id: number, icon: TerminalIcon, color?: string): Promise<void>;
+ updateIcon(id: number, userInitiated: boolean, icon: TerminalIcon, color?: string): Promise<void>;
installAutoReply(match: string, reply: string): Promise<void>;
uninstallAllAutoReplies(): Promise<void>;
uninstallAutoReply(match: string): Promise<void>;
diff --git a/src/vs/platform/terminal/node/ptyHostService.ts b/src/vs/platform/terminal/node/ptyHostService.ts
index 21e2cf78d96..aab32f8cfcd 100644
--- a/src/vs/platform/terminal/node/ptyHostService.ts
+++ b/src/vs/platform/terminal/node/ptyHostService.ts
@@ -224,8 +224,8 @@ export class PtyHostService extends Disposable implements IPtyService {
updateTitle(id: number, title: string, titleSource: TitleEventSource): Promise<void> {
return this._proxy.updateTitle(id, title, titleSource);
}
- updateIcon(id: number, icon: TerminalIcon, color?: string): Promise<void> {
- return this._proxy.updateIcon(id, icon, color);
+ updateIcon(id: number, userInitiated: boolean, icon: TerminalIcon, color?: string): Promise<void> {
+ return this._proxy.updateIcon(id, userInitiated, icon, color);
}
attachToProcess(id: number): Promise<void> {
return this._proxy.attachToProcess(id);
diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts
index 69d05de8395..0d8f58a6dbd 100644
--- a/src/vs/platform/terminal/node/ptyService.ts
+++ b/src/vs/platform/terminal/node/ptyService.ts
@@ -225,8 +225,8 @@ export class PtyService extends Disposable implements IPtyService {
this._throwIfNoPty(id).setTitle(title, titleSource);
}
- async updateIcon(id: number, icon: URI | { light: URI; dark: URI } | { id: string; color?: { id: string } }, color?: string): Promise<void> {
- this._throwIfNoPty(id).setIcon(icon, color);
+ async updateIcon(id: number, userInitiated: boolean, icon: URI | { light: URI; dark: URI } | { id: string; color?: { id: string } }, color?: string): Promise<void> {
+ this._throwIfNoPty(id).setIcon(userInitiated, icon, color);
}
async refreshProperty<T extends ProcessPropertyType>(id: number, type: T): Promise<IProcessPropertyMap[T]> {
@@ -496,12 +496,14 @@ export class PersistentTerminalProcess extends Disposable {
this._titleSource = titleSource;
}
- setIcon(icon: TerminalIcon, color?: string): void {
+ setIcon(userInitiated: boolean, icon: TerminalIcon, color?: string): void {
if (!this._icon || 'id' in icon && 'id' in this._icon && icon.id !== this._icon.id ||
!this.color || color !== this._color) {
this._serializer.freeRawReviveBuffer();
- this._interactionState.setValue(InteractionState.Session, 'setIcon');
+ if (userInitiated) {
+ this._interactionState.setValue(InteractionState.Session, 'setIcon');
+ }
}
this._icon = icon;
this._color = color;
diff --git a/src/vs/platform/userDataProfile/common/userDataProfile.ts b/src/vs/platform/userDataProfile/common/userDataProfile.ts
index 99144a4ace0..cdde6fb2af3 100644
--- a/src/vs/platform/userDataProfile/common/userDataProfile.ts
+++ b/src/vs/platform/userDataProfile/common/userDataProfile.ts
@@ -6,7 +6,7 @@
import { hash } from 'vs/base/common/hash';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
-import { joinPath } from 'vs/base/common/resources';
+import { basename, joinPath } from 'vs/base/common/resources';
import { isUndefined } from 'vs/base/common/types';
import { URI, UriDto } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
@@ -95,9 +95,10 @@ export interface IUserDataProfilesService {
readonly onDidResetWorkspaces: Event<void>;
- createProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags, workspaceIdentifier?: WorkspaceIdentifier): Promise<IUserDataProfile>;
+ createNamedProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags, workspaceIdentifier?: WorkspaceIdentifier): Promise<IUserDataProfile>;
createTransientProfile(workspaceIdentifier?: WorkspaceIdentifier): Promise<IUserDataProfile>;
- updateProfile(profile: IUserDataProfile, name: string, useDefaultFlags?: UseDefaultProfileFlags): Promise<IUserDataProfile>;
+ createProfile(id: string, name: string, useDefaultFlags?: UseDefaultProfileFlags, transient?: boolean, workspaceIdentifier?: WorkspaceIdentifier): Promise<IUserDataProfile>;
+ updateProfile(profile: IUserDataProfile, name: string, useDefaultFlags?: UseDefaultProfileFlags, transient?: boolean): Promise<IUserDataProfile>;
removeProfile(profile: IUserDataProfile): Promise<void>;
setProfileForWorkspace(workspaceIdentifier: WorkspaceIdentifier, profile: IUserDataProfile): Promise<void>;
@@ -126,10 +127,10 @@ export function reviveProfile(profile: UriDto<IUserDataProfile>, scheme: string)
export const EXTENSIONS_RESOURCE_NAME = 'extensions.json';
-export function toUserDataProfile(name: string, location: URI, useDefaultFlags?: UseDefaultProfileFlags, transient?: boolean): IUserDataProfile {
+export function toUserDataProfile(id: string, name: string, location: URI, useDefaultFlags?: UseDefaultProfileFlags, transient?: boolean): IUserDataProfile {
return {
- id: hash(location.path).toString(16),
- name: name,
+ id,
+ name,
location: location,
isDefault: false,
globalStorageHome: joinPath(location, 'globalStorage'),
@@ -213,10 +214,10 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
protected _profilesObject: UserDataProfilesObject | undefined;
protected get profilesObject(): UserDataProfilesObject {
if (!this._profilesObject) {
- const profiles = this.enabled ? this.getStoredProfiles().map<IUserDataProfile>(storedProfile => toUserDataProfile(storedProfile.name, storedProfile.location, storedProfile.useDefaultFlags)) : [];
+ const profiles = this.enabled ? this.getStoredProfiles().map<IUserDataProfile>(storedProfile => toUserDataProfile(basename(storedProfile.location), storedProfile.name, storedProfile.location, storedProfile.useDefaultFlags)) : [];
let emptyWindow: IUserDataProfile | undefined;
const workspaces = new ResourceMap<IUserDataProfile>();
- const defaultProfile = toUserDataProfile(localize('defaultProfile', "Default"), this.environmentService.userRoamingDataHome);
+ const defaultProfile = toUserDataProfile(hash(this.environmentService.userRoamingDataHome.path).toString(16), localize('defaultProfile', "Default"), this.environmentService.userRoamingDataHome);
profiles.unshift({ ...defaultProfile, isDefault: true, extensionsResource: this.defaultProfileShouldIncludeExtensionsResourceAlways || profiles.length > 0 || this.transientProfilesObject.profiles.length > 0 ? defaultProfile.extensionsResource : undefined });
if (profiles.length) {
const profileAssicaitions = this.getStoredProfileAssociations();
@@ -250,15 +251,19 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
nameIndex = index > nameIndex ? index : nameIndex;
}
const name = `${namePrefix} ${nameIndex + 1}`;
- return this.createProfile(name, undefined, workspaceIdentifier, true);
+ return this.createProfile(hash(generateUuid()).toString(16), name, undefined, true, workspaceIdentifier);
}
- async createProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags, workspaceIdentifier?: WorkspaceIdentifier, transient?: boolean): Promise<IUserDataProfile> {
+ async createNamedProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags, workspaceIdentifier?: WorkspaceIdentifier): Promise<IUserDataProfile> {
+ return this.createProfile(hash(generateUuid()).toString(16), name, useDefaultFlags, false, workspaceIdentifier);
+ }
+
+ async createProfile(id: string, name: string, useDefaultFlags?: UseDefaultProfileFlags, transient?: boolean, workspaceIdentifier?: WorkspaceIdentifier): Promise<IUserDataProfile> {
if (!this.enabled) {
throw new Error(`Settings Profiles are disabled. Enable them via the '${PROFILES_ENABLEMENT_CONFIG}' setting.`);
}
- const profile = await this.doCreateProfile(name, useDefaultFlags, transient);
+ const profile = await this.doCreateProfile(id, name, useDefaultFlags, !!transient);
if (workspaceIdentifier) {
await this.setProfileForWorkspace(workspaceIdentifier, profile);
@@ -267,17 +272,17 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
return profile;
}
- private async doCreateProfile(name: string, useDefaultFlags: UseDefaultProfileFlags | undefined, transient?: boolean): Promise<IUserDataProfile> {
+ private async doCreateProfile(id: string, name: string, useDefaultFlags: UseDefaultProfileFlags | undefined, transient: boolean): Promise<IUserDataProfile> {
let profileCreationPromise = this.profileCreationPromises.get(name);
if (!profileCreationPromise) {
profileCreationPromise = (async () => {
try {
- const existing = this.profiles.find(p => p.name === name);
+ const existing = this.profiles.find(p => p.name === name || p.id === id);
if (existing) {
return existing;
}
- const profile = toUserDataProfile(name, joinPath(this.profilesHome, hash(generateUuid()).toString(16)), useDefaultFlags, transient);
+ const profile = toUserDataProfile(id, name, joinPath(this.profilesHome, id), useDefaultFlags, transient);
await this.fileService.createFolder(profile.location);
const joiners: Promise<void>[] = [];
@@ -300,7 +305,7 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
return profileCreationPromise;
}
- async updateProfile(profileToUpdate: IUserDataProfile, name: string, useDefaultFlags?: UseDefaultProfileFlags): Promise<IUserDataProfile> {
+ async updateProfile(profileToUpdate: IUserDataProfile, name: string, useDefaultFlags?: UseDefaultProfileFlags, transient?: boolean): Promise<IUserDataProfile> {
if (!this.enabled) {
throw new Error(`Settings Profiles are disabled. Enable them via the '${PROFILES_ENABLEMENT_CONFIG}' setting.`);
}
@@ -310,7 +315,7 @@ export class UserDataProfilesService extends Disposable implements IUserDataProf
throw new Error(`Profile '${profileToUpdate.name}' does not exist`);
}
- profile = toUserDataProfile(name, profile.location, useDefaultFlags);
+ profile = toUserDataProfile(profile.id, name, profile.location, useDefaultFlags, transient ?? profile.isTransient);
this.updateProfiles([], [], [profile]);
return profile;
diff --git a/src/vs/platform/userDataProfile/electron-main/userDataProfile.ts b/src/vs/platform/userDataProfile/electron-main/userDataProfile.ts
index 4578e6d40a9..a5776c1fa3d 100644
--- a/src/vs/platform/userDataProfile/electron-main/userDataProfile.ts
+++ b/src/vs/platform/userDataProfile/electron-main/userDataProfile.ts
@@ -58,7 +58,7 @@ export class UserDataProfilesMainService extends UserDataProfilesService impleme
}
if (args.profile) {
const profile = this.profiles.find(p => p.name === args.profile);
- return profile ? Promise.resolve(profile) : this.createProfile(args.profile);
+ return profile ? Promise.resolve(profile) : this.createNamedProfile(args.profile);
}
if (args['profile-temp']) {
return this.createTransientProfile();
diff --git a/src/vs/platform/userDataProfile/electron-sandbox/userDataProfile.ts b/src/vs/platform/userDataProfile/electron-sandbox/userDataProfile.ts
index b9f50c294a6..dda0d6fa9b5 100644
--- a/src/vs/platform/userDataProfile/electron-sandbox/userDataProfile.ts
+++ b/src/vs/platform/userDataProfile/electron-sandbox/userDataProfile.ts
@@ -48,8 +48,13 @@ export class UserDataProfilesNativeService extends Disposable implements IUserDa
this.onDidResetWorkspaces = this.channel.listen<void>('onDidResetWorkspaces');
}
- async createProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags, workspaceIdentifier?: WorkspaceIdentifier): Promise<IUserDataProfile> {
- const result = await this.channel.call<UriDto<IUserDataProfile>>('createProfile', [name, useDefaultFlags, workspaceIdentifier]);
+ async createNamedProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags, workspaceIdentifier?: WorkspaceIdentifier): Promise<IUserDataProfile> {
+ const result = await this.channel.call<UriDto<IUserDataProfile>>('createNamedProfile', [name, useDefaultFlags, workspaceIdentifier]);
+ return reviveProfile(result, this.profilesHome.scheme);
+ }
+
+ async createProfile(id: string, name: string, useDefaultFlags?: UseDefaultProfileFlags, transient?: boolean, workspaceIdentifier?: WorkspaceIdentifier): Promise<IUserDataProfile> {
+ const result = await this.channel.call<UriDto<IUserDataProfile>>('createProfile', [id, name, useDefaultFlags, transient, workspaceIdentifier]);
return reviveProfile(result, this.profilesHome.scheme);
}
@@ -66,8 +71,8 @@ export class UserDataProfilesNativeService extends Disposable implements IUserDa
return this.channel.call('removeProfile', [profile]);
}
- async updateProfile(profile: IUserDataProfile, name: string, useDefaultFlags?: UseDefaultProfileFlags): Promise<IUserDataProfile> {
- const result = await this.channel.call<UriDto<IUserDataProfile>>('updateProfile', [profile, name, useDefaultFlags]);
+ async updateProfile(profile: IUserDataProfile, name: string, useDefaultFlags?: UseDefaultProfileFlags, transient?: boolean): Promise<IUserDataProfile> {
+ const result = await this.channel.call<UriDto<IUserDataProfile>>('updateProfile', [profile, name, useDefaultFlags, transient]);
return reviveProfile(result, this.profilesHome.scheme);
}
diff --git a/src/vs/platform/userDataProfile/test/common/userDataProfileService.test.ts b/src/vs/platform/userDataProfile/test/common/userDataProfileService.test.ts
index 22c5539cb09..a4cc6a8f50c 100644
--- a/src/vs/platform/userDataProfile/test/common/userDataProfileService.test.ts
+++ b/src/vs/platform/userDataProfile/test/common/userDataProfileService.test.ts
@@ -74,12 +74,32 @@ suite('UserDataProfileService (Common)', () => {
assert.deepStrictEqual(testObject.profiles[0].extensionsResource, undefined);
});
+ test('create profile with id', async () => {
+ const profile = await testObject.createProfile('id', 'name');
+ assert.deepStrictEqual(testObject.profiles.length, 2);
+ assert.deepStrictEqual(profile.id, 'id');
+ assert.deepStrictEqual(profile.name, 'name');
+ assert.deepStrictEqual(!!profile.isTransient, false);
+ assert.deepStrictEqual(testObject.profiles[1].id, profile.id);
+ assert.deepStrictEqual(testObject.profiles[1].name, profile.name);
+ });
+
+ test('create profile with id, name and transient', async () => {
+ const profile = await testObject.createProfile('id', 'name', undefined, true);
+ assert.deepStrictEqual(testObject.profiles.length, 2);
+ assert.deepStrictEqual(profile.id, 'id');
+ assert.deepStrictEqual(profile.name, 'name');
+ assert.deepStrictEqual(!!profile.isTransient, true);
+ assert.deepStrictEqual(testObject.profiles[1].id, profile.id);
+ });
+
test('create transient profiles', async () => {
const profile1 = await testObject.createTransientProfile();
const profile2 = await testObject.createTransientProfile();
const profile3 = await testObject.createTransientProfile();
+ const profile4 = await testObject.createProfile('id', 'name', undefined, true);
- assert.deepStrictEqual(testObject.profiles.length, 4);
+ assert.deepStrictEqual(testObject.profiles.length, 5);
assert.deepStrictEqual(profile1.name, 'Temp 1');
assert.deepStrictEqual(profile1.isTransient, true);
assert.deepStrictEqual(testObject.profiles[1].id, profile1.id);
@@ -89,10 +109,13 @@ suite('UserDataProfileService (Common)', () => {
assert.deepStrictEqual(profile3.name, 'Temp 3');
assert.deepStrictEqual(profile3.isTransient, true);
assert.deepStrictEqual(testObject.profiles[3].id, profile3.id);
+ assert.deepStrictEqual(profile4.name, 'name');
+ assert.deepStrictEqual(profile4.isTransient, true);
+ assert.deepStrictEqual(testObject.profiles[4].id, profile4.id);
});
test('create transient profile when a normal profile with Temp is already created', async () => {
- await testObject.createProfile('Temp 1');
+ await testObject.createNamedProfile('Temp 1');
const profile1 = await testObject.createTransientProfile();
assert.deepStrictEqual(profile1.name, 'Temp 2');
@@ -116,4 +139,44 @@ suite('UserDataProfileService (Common)', () => {
assert.deepStrictEqual(testObject.profiles[0].extensionsResource, undefined);
});
+ test('update named profile', async () => {
+ const profile = await testObject.createNamedProfile('name');
+ await testObject.updateProfile(profile, 'name changed');
+
+ assert.deepStrictEqual(testObject.profiles.length, 2);
+ assert.deepStrictEqual(testObject.profiles[1].name, 'name changed');
+ assert.deepStrictEqual(!!testObject.profiles[1].isTransient, false);
+ assert.deepStrictEqual(testObject.profiles[1].id, profile.id);
+ });
+
+ test('persist transient profile', async () => {
+ const profile = await testObject.createTransientProfile();
+ await testObject.updateProfile(profile, 'saved', undefined, false);
+
+ assert.deepStrictEqual(testObject.profiles.length, 2);
+ assert.deepStrictEqual(testObject.profiles[1].name, 'saved');
+ assert.deepStrictEqual(!!testObject.profiles[1].isTransient, false);
+ assert.deepStrictEqual(testObject.profiles[1].id, profile.id);
+ });
+
+ test('persist transient profile (2)', async () => {
+ const profile = await testObject.createProfile('id', 'name', undefined, true);
+ await testObject.updateProfile(profile, 'saved', undefined, false);
+
+ assert.deepStrictEqual(testObject.profiles.length, 2);
+ assert.deepStrictEqual(testObject.profiles[1].name, 'saved');
+ assert.deepStrictEqual(!!testObject.profiles[1].isTransient, false);
+ assert.deepStrictEqual(testObject.profiles[1].id, profile.id);
+ });
+
+ test('save transient profile', async () => {
+ const profile = await testObject.createTransientProfile();
+ await testObject.updateProfile(profile, 'saved');
+
+ assert.deepStrictEqual(testObject.profiles.length, 2);
+ assert.deepStrictEqual(testObject.profiles[1].name, 'saved');
+ assert.deepStrictEqual(!!testObject.profiles[1].isTransient, true);
+ assert.deepStrictEqual(testObject.profiles[1].id, profile.id);
+ });
+
});
diff --git a/src/vs/platform/userDataProfile/test/electron-main/userDataProfileMainService.test.ts b/src/vs/platform/userDataProfile/test/electron-main/userDataProfileMainService.test.ts
index 23e7656a1ad..7078f0757a4 100644
--- a/src/vs/platform/userDataProfile/test/electron-main/userDataProfileMainService.test.ts
+++ b/src/vs/platform/userDataProfile/test/electron-main/userDataProfileMainService.test.ts
@@ -61,13 +61,13 @@ suite('UserDataProfileMainService', () => {
});
test('default profile when there are profiles', async () => {
- await testObject.createProfile('test');
+ await testObject.createNamedProfile('test');
assert.strictEqual(testObject.defaultProfile.isDefault, true);
assert.strictEqual(testObject.defaultProfile.extensionsResource?.toString(), joinPath(environmentService.userRoamingDataHome, 'extensions.json').toString());
});
test('default profile when profiles are removed', async () => {
- const profile = await testObject.createProfile('test');
+ const profile = await testObject.createNamedProfile('test');
await testObject.removeProfile(profile);
assert.strictEqual(testObject.defaultProfile.isDefault, true);
assert.strictEqual(testObject.defaultProfile.extensionsResource, undefined);
diff --git a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts
index 3ad057b3cae..054565bd9d1 100644
--- a/src/vs/platform/userDataSync/common/abstractSynchronizer.ts
+++ b/src/vs/platform/userDataSync/common/abstractSynchronizer.ts
@@ -484,7 +484,7 @@ export abstract class AbstractSynchroniser extends Disposable implements IUserDa
}
async getRemoteSyncResourceHandles(): Promise<ISyncResourceHandle[]> {
- const handles = await this.userDataSyncStoreService.getAllRefs(this.resource);
+ const handles = await this.userDataSyncStoreService.getAllResourceRefs(this.resource);
return handles.map(({ created, ref }) => ({ created, uri: this.toRemoteBackupResource(ref) }));
}
@@ -665,11 +665,11 @@ export abstract class AbstractSynchroniser extends Disposable implements IUserDa
private async getUserData(refOrLastSyncData: string | IRemoteUserData | null): Promise<IUserData> {
if (isString(refOrLastSyncData)) {
- const content = await this.userDataSyncStoreService.resolveContent(this.resource, refOrLastSyncData);
+ const content = await this.userDataSyncStoreService.resolveResourceContent(this.resource, refOrLastSyncData);
return { ref: refOrLastSyncData, content };
} else {
const lastSyncUserData: IUserData | null = refOrLastSyncData ? { ref: refOrLastSyncData.ref, content: refOrLastSyncData.syncData ? JSON.stringify(refOrLastSyncData.syncData) : null } : null;
- return this.userDataSyncStoreService.read(this.resource, lastSyncUserData, undefined, this.syncHeaders);
+ return this.userDataSyncStoreService.readResource(this.resource, lastSyncUserData, undefined, this.syncHeaders);
}
}
@@ -677,7 +677,7 @@ export abstract class AbstractSynchroniser extends Disposable implements IUserDa
const machineId = await this.currentMachineIdPromise;
const syncData: ISyncData = { version: this.version, machineId, content };
try {
- ref = await this.userDataSyncStoreService.write(this.resource, JSON.stringify(syncData), ref, undefined, this.syncHeaders);
+ ref = await this.userDataSyncStoreService.writeResource(this.resource, JSON.stringify(syncData), ref, undefined, this.syncHeaders);
return { ref, syncData };
} catch (error) {
if (error instanceof UserDataSyncError && error.code === UserDataSyncErrorCode.TooLarge) {
diff --git a/src/vs/platform/userDataSync/common/globalStateSync.ts b/src/vs/platform/userDataSync/common/globalStateSync.ts
index 1f7f2860565..a3d8c085cec 100644
--- a/src/vs/platform/userDataSync/common/globalStateSync.ts
+++ b/src/vs/platform/userDataSync/common/globalStateSync.ts
@@ -493,7 +493,7 @@ export class UserDataSyncStoreTypeSynchronizer {
private async doSync(userDataSyncStoreType: UserDataSyncStoreType, syncHeaders: IHeaders): Promise<void> {
// Read the global state from remote
- const globalStateUserData = await this.userDataSyncStoreClient.readResource(SyncResource.GlobalState, null, syncHeaders);
+ const globalStateUserData = await this.userDataSyncStoreClient.readResource(SyncResource.GlobalState, null, undefined, syncHeaders);
const remoteGlobalState = this.parseGlobalState(globalStateUserData) || { storage: {} };
// Update the sync store type
@@ -502,7 +502,7 @@ export class UserDataSyncStoreTypeSynchronizer {
// Write the global state to remote
const machineId = await getServiceMachineId(this.environmentService, this.fileService, this.storageService);
const syncDataToUpdate: ISyncData = { version: GLOBAL_STATE_DATA_VERSION, machineId, content: stringify(remoteGlobalState, false) };
- await this.userDataSyncStoreClient.writeResource(SyncResource.GlobalState, JSON.stringify(syncDataToUpdate), globalStateUserData.ref, syncHeaders);
+ await this.userDataSyncStoreClient.writeResource(SyncResource.GlobalState, JSON.stringify(syncDataToUpdate), globalStateUserData.ref, undefined, syncHeaders);
}
private parseGlobalState({ content }: IUserData): IGlobalState | null {
diff --git a/src/vs/platform/userDataSync/common/userDataSync.ts b/src/vs/platform/userDataSync/common/userDataSync.ts
index 17ed95577de..506557b371c 100644
--- a/src/vs/platform/userDataSync/common/userDataSync.ts
+++ b/src/vs/platform/userDataSync/common/userDataSync.ts
@@ -145,10 +145,19 @@ export function getLastSyncResourceUri(syncResource: SyncResource, environmentSe
return extUri.joinPath(environmentService.userDataSyncHome, syncResource, `lastSync${syncResource}.json`);
}
+export type IUserDataResourceManifest = Record<ServerResource, string>;
+
+export interface IUserDataCollectionManifest {
+ [collectionId: string]: {
+ readonly latest: IUserDataResourceManifest;
+ };
+}
+
export interface IUserDataManifest {
- readonly latest?: Record<ServerResource, string>;
+ readonly latest?: IUserDataResourceManifest;
readonly session: string;
readonly ref: string;
+ readonly collections?: IUserDataCollectionManifest;
}
export interface IResourceRefHandle {
@@ -156,7 +165,7 @@ export interface IResourceRefHandle {
created: number;
}
-export type ServerResource = SyncResource | 'machines' | 'editSessions' | 'profiles';
+export type ServerResource = SyncResource | 'machines' | 'editSessions';
export type UserDataSyncStoreType = 'insiders' | 'stable';
export const IUserDataSyncStoreManagementService = createDecorator<IUserDataSyncStoreManagementService>('IUserDataSyncStoreManagementService');
@@ -179,11 +188,16 @@ export interface IUserDataSyncStoreService {
setAuthToken(token: string, type: string): void;
manifest(oldValue: IUserDataManifest | null, headers?: IHeaders): Promise<IUserDataManifest | null>;
- read(resource: ServerResource, oldValue: IUserData | null, profile?: string, headers?: IHeaders): Promise<IUserData>;
- write(resource: ServerResource, content: string, ref: string | null, profile?: string, headers?: IHeaders): Promise<string>;
- delete(resource: ServerResource, ref: string | null, profile?: string): Promise<void>;
- getAllRefs(resource: ServerResource, profile?: string): Promise<IResourceRefHandle[]>;
- resolveContent(resource: ServerResource, ref: string, profile?: string, headers?: IHeaders): Promise<string | null>;
+ readResource(resource: ServerResource, oldValue: IUserData | null, collection?: string, headers?: IHeaders): Promise<IUserData>;
+ writeResource(resource: ServerResource, content: string, ref: string | null, collection?: string, headers?: IHeaders): Promise<string>;
+ deleteResource(resource: ServerResource, ref: string | null, collection?: string): Promise<void>;
+ getAllResourceRefs(resource: ServerResource, collection?: string): Promise<IResourceRefHandle[]>;
+ resolveResourceContent(resource: ServerResource, ref: string, collection?: string, headers?: IHeaders): Promise<string | null>;
+
+ getAllCollections(headers?: IHeaders): Promise<string[]>;
+ createCollection(headers?: IHeaders): Promise<string>;
+ deleteCollection(collection?: string, headers?: IHeaders): Promise<void>;
+
clear(): Promise<void>;
}
@@ -231,6 +245,7 @@ export const enum UserDataSyncErrorCode {
RequestProtocolNotSupported = 'RequestProtocolNotSupported',
RequestPathNotEscaped = 'RequestPathNotEscaped',
RequestHeadersNotObject = 'RequestHeadersNotObject',
+ NoCollection = 'NoCollection',
NoRef = 'NoRef',
EmptyResponse = 'EmptyResponse',
TurnedOff = 'TurnedOff',
diff --git a/src/vs/platform/userDataSync/common/userDataSyncMachines.ts b/src/vs/platform/userDataSync/common/userDataSyncMachines.ts
index 1158727d42c..6460ed5792d 100644
--- a/src/vs/platform/userDataSync/common/userDataSyncMachines.ts
+++ b/src/vs/platform/userDataSync/common/userDataSyncMachines.ts
@@ -176,7 +176,7 @@ export class UserDataSyncMachinesService extends Disposable implements IUserData
private async writeMachinesData(machinesData: IMachinesData): Promise<void> {
const content = JSON.stringify(machinesData);
- const ref = await this.userDataSyncStoreService.write(UserDataSyncMachinesService.RESOURCE, content, this.userData?.ref || null);
+ const ref = await this.userDataSyncStoreService.writeResource(UserDataSyncMachinesService.RESOURCE, content, this.userData?.ref || null);
this.userData = { ref, content };
this._onDidChange.fire();
}
@@ -197,7 +197,7 @@ export class UserDataSyncMachinesService extends Disposable implements IUserData
}
}
- return this.userDataSyncStoreService.read(UserDataSyncMachinesService.RESOURCE, this.userData);
+ return this.userDataSyncStoreService.readResource(UserDataSyncMachinesService.RESOURCE, this.userData);
}
private parse(userData: IUserData): IMachinesData {
diff --git a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts
index 4104c13c6ad..61e2b0df33d 100644
--- a/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts
+++ b/src/vs/platform/userDataSync/common/userDataSyncStoreService.ts
@@ -12,7 +12,6 @@ import { Mimes } from 'vs/base/common/mime';
import { isWeb } from 'vs/base/common/platform';
import { ConfigurationSyncStore } from 'vs/base/common/product';
import { joinPath, relativePath } from 'vs/base/common/resources';
-import { join } from 'vs/base/common/path';
import { isObject, isString } from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid';
@@ -231,12 +230,60 @@ export class UserDataSyncStoreClient extends Disposable {
}
}
- async getAllResourceRefs(path: string): Promise<IResourceRefHandle[]> {
+ // #region Collection
+
+ async getAllCollections(headers: IHeaders = {}): Promise<string[]> {
+ if (!this.userDataSyncStoreUrl) {
+ throw new Error('No settings sync store url configured.');
+ }
+
+ const url = joinPath(this.userDataSyncStoreUrl, 'collection').toString();
+ headers = { ...headers };
+ headers['Content-Type'] = 'application/json';
+
+ const context = await this.request(url, { type: 'GET', headers }, [], CancellationToken.None);
+
+ return (await asJson<string[]>(context)) || [];
+ }
+
+ async createCollection(headers: IHeaders = {}): Promise<string> {
+ if (!this.userDataSyncStoreUrl) {
+ throw new Error('No settings sync store url configured.');
+ }
+
+ const url = joinPath(this.userDataSyncStoreUrl, 'collection').toString();
+ headers = { ...headers };
+ headers['Content-Type'] = Mimes.text;
+
+ const context = await this.request(url, { type: 'POST', headers }, [], CancellationToken.None);
+ const collectionId = await asTextOrError(context);
+ if (!collectionId) {
+ throw new UserDataSyncStoreError('Server did not return the collection id', url, UserDataSyncErrorCode.NoCollection, context.res.statusCode, context.res.headers[HEADER_OPERATION_ID]);
+ }
+ return collectionId;
+ }
+
+ async deleteCollection(collection?: string, headers: IHeaders = {}): Promise<void> {
+ if (!this.userDataSyncStoreUrl) {
+ throw new Error('No settings sync store url configured.');
+ }
+
+ const url = collection ? joinPath(this.userDataSyncStoreUrl, 'collection', collection).toString() : joinPath(this.userDataSyncStoreUrl, 'collection').toString();
+ headers = { ...headers };
+
+ await this.request(url, { type: 'DELETE', headers }, [], CancellationToken.None);
+ }
+
+ // #endregion
+
+ // #region Resource
+
+ async getAllResourceRefs(resource: ServerResource, collection?: string): Promise<IResourceRefHandle[]> {
if (!this.userDataSyncStoreUrl) {
throw new Error('No settings sync store url configured.');
}
- const uri = joinPath(this.userDataSyncStoreUrl, 'resource', path);
+ const uri = this.getResourceUrl(this.userDataSyncStoreUrl, collection, resource);
const headers: IHeaders = {};
const context = await this.request(uri.toString(), { type: 'GET', headers }, [], CancellationToken.None);
@@ -245,12 +292,12 @@ export class UserDataSyncStoreClient extends Disposable {
return result.map(({ url, created }) => ({ ref: relativePath(uri, uri.with({ path: url }))!, created: created * 1000 /* Server returns in seconds */ }));
}
- async resolveResourceContent(path: string, ref: string, headers: IHeaders = {}): Promise<string | null> {
+ async resolveResourceContent(resource: ServerResource, ref: string, collection?: string, headers: IHeaders = {}): Promise<string | null> {
if (!this.userDataSyncStoreUrl) {
throw new Error('No settings sync store url configured.');
}
- const url = joinPath(this.userDataSyncStoreUrl, 'resource', path, ref).toString();
+ const url = joinPath(this.getResourceUrl(this.userDataSyncStoreUrl, collection, resource), ref).toString();
headers = { ...headers };
headers['Cache-Control'] = 'no-cache';
@@ -259,23 +306,34 @@ export class UserDataSyncStoreClient extends Disposable {
return content;
}
- async deleteResource(path: string, ref: string | null): Promise<void> {
+ async deleteResource(resource: ServerResource, ref: string | null, collection?: string): Promise<void> {
if (!this.userDataSyncStoreUrl) {
throw new Error('No settings sync store url configured.');
}
- const url = ref !== null ? joinPath(this.userDataSyncStoreUrl, 'resource', path, ref).toString() : joinPath(this.userDataSyncStoreUrl, 'resource', path).toString();
+ const url = ref !== null ? joinPath(this.getResourceUrl(this.userDataSyncStoreUrl, collection, resource), ref).toString() : this.getResourceUrl(this.userDataSyncStoreUrl, collection, resource).toString();
const headers: IHeaders = {};
await this.request(url, { type: 'DELETE', headers }, [], CancellationToken.None);
}
- async readResource(path: string, oldValue: IUserData | null, headers: IHeaders = {}): Promise<IUserData> {
+ async deleteResources(): Promise<void> {
+ if (!this.userDataSyncStoreUrl) {
+ throw new Error('No settings sync store url configured.');
+ }
+
+ const url = joinPath(this.userDataSyncStoreUrl, 'resource').toString();
+ const headers: IHeaders = { 'Content-Type': Mimes.text };
+
+ await this.request(url, { type: 'DELETE', headers }, [], CancellationToken.None);
+ }
+
+ async readResource(resource: ServerResource, oldValue: IUserData | null, collection?: string, headers: IHeaders = {}): Promise<IUserData> {
if (!this.userDataSyncStoreUrl) {
throw new Error('No settings sync store url configured.');
}
- const url = joinPath(this.userDataSyncStoreUrl, 'resource', path, 'latest').toString();
+ const url = joinPath(this.getResourceUrl(this.userDataSyncStoreUrl, collection, resource), 'latest').toString();
headers = { ...headers };
// Disable caching as they are cached by synchronisers
headers['Cache-Control'] = 'no-cache';
@@ -307,12 +365,12 @@ export class UserDataSyncStoreClient extends Disposable {
return userData;
}
- async writeResource(path: string, data: string, ref: string | null, headers: IHeaders = {}): Promise<string> {
+ async writeResource(resource: ServerResource, data: string, ref: string | null, collection?: string, headers: IHeaders = {}): Promise<string> {
if (!this.userDataSyncStoreUrl) {
throw new Error('No settings sync store url configured.');
}
- const url = joinPath(this.userDataSyncStoreUrl, 'resource', path).toString();
+ const url = this.getResourceUrl(this.userDataSyncStoreUrl, collection, resource).toString();
headers = { ...headers };
headers['Content-Type'] = Mimes.text;
if (ref) {
@@ -328,6 +386,8 @@ export class UserDataSyncStoreClient extends Disposable {
return newRef;
}
+ // #endregion
+
async manifest(oldValue: IUserDataManifest | null, headers: IHeaders = {}): Promise<IUserDataManifest | null> {
if (!this.userDataSyncStoreUrl) {
throw new Error('No settings sync store url configured.');
@@ -388,15 +448,17 @@ export class UserDataSyncStoreClient extends Disposable {
throw new Error('No settings sync store url configured.');
}
- const url = joinPath(this.userDataSyncStoreUrl, 'resource').toString();
- const headers: IHeaders = { 'Content-Type': Mimes.text };
-
- await this.request(url, { type: 'DELETE', headers }, [], CancellationToken.None);
+ await this.deleteResources();
+ await this.deleteCollection();
// clear cached session.
this.clearSession();
}
+ private getResourceUrl(userDataSyncStoreUrl: URI, collection: string | undefined, resource: ServerResource): URI {
+ return collection ? joinPath(userDataSyncStoreUrl, 'collection', collection, 'resource', resource) : joinPath(userDataSyncStoreUrl, 'resource', resource);
+ }
+
private clearSession(): void {
this.storageService.remove(USER_SESSION_ID_KEY, StorageScope.APPLICATION);
this.storageService.remove(MACHINE_SESSION_ID_KEY, StorageScope.APPLICATION);
@@ -551,32 +613,6 @@ export class UserDataSyncStoreService extends UserDataSyncStoreClient implements
this._register(userDataSyncStoreManagementService.onDidChangeUserDataSyncStore(() => this.updateUserDataSyncStoreUrl(userDataSyncStoreManagementService.userDataSyncStore?.url)));
}
- getAllRefs(resource: ServerResource, profile?: string): Promise<IResourceRefHandle[]> {
- return this.getAllResourceRefs(profile ? this.getProfileResource(resource, profile) : resource);
- }
-
- read(resource: ServerResource, oldValue: IUserData | null, profile?: string, headers?: IHeaders): Promise<IUserData> {
- return this.readResource(profile ? this.getProfileResource(resource, profile) : resource, oldValue, headers);
- }
-
- write(resource: ServerResource, content: string, ref: string | null, profile?: string, headers?: IHeaders): Promise<string> {
- return this.writeResource(profile ? this.getProfileResource(resource, profile) : resource, content, ref, headers);
- }
-
- delete(resource: ServerResource, ref: string | null, profile?: string): Promise<void> {
- return this.deleteResource(profile ? this.getProfileResource(resource, profile) : resource, ref);
- }
-
- resolveContent(resource: ServerResource, ref: string, profile?: string, headers?: IHeaders): Promise<string | null> {
- return this.resolveResourceContent(profile ? this.getProfileResource(resource, profile) : resource, ref, headers);
- }
-
- private getProfileResource(resource: ServerResource, profile: string): string {
- if (resource === 'profiles') {
- throw new Error(`Invalid Resource Argument: ${resource}`);
- }
- return join('profiles', profile, resource);
- }
}
export class RequestsSession {
diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts
index c81de3f5134..ba4adbedc26 100644
--- a/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts
+++ b/src/vs/platform/userDataSync/test/common/userDataSyncClient.ts
@@ -144,7 +144,7 @@ export class UserDataSyncClient extends Disposable {
}
read(resource: SyncResource): Promise<IUserData> {
- return this.instantiationService.get(IUserDataSyncStoreService).read(resource, null);
+ return this.instantiationService.get(IUserDataSyncStoreService).readResource(resource, null);
}
manifest(): Promise<IUserDataManifest | null> {
@@ -219,6 +219,9 @@ export class UserDataSyncTestServer implements IRequestService {
if (options.type === 'DELETE' && segments.length === 1 && segments[0] === 'resource') {
return this.clear(options.headers);
}
+ if (options.type === 'DELETE' && segments.length === 1 && segments[0] === 'collection') {
+ return this.toResponse(204);
+ }
return this.toResponse(501);
}
diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts b/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts
index 260b35b44b9..180502a1b2e 100644
--- a/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts
+++ b/src/vs/platform/userDataSync/test/common/userDataSyncService.test.ts
@@ -332,6 +332,7 @@ suite('UserDataSyncService', () => {
assert.deepStrictEqual(target.requests, [
// Manifest
{ type: 'DELETE', url: `${target.url}/v1/resource`, headers: {} },
+ { type: 'DELETE', url: `${target.url}/v1/collection`, headers: {} },
]);
});
diff --git a/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts b/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts
index 4f4c8fe12d3..08445ac0e43 100644
--- a/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts
+++ b/src/vs/platform/userDataSync/test/common/userDataSyncStoreService.test.ts
@@ -120,7 +120,7 @@ suite('UserDataSyncStoreService', () => {
await testObject.manifest(null);
const machineSessionId = target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'];
- await testObject.write(SyncResource.Settings, 'some content', null);
+ await testObject.writeResource(SyncResource.Settings, 'some content', null);
target.reset();
await testObject.manifest(null);
@@ -139,7 +139,7 @@ suite('UserDataSyncStoreService', () => {
await testObject.manifest(null);
const machineSessionId = target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'];
- await testObject.write(SyncResource.Settings, 'some content', null);
+ await testObject.writeResource(SyncResource.Settings, 'some content', null);
await testObject.manifest(null);
target.reset();
@@ -159,12 +159,12 @@ suite('UserDataSyncStoreService', () => {
await testObject.manifest(null);
const machineSessionId = target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'];
- await testObject.write(SyncResource.Settings, 'some content', null);
+ await testObject.writeResource(SyncResource.Settings, 'some content', null);
await testObject.manifest(null);
await testObject.manifest(null);
target.reset();
- await testObject.write(SyncResource.Settings, 'some content', null);
+ await testObject.writeResource(SyncResource.Settings, 'some content', null);
assert.strictEqual(target.requestsWithAllHeaders.length, 1);
assert.strictEqual(target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'], machineSessionId);
@@ -180,12 +180,12 @@ suite('UserDataSyncStoreService', () => {
await testObject.manifest(null);
const machineSessionId = target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'];
- await testObject.write(SyncResource.Settings, 'some content', null);
+ await testObject.writeResource(SyncResource.Settings, 'some content', null);
await testObject.manifest(null);
await testObject.manifest(null);
target.reset();
- await testObject.read(SyncResource.Settings, null);
+ await testObject.readResource(SyncResource.Settings, null);
assert.strictEqual(target.requestsWithAllHeaders.length, 1);
assert.strictEqual(target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'], machineSessionId);
@@ -201,7 +201,7 @@ suite('UserDataSyncStoreService', () => {
await testObject.manifest(null);
const machineSessionId = target.requestsWithAllHeaders[0].headers!['X-Machine-Session-Id'];
- await testObject.write(SyncResource.Settings, 'some content', null);
+ await testObject.writeResource(SyncResource.Settings, 'some content', null);
await testObject.manifest(null);
await testObject.manifest(null);
await testObject.clear();
@@ -223,7 +223,7 @@ suite('UserDataSyncStoreService', () => {
const testObject = client.instantiationService.get(IUserDataSyncStoreService);
await testObject.manifest(null);
- await testObject.write(SyncResource.Settings, 'some content', null);
+ await testObject.writeResource(SyncResource.Settings, 'some content', null);
await testObject.manifest(null);
target.reset();
await testObject.manifest(null);
@@ -235,7 +235,7 @@ suite('UserDataSyncStoreService', () => {
const client2 = disposableStore.add(new UserDataSyncClient(target));
await client2.setUp();
const testObject2 = client2.instantiationService.get(IUserDataSyncStoreService);
- await testObject2.write(SyncResource.Settings, 'some content', null);
+ await testObject2.writeResource(SyncResource.Settings, 'some content', null);
target.reset();
await testObject.manifest(null);
@@ -255,7 +255,7 @@ suite('UserDataSyncStoreService', () => {
const testObject = client.instantiationService.get(IUserDataSyncStoreService);
await testObject.manifest(null);
- await testObject.write(SyncResource.Settings, 'some content', null);
+ await testObject.writeResource(SyncResource.Settings, 'some content', null);
await testObject.manifest(null);
target.reset();
await testObject.manifest(null);
@@ -267,7 +267,7 @@ suite('UserDataSyncStoreService', () => {
const client2 = disposableStore.add(new UserDataSyncClient(target));
await client2.setUp();
const testObject2 = client2.instantiationService.get(IUserDataSyncStoreService);
- await testObject2.write(SyncResource.Settings, 'some content', null);
+ await testObject2.writeResource(SyncResource.Settings, 'some content', null);
await testObject.manifest(null);
target.reset();
@@ -288,7 +288,7 @@ suite('UserDataSyncStoreService', () => {
const testObject = client.instantiationService.get(IUserDataSyncStoreService);
await testObject.manifest(null);
- await testObject.write(SyncResource.Settings, 'some content', null);
+ await testObject.writeResource(SyncResource.Settings, 'some content', null);
await testObject.manifest(null);
target.reset();
await testObject.manifest(null);
@@ -319,7 +319,7 @@ suite('UserDataSyncStoreService', () => {
const testObject = client.instantiationService.get(IUserDataSyncStoreService);
await testObject.manifest(null);
- await testObject.write(SyncResource.Settings, 'some content', null);
+ await testObject.writeResource(SyncResource.Settings, 'some content', null);
await testObject.manifest(null);
target.reset();
await testObject.manifest(null);
@@ -349,7 +349,7 @@ suite('UserDataSyncStoreService', () => {
const testObject = client.instantiationService.get(IUserDataSyncStoreService);
await testObject.manifest(null);
- await testObject.write(SyncResource.Settings, 'some content', null);
+ await testObject.writeResource(SyncResource.Settings, 'some content', null);
await testObject.manifest(null);
target.reset();
await testObject.manifest(null);
@@ -363,7 +363,7 @@ suite('UserDataSyncStoreService', () => {
await testObject2.clear();
await testObject.manifest(null);
- await testObject.write(SyncResource.Settings, 'some content', null);
+ await testObject.writeResource(SyncResource.Settings, 'some content', null);
await testObject.manifest(null);
target.reset();
await testObject.manifest(null);
@@ -454,8 +454,8 @@ suite('UserDataSyncStoreService', () => {
await client.sync();
const testObject = client.instantiationService.get(IUserDataSyncStoreService);
- const expected = await testObject.read(SyncResource.Settings, null);
- const actual = await testObject.read(SyncResource.Settings, expected);
+ const expected = await testObject.readResource(SyncResource.Settings, null);
+ const actual = await testObject.readResource(SyncResource.Settings, expected);
assert.strictEqual(actual, expected);
});
diff --git a/src/vs/platform/windows/electron-main/windowImpl.ts b/src/vs/platform/windows/electron-main/windowImpl.ts
index dea36b686a5..429b5ae994c 100644
--- a/src/vs/platform/windows/electron-main/windowImpl.ts
+++ b/src/vs/platform/windows/electron-main/windowImpl.ts
@@ -666,7 +666,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
// shutdown as much as possible by destroying the window
// and then calling the normal `quit` routine.
if (this.environmentMainService.args['enable-smoke-test-driver']) {
- this.destroyWindow(false, false);
+ await this.destroyWindow(false, false);
this.lifecycleMainService.quit(); // still allow for an orderly shutdown
return;
}
@@ -703,7 +703,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
// Handle choice
if (result.response !== 1 /* keep waiting */) {
const reopen = result.response === 0;
- this.destroyWindow(reopen, result.checkboxChecked);
+ await this.destroyWindow(reopen, result.checkboxChecked);
}
}
@@ -733,7 +733,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
// Handle choice
const reopen = result.response === 0;
- this.destroyWindow(reopen, result.checkboxChecked);
+ await this.destroyWindow(reopen, result.checkboxChecked);
}
break;
}
@@ -775,7 +775,7 @@ export class CodeWindow extends Disposable implements ICodeWindow {
}
// Delegate to windows service
- const [window] = this.windowsMainService.open({
+ const [window] = await this.windowsMainService.open({
context: OpenContext.API,
userEnv: this._config.userEnv,
cli: {
diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts
index 8d42890f5f4..7825919d377 100644
--- a/src/vs/platform/windows/electron-main/windows.ts
+++ b/src/vs/platform/windows/electron-main/windows.ts
@@ -26,10 +26,11 @@ export interface IWindowsMainService {
readonly onDidTriggerSystemContextMenu: Event<{ window: ICodeWindow; x: number; y: number }>;
readonly onDidDestroyWindow: Event<ICodeWindow>;
- open(openConfig: IOpenConfiguration): ICodeWindow[];
- openEmptyWindow(openConfig: IOpenEmptyConfiguration, options?: IOpenEmptyWindowOptions): ICodeWindow[];
+ open(openConfig: IOpenConfiguration): Promise<ICodeWindow[]>;
+ openEmptyWindow(openConfig: IOpenEmptyConfiguration, options?: IOpenEmptyWindowOptions): Promise<ICodeWindow[]>;
+ openExtensionDevelopmentHostWindow(extensionDevelopmentPath: string[], openConfig: IOpenConfiguration): Promise<ICodeWindow[]>;
+
openExistingWindow(window: ICodeWindow, openConfig: IOpenConfiguration): void;
- openExtensionDevelopmentHostWindow(extensionDevelopmentPath: string[], openConfig: IOpenConfiguration): ICodeWindow[];
sendToFocused(channel: string, ...args: any[]): void;
sendToAll(channel: string, payload?: any, windowIdsToIgnore?: number[]): void;
diff --git a/src/vs/platform/windows/electron-main/windowsFinder.ts b/src/vs/platform/windows/electron-main/windowsFinder.ts
index ad0cc929fd5..c8d34be7505 100644
--- a/src/vs/platform/windows/electron-main/windowsFinder.ts
+++ b/src/vs/platform/windows/electron-main/windowsFinder.ts
@@ -8,13 +8,13 @@ import { URI } from 'vs/base/common/uri';
import { ICodeWindow } from 'vs/platform/window/electron-main/window';
import { IResolvedWorkspace, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace';
-export function findWindowOnFile(windows: ICodeWindow[], fileUri: URI, localWorkspaceResolver: (workspace: IWorkspaceIdentifier) => IResolvedWorkspace | undefined): ICodeWindow | undefined {
+export async function findWindowOnFile(windows: ICodeWindow[], fileUri: URI, localWorkspaceResolver: (workspace: IWorkspaceIdentifier) => Promise<IResolvedWorkspace | undefined>): Promise<ICodeWindow | undefined> {
// First check for windows with workspaces that have a parent folder of the provided path opened
for (const window of windows) {
const workspace = window.openedWorkspace;
if (isWorkspaceIdentifier(workspace)) {
- const resolvedWorkspace = localWorkspaceResolver(workspace);
+ const resolvedWorkspace = await localWorkspaceResolver(workspace);
// resolved workspace: folders are known and can be compared with
if (resolvedWorkspace) {
diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts
index 64484bd227f..4f1f9d43384 100644
--- a/src/vs/platform/windows/electron-main/windowsMainService.ts
+++ b/src/vs/platform/windows/electron-main/windowsMainService.ts
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { app, BrowserWindow, MessageBoxOptions, WebContents } from 'electron';
-import { statSync } from 'fs';
+import { Promises } from 'vs/base/node/pfs';
import { hostname, release } from 'os';
import { coalesce, distinct, firstOrDefault } from 'vs/base/common/arrays';
import { CancellationToken } from 'vs/base/common/cancellation';
@@ -247,7 +247,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
}));
}
- openEmptyWindow(openConfig: IOpenEmptyConfiguration, options?: IOpenEmptyWindowOptions): ICodeWindow[] {
+ openEmptyWindow(openConfig: IOpenEmptyConfiguration, options?: IOpenEmptyWindowOptions): Promise<ICodeWindow[]> {
const cli = this.environmentMainService.args;
const remoteAuthority = options?.remoteAuthority || undefined;
const forceEmpty = true;
@@ -266,7 +266,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
this.handleWaitMarkerFile(openConfig, [window]);
}
- open(openConfig: IOpenConfiguration): ICodeWindow[] {
+ async open(openConfig: IOpenConfiguration): Promise<ICodeWindow[]> {
this.logService.trace('windowsManager#open');
if (openConfig.addMode && (openConfig.initialStartup || !this.getLastActiveWindow())) {
@@ -285,7 +285,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
let emptyToOpen = 0;
// Identify things to open from open config
- const pathsToOpen = this.getPathsToOpen(openConfig);
+ const pathsToOpen = await this.getPathsToOpen(openConfig);
this.logService.trace('windowsManager#open pathsToOpen', pathsToOpen);
for (const path of pathsToOpen) {
if (isSingleFolderWorkspacePathToOpen(path)) {
@@ -332,7 +332,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
if (openConfig.initialStartup) {
// Untitled workspaces are always restored
- untitledWorkspacesToRestore.push(...this.workspacesManagementMainService.getUntitledWorkspacesSync());
+ untitledWorkspacesToRestore.push(...this.workspacesManagementMainService.getUntitledWorkspaces());
workspacesToOpen.push(...untitledWorkspacesToRestore);
// Empty windows with backups are always restored
@@ -342,7 +342,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
}
// Open based on config
- const { windows: usedWindows, filesOpenedInWindow } = this.doOpen(openConfig, workspacesToOpen, foldersToOpen, emptyWindowsWithBackupsToRestore, emptyToOpen, filesToOpen, foldersToAdd);
+ const { windows: usedWindows, filesOpenedInWindow } = await this.doOpen(openConfig, workspacesToOpen, foldersToOpen, emptyWindowsWithBackupsToRestore, emptyToOpen, filesToOpen, foldersToAdd);
this.logService.trace(`windowsManager#open used window count ${usedWindows.length} (workspacesToOpen: ${workspacesToOpen.length}, foldersToOpen: ${foldersToOpen.length}, emptyToRestore: ${emptyWindowsWithBackupsToRestore.length}, emptyToOpen: ${emptyToOpen})`);
@@ -438,7 +438,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
}
}
- private doOpen(
+ private async doOpen(
openConfig: IOpenConfiguration,
workspacesToOpen: IWorkspacePathToOpen[],
foldersToOpen: ISingleFolderWorkspacePathToOpen[],
@@ -446,7 +446,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
emptyToOpen: number,
filesToOpen: IFilesToOpen | undefined,
foldersToAdd: ISingleFolderWorkspacePathToOpen[]
- ): { windows: ICodeWindow[]; filesOpenedInWindow: ICodeWindow | undefined } {
+ ): Promise<{ windows: ICodeWindow[]; filesOpenedInWindow: ICodeWindow | undefined }> {
// Keep track of used windows and remember
// if files have been opened in one of them
@@ -492,7 +492,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
let windowToUseForFiles: ICodeWindow | undefined = undefined;
if (fileToCheck?.fileUri && !openFilesInNewWindow) {
if (openConfig.context === OpenContext.DESKTOP || openConfig.context === OpenContext.CLI || openConfig.context === OpenContext.DOCK) {
- windowToUseForFiles = findWindowOnFile(windows, fileToCheck.fileUri, workspace => workspace.configPath.scheme === Schemas.file ? this.workspacesManagementMainService.resolveLocalWorkspaceSync(workspace.configPath) : undefined);
+ windowToUseForFiles = await findWindowOnFile(windows, fileToCheck.fileUri, async workspace => workspace.configPath.scheme === Schemas.file ? this.workspacesManagementMainService.resolveLocalWorkspace(workspace.configPath) : undefined);
}
if (!windowToUseForFiles) {
@@ -701,14 +701,14 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
});
}
- private getPathsToOpen(openConfig: IOpenConfiguration): IPathToOpen[] {
+ private async getPathsToOpen(openConfig: IOpenConfiguration): Promise<IPathToOpen[]> {
let pathsToOpen: IPathToOpen[];
let isCommandLineOrAPICall = false;
let restoredWindows = false;
// Extract paths: from API
if (openConfig.urisToOpen && openConfig.urisToOpen.length > 0) {
- pathsToOpen = this.doExtractPathsFromAPI(openConfig);
+ pathsToOpen = await this.doExtractPathsFromAPI(openConfig);
isCommandLineOrAPICall = true;
}
@@ -719,7 +719,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
// Extract paths: from CLI
else if (openConfig.cli._.length || openConfig.cli['folder-uri'] || openConfig.cli['file-uri']) {
- pathsToOpen = this.doExtractPathsFromCLI(openConfig.cli);
+ pathsToOpen = await this.doExtractPathsFromCLI(openConfig.cli);
if (pathsToOpen.length === 0) {
pathsToOpen.push(Object.create(null)); // add an empty window if we did not have windows to open from command line
}
@@ -729,7 +729,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
// Extract paths: from previous session
else {
- pathsToOpen = this.doGetPathsFromLastSession();
+ pathsToOpen = await this.doGetPathsFromLastSession();
if (pathsToOpen.length === 0) {
pathsToOpen.push(Object.create(null)); // add an empty window if we did not have windows to restore
}
@@ -746,7 +746,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
if (foldersToOpen.length > 1) {
const remoteAuthority = foldersToOpen[0].remoteAuthority;
if (foldersToOpen.every(folderToOpen => isEqualAuthority(folderToOpen.remoteAuthority, remoteAuthority))) { // only if all folder have the same authority
- const workspace = this.workspacesManagementMainService.createUntitledWorkspaceSync(foldersToOpen.map(folder => ({ uri: folder.workspace.uri })));
+ const workspace = await this.workspacesManagementMainService.createUntitledWorkspace(foldersToOpen.map(folder => ({ uri: folder.workspace.uri })));
// Add workspace and remove folders thereby
pathsToOpen.push({ workspace, remoteAuthority });
@@ -761,48 +761,53 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
// Use `unshift` to ensure any new window to open comes last
// for proper focus treatment.
if (openConfig.initialStartup && !restoredWindows && this.configurationService.getValue<IWindowSettings | undefined>('window')?.restoreWindows === 'preserve') {
- pathsToOpen.unshift(...this.doGetPathsFromLastSession().filter(path => isWorkspacePathToOpen(path) || isSingleFolderWorkspacePathToOpen(path) || path.backupPath));
+ const lastSessionPaths = await this.doGetPathsFromLastSession();
+ pathsToOpen.unshift(...lastSessionPaths.filter(path => isWorkspacePathToOpen(path) || isSingleFolderWorkspacePathToOpen(path) || path.backupPath));
}
return pathsToOpen;
}
- private doExtractPathsFromAPI(openConfig: IOpenConfiguration): IPathToOpen[] {
- const pathsToOpen: IPathToOpen[] = [];
- const pathResolveOptions: IPathResolveOptions = { gotoLineMode: openConfig.gotoLineMode, remoteAuthority: openConfig.remoteAuthority };
- for (const pathToOpen of coalesce(openConfig.urisToOpen || [])) {
- const path = this.resolveOpenable(pathToOpen, pathResolveOptions);
+ private async doExtractPathsFromAPI(openConfig: IOpenConfiguration): Promise<IPathToOpen[]> {
+ const pathResolveOptions: IPathResolveOptions = {
+ gotoLineMode: openConfig.gotoLineMode,
+ remoteAuthority: openConfig.remoteAuthority
+ };
+
+ const pathsToOpen = await Promise.all(coalesce(openConfig.urisToOpen || []).map(async pathToOpen => {
+ const path = await this.resolveOpenable(pathToOpen, pathResolveOptions);
// Path exists
if (path) {
path.label = pathToOpen.label;
- pathsToOpen.push(path);
+
+ return path;
}
// Path does not exist: show a warning box
- else {
- const uri = this.resourceFromOpenable(pathToOpen);
-
- const options: MessageBoxOptions = {
- title: this.productService.nameLong,
- type: 'info',
- buttons: [mnemonicButtonLabel(localize({ key: 'ok', comment: ['&& denotes a mnemonic'] }, "&&OK"))],
- defaultId: 0,
- message: uri.scheme === Schemas.file ? localize('pathNotExistTitle', "Path does not exist") : localize('uriInvalidTitle', "URI can not be opened"),
- detail: uri.scheme === Schemas.file ?
- localize('pathNotExistDetail', "The path '{0}' does not exist on this computer.", getPathLabel(uri, { os: OS, tildify: this.environmentMainService })) :
- localize('uriInvalidDetail', "The URI '{0}' is not valid and can not be opened.", uri.toString(true)),
- noLink: true
- };
+ const uri = this.resourceFromOpenable(pathToOpen);
+
+ const options: MessageBoxOptions = {
+ title: this.productService.nameLong,
+ type: 'info',
+ buttons: [mnemonicButtonLabel(localize({ key: 'ok', comment: ['&& denotes a mnemonic'] }, "&&OK"))],
+ defaultId: 0,
+ message: uri.scheme === Schemas.file ? localize('pathNotExistTitle', "Path does not exist") : localize('uriInvalidTitle', "URI can not be opened"),
+ detail: uri.scheme === Schemas.file ?
+ localize('pathNotExistDetail', "The path '{0}' does not exist on this computer.", getPathLabel(uri, { os: OS, tildify: this.environmentMainService })) :
+ localize('uriInvalidDetail', "The URI '{0}' is not valid and can not be opened.", uri.toString(true)),
+ noLink: true
+ };
+
+ this.dialogMainService.showMessageBox(options, withNullAsUndefined(BrowserWindow.getFocusedWindow()));
- this.dialogMainService.showMessageBox(options, withNullAsUndefined(BrowserWindow.getFocusedWindow()));
- }
- }
+ return undefined;
+ }));
- return pathsToOpen;
+ return coalesce(pathsToOpen);
}
- private doExtractPathsFromCLI(cli: NativeParsedArgs): IPath[] {
+ private async doExtractPathsFromCLI(cli: NativeParsedArgs): Promise<IPath[]> {
const pathsToOpen: IPathToOpen[] = [];
const pathResolveOptions: IPathResolveOptions = {
ignoreFileNotFound: true,
@@ -819,39 +824,39 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
// folder uris
const folderUris = cli['folder-uri'];
if (folderUris) {
- for (const rawFolderUri of folderUris) {
+ const resolvedFolderUris = await Promise.all(folderUris.map(rawFolderUri => {
const folderUri = this.cliArgToUri(rawFolderUri);
- if (folderUri) {
- const path = this.resolveOpenable({ folderUri }, pathResolveOptions);
- if (path) {
- pathsToOpen.push(path);
- }
+ if (!folderUri) {
+ return undefined;
}
- }
+
+ return this.resolveOpenable({ folderUri }, pathResolveOptions);
+ }));
+
+ pathsToOpen.push(...coalesce(resolvedFolderUris));
}
// file uris
const fileUris = cli['file-uri'];
if (fileUris) {
- for (const rawFileUri of fileUris) {
+ const resolvedFileUris = await Promise.all(fileUris.map(rawFileUri => {
const fileUri = this.cliArgToUri(rawFileUri);
- if (fileUri) {
- const path = this.resolveOpenable(hasWorkspaceFileExtension(rawFileUri) ? { workspaceUri: fileUri } : { fileUri }, pathResolveOptions);
- if (path) {
- pathsToOpen.push(path);
- }
+ if (!fileUri) {
+ return undefined;
}
- }
+
+ return this.resolveOpenable(hasWorkspaceFileExtension(rawFileUri) ? { workspaceUri: fileUri } : { fileUri }, pathResolveOptions);
+ }));
+
+ pathsToOpen.push(...coalesce(resolvedFileUris));
}
// folder or file paths
- const cliPaths = cli._;
- for (const cliPath of cliPaths) {
- const path = pathResolveOptions.remoteAuthority ? this.doResolvePathRemote(cliPath, pathResolveOptions) : this.doResolveFilePath(cliPath, pathResolveOptions);
- if (path) {
- pathsToOpen.push(path);
- }
- }
+ const resolvedCliPaths = await Promise.all(cli._.map(cliPath => {
+ return pathResolveOptions.remoteAuthority ? this.doResolveRemotePath(cliPath, pathResolveOptions) : this.doResolveFilePath(cliPath, pathResolveOptions);
+ }));
+
+ pathsToOpen.push(...coalesce(resolvedCliPaths));
return pathsToOpen;
}
@@ -873,7 +878,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
return undefined;
}
- private doGetPathsFromLastSession(): IPathToOpen[] {
+ private async doGetPathsFromLastSession(): Promise<IPathToOpen[]> {
const restoreWindowsSetting = this.getRestoreWindowsSetting();
switch (restoreWindowsSetting) {
@@ -899,32 +904,33 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
lastSessionWindows.push(this.windowsStateHandler.state.lastActiveWindow);
}
- const pathsToOpen: IPathToOpen[] = [];
- for (const lastSessionWindow of lastSessionWindows) {
+ const pathsToOpen = await Promise.all(lastSessionWindows.map(async lastSessionWindow => {
// Workspaces
if (lastSessionWindow.workspace) {
- const pathToOpen = this.resolveOpenable({ workspaceUri: lastSessionWindow.workspace.configPath }, { remoteAuthority: lastSessionWindow.remoteAuthority, rejectTransientWorkspaces: true /* https://github.com/microsoft/vscode/issues/119695 */ });
+ const pathToOpen = await this.resolveOpenable({ workspaceUri: lastSessionWindow.workspace.configPath }, { remoteAuthority: lastSessionWindow.remoteAuthority, rejectTransientWorkspaces: true /* https://github.com/microsoft/vscode/issues/119695 */ });
if (isWorkspacePathToOpen(pathToOpen)) {
- pathsToOpen.push(pathToOpen);
+ return pathToOpen;
}
}
// Folders
else if (lastSessionWindow.folderUri) {
- const pathToOpen = this.resolveOpenable({ folderUri: lastSessionWindow.folderUri }, { remoteAuthority: lastSessionWindow.remoteAuthority });
+ const pathToOpen = await this.resolveOpenable({ folderUri: lastSessionWindow.folderUri }, { remoteAuthority: lastSessionWindow.remoteAuthority });
if (isSingleFolderWorkspacePathToOpen(pathToOpen)) {
- pathsToOpen.push(pathToOpen);
+ return pathToOpen;
}
}
// Empty window, potentially editors open to be restored
else if (restoreWindowsSetting !== 'folders' && lastSessionWindow.backupPath) {
- pathsToOpen.push({ backupPath: lastSessionWindow.backupPath, remoteAuthority: lastSessionWindow.remoteAuthority });
+ return { backupPath: lastSessionWindow.backupPath, remoteAuthority: lastSessionWindow.remoteAuthority };
}
- }
- return pathsToOpen;
+ return undefined;
+ }));
+
+ return coalesce(pathsToOpen);
}
}
}
@@ -945,7 +951,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
return restoreWindows;
}
- private resolveOpenable(openable: IWindowOpenable, options: IPathResolveOptions = Object.create(null)): IPathToOpen | undefined {
+ private async resolveOpenable(openable: IWindowOpenable, options: IPathResolveOptions = Object.create(null)): Promise<IPathToOpen | undefined> {
// handle file:// openables with some extra validation
const uri = this.resourceFromOpenable(openable);
@@ -1008,7 +1014,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
return openable.fileUri;
}
- private doResolveFilePath(path: string, options: IPathResolveOptions): IPathToOpen<ITextEditorOptions> | undefined {
+ private async doResolveFilePath(path: string, options: IPathResolveOptions): Promise<IPathToOpen<ITextEditorOptions> | undefined> {
// Extract line/col information from path
let lineNumber: number | undefined;
@@ -1021,14 +1027,14 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
path = sanitizeFilePath(normalize(path), cwd());
try {
- const pathStat = statSync(path);
+ const pathStat = await Promises.stat(path);
// File
if (pathStat.isFile()) {
// Workspace (unless disabled via flag)
if (!options.forceOpenWorkspaceAsFile) {
- const workspace = this.workspacesManagementMainService.resolveLocalWorkspaceSync(URI.file(path));
+ const workspace = await this.workspacesManagementMainService.resolveLocalWorkspace(URI.file(path));
if (workspace) {
// If the workspace is transient and we are to ignore
@@ -1096,7 +1102,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
return undefined;
}
- private doResolvePathRemote(path: string, options: IPathResolveOptions): IPathToOpen<ITextEditorOptions> | undefined {
+ private doResolveRemotePath(path: string, options: IPathResolveOptions): IPathToOpen<ITextEditorOptions> | undefined {
const first = path.charCodeAt(0);
const remoteAuthority = options.remoteAuthority;
@@ -1197,7 +1203,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
return { openFolderInNewWindow: !!openFolderInNewWindow, openFilesInNewWindow };
}
- openExtensionDevelopmentHostWindow(extensionDevelopmentPaths: string[], openConfig: IOpenConfiguration): ICodeWindow[] {
+ async openExtensionDevelopmentHostWindow(extensionDevelopmentPaths: string[], openConfig: IOpenConfiguration): Promise<ICodeWindow[]> {
// Reload an existing extension development host window on the same path
// We currently do not allow more than one extension development window
diff --git a/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts b/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts
index f211c37ae0a..9b64d540ba4 100644
--- a/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts
+++ b/src/vs/platform/windows/test/electron-main/windowsFinder.test.ts
@@ -28,7 +28,7 @@ suite('WindowsFinder', () => {
};
const testWorkspaceFolders = toWorkspaceFolders([{ path: join(fixturesFolder, 'vscode_workspace_1_folder') }, { path: join(fixturesFolder, 'vscode_workspace_2_folder') }], testWorkspace.configPath, extUriBiasedIgnorePathCase);
- const localWorkspaceResolver = (workspace: any) => { return workspace === testWorkspace ? { id: testWorkspace.id, configPath: workspace.configPath, folders: testWorkspaceFolders } : undefined; };
+ const localWorkspaceResolver = async (workspace: any) => { return workspace === testWorkspace ? { id: testWorkspace.id, configPath: workspace.configPath, folders: testWorkspaceFolders } : undefined; };
function createTestCodeWindow(options: { lastFocusTime: number; openedFolderUri?: URI; openedWorkspace?: IWorkspaceIdentifier }): ICodeWindow {
return new class implements ICodeWindow {
@@ -83,28 +83,28 @@ suite('WindowsFinder', () => {
noVscodeFolderWindow,
];
- test('New window without folder when no windows exist', () => {
- assert.strictEqual(findWindowOnFile([], URI.file('nonexisting'), localWorkspaceResolver), undefined);
- assert.strictEqual(findWindowOnFile([], URI.file(join(fixturesFolder, 'no_vscode_folder', 'file.txt')), localWorkspaceResolver), undefined);
+ test('New window without folder when no windows exist', async () => {
+ assert.strictEqual(await findWindowOnFile([], URI.file('nonexisting'), localWorkspaceResolver), undefined);
+ assert.strictEqual(await findWindowOnFile([], URI.file(join(fixturesFolder, 'no_vscode_folder', 'file.txt')), localWorkspaceResolver), undefined);
});
- test('Existing window with folder', () => {
- assert.strictEqual(findWindowOnFile(windows, URI.file(join(fixturesFolder, 'no_vscode_folder', 'file.txt')), localWorkspaceResolver), noVscodeFolderWindow);
+ test('Existing window with folder', async () => {
+ assert.strictEqual(await findWindowOnFile(windows, URI.file(join(fixturesFolder, 'no_vscode_folder', 'file.txt')), localWorkspaceResolver), noVscodeFolderWindow);
- assert.strictEqual(findWindowOnFile(windows, URI.file(join(fixturesFolder, 'vscode_folder', 'file.txt')), localWorkspaceResolver), vscodeFolderWindow);
+ assert.strictEqual(await findWindowOnFile(windows, URI.file(join(fixturesFolder, 'vscode_folder', 'file.txt')), localWorkspaceResolver), vscodeFolderWindow);
const window: ICodeWindow = createTestCodeWindow({ lastFocusTime: 1, openedFolderUri: URI.file(join(fixturesFolder, 'vscode_folder', 'nested_folder')) });
- assert.strictEqual(findWindowOnFile([window], URI.file(join(fixturesFolder, 'vscode_folder', 'nested_folder', 'subfolder', 'file.txt')), localWorkspaceResolver), window);
+ assert.strictEqual(await findWindowOnFile([window], URI.file(join(fixturesFolder, 'vscode_folder', 'nested_folder', 'subfolder', 'file.txt')), localWorkspaceResolver), window);
});
- test('More specific existing window wins', () => {
+ test('More specific existing window wins', async () => {
const window: ICodeWindow = createTestCodeWindow({ lastFocusTime: 2, openedFolderUri: URI.file(join(fixturesFolder, 'no_vscode_folder')) });
const nestedFolderWindow: ICodeWindow = createTestCodeWindow({ lastFocusTime: 1, openedFolderUri: URI.file(join(fixturesFolder, 'no_vscode_folder', 'nested_folder')) });
- assert.strictEqual(findWindowOnFile([window, nestedFolderWindow], URI.file(join(fixturesFolder, 'no_vscode_folder', 'nested_folder', 'subfolder', 'file.txt')), localWorkspaceResolver), nestedFolderWindow);
+ assert.strictEqual(await findWindowOnFile([window, nestedFolderWindow], URI.file(join(fixturesFolder, 'no_vscode_folder', 'nested_folder', 'subfolder', 'file.txt')), localWorkspaceResolver), nestedFolderWindow);
});
- test('Workspace folder wins', () => {
+ test('Workspace folder wins', async () => {
const window: ICodeWindow = createTestCodeWindow({ lastFocusTime: 1, openedWorkspace: testWorkspace });
- assert.strictEqual(findWindowOnFile([window], URI.file(join(fixturesFolder, 'vscode_workspace_2_folder', 'nested_vscode_folder', 'subfolder', 'file.txt')), localWorkspaceResolver), window);
+ assert.strictEqual(await findWindowOnFile([window], URI.file(join(fixturesFolder, 'vscode_workspace_2_folder', 'nested_vscode_folder', 'subfolder', 'file.txt')), localWorkspaceResolver), window);
});
});
diff --git a/src/vs/platform/workspaces/electron-main/workspacesManagementMainService.ts b/src/vs/platform/workspaces/electron-main/workspacesManagementMainService.ts
index 7edf65f704b..de73f7f5572 100644
--- a/src/vs/platform/workspaces/electron-main/workspacesManagementMainService.ts
+++ b/src/vs/platform/workspaces/electron-main/workspacesManagementMainService.ts
@@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import { BrowserWindow, MessageBoxOptions } from 'electron';
-import { existsSync, mkdirSync, readFileSync } from 'fs';
import { Emitter, Event } from 'vs/base/common/event';
import { parse } from 'vs/base/common/json';
import { mnemonicButtonLabel } from 'vs/base/common/labels';
@@ -14,7 +13,7 @@ import { dirname, join } from 'vs/base/common/path';
import { basename, extUriBiasedIgnorePathCase, joinPath, originalFSPath } from 'vs/base/common/resources';
import { withNullAsUndefined } from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
-import { Promises, readdirSync, rimrafSync, writeFileSync } from 'vs/base/node/pfs';
+import { Promises } from 'vs/base/node/pfs';
import { localize } from 'vs/nls';
import { IBackupMainService } from 'vs/platform/backup/electron-main/backup';
import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogMainService';
@@ -46,15 +45,12 @@ export interface IWorkspacesManagementMainService {
enterWorkspace(intoWindow: ICodeWindow, openedWindows: ICodeWindow[], path: URI): Promise<IEnterWorkspaceResult | undefined>;
createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise<IWorkspaceIdentifier>;
- createUntitledWorkspaceSync(folders?: IWorkspaceFolderCreationData[]): IWorkspaceIdentifier;
deleteUntitledWorkspace(workspace: IWorkspaceIdentifier): Promise<void>;
- deleteUntitledWorkspaceSync(workspace: IWorkspaceIdentifier): void;
- getUntitledWorkspacesSync(): IUntitledWorkspaceInfo[];
+ getUntitledWorkspaces(): IUntitledWorkspaceInfo[];
isUntitledWorkspace(workspace: IWorkspaceIdentifier): boolean;
- resolveLocalWorkspaceSync(path: URI): IResolvedWorkspace | undefined;
resolveLocalWorkspace(path: URI): Promise<IResolvedWorkspace | undefined>;
getWorkspaceIdentifier(workspacePath: URI): Promise<IWorkspaceIdentifier>;
@@ -64,14 +60,16 @@ export class WorkspacesManagementMainService extends Disposable implements IWork
declare readonly _serviceBrand: undefined;
- private readonly untitledWorkspacesHome = this.environmentMainService.untitledWorkspacesHome; // local URI that contains all untitled workspaces
-
private readonly _onDidDeleteUntitledWorkspace = this._register(new Emitter<IWorkspaceIdentifier>());
readonly onDidDeleteUntitledWorkspace: Event<IWorkspaceIdentifier> = this._onDidDeleteUntitledWorkspace.event;
private readonly _onDidEnterWorkspace = this._register(new Emitter<IWorkspaceEnteredEvent>());
readonly onDidEnterWorkspace: Event<IWorkspaceEnteredEvent> = this._onDidEnterWorkspace.event;
+ private readonly untitledWorkspacesHome = this.environmentMainService.untitledWorkspacesHome; // local URI that contains all untitled workspaces
+
+ private untitledWorkspaces: IUntitledWorkspaceInfo[] = [];
+
constructor(
@IEnvironmentMainService private readonly environmentMainService: IEnvironmentMainService,
@ILogService private readonly logService: ILogService,
@@ -83,8 +81,28 @@ export class WorkspacesManagementMainService extends Disposable implements IWork
super();
}
- resolveLocalWorkspaceSync(uri: URI): IResolvedWorkspace | undefined {
- return this.doResolveLocalWorkspace(uri, path => readFileSync(path, 'utf8'));
+ async initialize(): Promise<void> {
+
+ // Reset
+ this.untitledWorkspaces = [];
+
+ // Resolve untitled workspaces
+ try {
+ const untitledWorkspacePaths = (await Promises.readdir(this.untitledWorkspacesHome.fsPath)).map(folder => joinPath(this.untitledWorkspacesHome, folder, UNTITLED_WORKSPACE_NAME));
+ for (const untitledWorkspacePath of untitledWorkspacePaths) {
+ const workspace = getWorkspaceIdentifier(untitledWorkspacePath);
+ const resolvedWorkspace = await this.resolveLocalWorkspace(untitledWorkspacePath);
+ if (!resolvedWorkspace) {
+ await this.deleteUntitledWorkspace(workspace);
+ } else {
+ this.untitledWorkspaces.push({ workspace, remoteAuthority: resolvedWorkspace.remoteAuthority });
+ }
+ }
+ } catch (error) {
+ if (error.code !== 'ENOENT') {
+ this.logService.warn(`Unable to read folders in ${this.untitledWorkspacesHome} (${error}).`);
+ }
+ }
}
resolveLocalWorkspace(uri: URI): Promise<IResolvedWorkspace | undefined> {
@@ -158,15 +176,7 @@ export class WorkspacesManagementMainService extends Disposable implements IWork
await Promises.mkdir(dirname(configPath), { recursive: true });
await Promises.writeFile(configPath, JSON.stringify(storedWorkspace, null, '\t'));
- return workspace;
- }
-
- createUntitledWorkspaceSync(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): IWorkspaceIdentifier {
- const { workspace, storedWorkspace } = this.newUntitledWorkspace(folders, remoteAuthority);
- const configPath = workspace.configPath.fsPath;
-
- mkdirSync(dirname(configPath), { recursive: true });
- writeFileSync(configPath, JSON.stringify(storedWorkspace, null, '\t'));
+ this.untitledWorkspaces.push({ workspace, remoteAuthority });
return workspace;
}
@@ -196,16 +206,16 @@ export class WorkspacesManagementMainService extends Disposable implements IWork
return isUntitledWorkspace(workspace.configPath, this.environmentMainService);
}
- deleteUntitledWorkspaceSync(workspace: IWorkspaceIdentifier): void {
+ async deleteUntitledWorkspace(workspace: IWorkspaceIdentifier): Promise<void> {
if (!this.isUntitledWorkspace(workspace)) {
return; // only supported for untitled workspaces
}
// Delete from disk
- this.doDeleteUntitledWorkspaceSync(workspace);
+ await this.doDeleteUntitledWorkspace(workspace);
+ // unset workspace from profiles
if (this.userDataProfilesMainService.isEnabled()) {
- // unset workspace from profiles
this.userDataProfilesMainService.unsetWorkspace(workspace);
}
@@ -213,47 +223,28 @@ export class WorkspacesManagementMainService extends Disposable implements IWork
this._onDidDeleteUntitledWorkspace.fire(workspace);
}
- async deleteUntitledWorkspace(workspace: IWorkspaceIdentifier): Promise<void> {
- this.deleteUntitledWorkspaceSync(workspace);
- }
-
- private doDeleteUntitledWorkspaceSync(workspace: IWorkspaceIdentifier): void {
+ private async doDeleteUntitledWorkspace(workspace: IWorkspaceIdentifier): Promise<void> {
const configPath = originalFSPath(workspace.configPath);
try {
// Delete Workspace
- rimrafSync(dirname(configPath));
+ await Promises.rm(dirname(configPath));
// Mark Workspace Storage to be deleted
const workspaceStoragePath = join(this.environmentMainService.workspaceStorageHome.fsPath, workspace.id);
- if (existsSync(workspaceStoragePath)) {
- writeFileSync(join(workspaceStoragePath, 'obsolete'), '');
+ if (await Promises.exists(workspaceStoragePath)) {
+ await Promises.writeFile(join(workspaceStoragePath, 'obsolete'), '');
}
+
+ // Remove from list
+ this.untitledWorkspaces = this.untitledWorkspaces.filter(untitledWorkspace => untitledWorkspace.workspace.id !== workspace.id);
} catch (error) {
this.logService.warn(`Unable to delete untitled workspace ${configPath} (${error}).`);
}
}
- getUntitledWorkspacesSync(): IUntitledWorkspaceInfo[] {
- const untitledWorkspaces: IUntitledWorkspaceInfo[] = [];
- try {
- const untitledWorkspacePaths = readdirSync(this.untitledWorkspacesHome.fsPath).map(folder => joinPath(this.untitledWorkspacesHome, folder, UNTITLED_WORKSPACE_NAME));
- for (const untitledWorkspacePath of untitledWorkspacePaths) {
- const workspace = getWorkspaceIdentifier(untitledWorkspacePath);
- const resolvedWorkspace = this.resolveLocalWorkspaceSync(untitledWorkspacePath);
- if (!resolvedWorkspace) {
- this.doDeleteUntitledWorkspaceSync(workspace);
- } else {
- untitledWorkspaces.push({ workspace, remoteAuthority: resolvedWorkspace.remoteAuthority });
- }
- }
- } catch (error) {
- if (error.code !== 'ENOENT') {
- this.logService.warn(`Unable to read folders in ${this.untitledWorkspacesHome} (${error}).`);
- }
- }
-
- return untitledWorkspaces;
+ getUntitledWorkspaces(): IUntitledWorkspaceInfo[] {
+ return this.untitledWorkspaces;
}
async enterWorkspace(window: ICodeWindow, windows: ICodeWindow[], path: URI): Promise<IEnterWorkspaceResult | undefined> {
@@ -266,7 +257,7 @@ export class WorkspacesManagementMainService extends Disposable implements IWork
return undefined; // return early if the workspace is not valid
}
- const result = this.doEnterWorkspace(window, getWorkspaceIdentifier(path));
+ const result = await this.doEnterWorkspace(window, getWorkspaceIdentifier(path));
if (!result) {
return undefined;
}
@@ -306,7 +297,7 @@ export class WorkspacesManagementMainService extends Disposable implements IWork
return true; // OK
}
- private doEnterWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier): IEnterWorkspaceResult | undefined {
+ private async doEnterWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier): Promise<IEnterWorkspaceResult | undefined> {
if (!window.config) {
return undefined;
}
@@ -316,12 +307,16 @@ export class WorkspacesManagementMainService extends Disposable implements IWork
// Register window for backups and migrate current backups over
let backupPath: string | undefined;
if (!window.config.extensionDevelopmentPath) {
- backupPath = this.backupMainService.registerWorkspaceBackup({ workspace, remoteAuthority: window.remoteAuthority }, window.config.backupPath);
+ if (window.config.backupPath) {
+ backupPath = await this.backupMainService.registerWorkspaceBackup({ workspace, remoteAuthority: window.remoteAuthority }, window.config.backupPath);
+ } else {
+ backupPath = this.backupMainService.registerWorkspaceBackup({ workspace, remoteAuthority: window.remoteAuthority });
+ }
}
// if the window was opened on an untitled workspace, delete it.
if (isWorkspaceIdentifier(window.openedWorkspace) && this.isUntitledWorkspace(window.openedWorkspace)) {
- this.deleteUntitledWorkspaceSync(window.openedWorkspace);
+ await this.deleteUntitledWorkspace(window.openedWorkspace);
}
// Update window configuration properly based on transition to workspace
diff --git a/src/vs/platform/workspaces/node/workspaces.ts b/src/vs/platform/workspaces/node/workspaces.ts
index 9ca6e95faa7..beffbbe2776 100644
--- a/src/vs/platform/workspaces/node/workspaces.ts
+++ b/src/vs/platform/workspaces/node/workspaces.ts
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { createHash } from 'crypto';
-import { Stats, statSync } from 'fs';
+import { Stats } from 'fs';
import { Schemas } from 'vs/base/common/network';
import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
import { originalFSPath } from 'vs/base/common/resources';
@@ -53,13 +53,15 @@ export function getSingleFolderWorkspaceIdentifier(folderUri: URI, folderStat?:
return createHash('md5').update(folderUri.toString()).digest('hex');
}
- // Local: produce a hash from the path and include creation time as salt
+ // Local: we use the ctime as extra salt to the
+ // identifier so that folders getting recreated
+ // result in a different identifier. However, if
+ // the stat is not provided we return `undefined`
+ // to ensure identifiers are stable for the given
+ // URI.
+
if (!folderStat) {
- try {
- folderStat = statSync(folderUri.fsPath);
- } catch (error) {
- return undefined; // folder does not exist
- }
+ return undefined;
}
let ctime: number | undefined;
@@ -75,8 +77,6 @@ export function getSingleFolderWorkspaceIdentifier(folderUri: URI, folderStat?:
}
}
- // we use the ctime as extra salt to the ID so that we catch the case of a folder getting
- // deleted and recreated. in that case we do not want to carry over previous state
return createHash('md5').update(folderUri.fsPath).update(ctime ? String(ctime) : '').digest('hex');
}
diff --git a/src/vs/platform/workspaces/test/electron-main/workspaces.test.ts b/src/vs/platform/workspaces/test/electron-main/workspaces.test.ts
index d0a8bc804a7..42427b5ea6d 100644
--- a/src/vs/platform/workspaces/test/electron-main/workspaces.test.ts
+++ b/src/vs/platform/workspaces/test/electron-main/workspaces.test.ts
@@ -41,7 +41,7 @@ flakySuite('Workspaces', () => {
fs.mkdirSync(path.join(testDir, 'f1'));
const localExistingUri = URI.file(path.join(testDir, 'f1'));
- const localExistingUriId = getSingleFolderWorkspaceIdentifier(localExistingUri);
+ const localExistingUriId = getSingleFolderWorkspaceIdentifier(localExistingUri, fs.statSync(localExistingUri.fsPath));
assert.ok(localExistingUriId?.id);
});
diff --git a/src/vs/platform/workspaces/test/electron-main/workspacesManagementMainService.test.ts b/src/vs/platform/workspaces/test/electron-main/workspacesManagementMainService.test.ts
index f45a4e289ff..362c33c1af7 100644
--- a/src/vs/platform/workspaces/test/electron-main/workspacesManagementMainService.test.ts
+++ b/src/vs/platform/workspaces/test/electron-main/workspacesManagementMainService.test.ts
@@ -53,7 +53,9 @@ flakySuite('WorkspacesManagementMainService', () => {
isHotExitEnabled(): boolean { throw new Error('Method not implemented.'); }
getEmptyWindowBackups(): IEmptyWindowBackupInfo[] { throw new Error('Method not implemented.'); }
- registerWorkspaceBackup(workspace: IWorkspaceBackupInfo, migrateFrom?: string | undefined): string { throw new Error('Method not implemented.'); }
+ registerWorkspaceBackup(workspaceInfo: IWorkspaceBackupInfo): string;
+ registerWorkspaceBackup(workspaceInfo: IWorkspaceBackupInfo, migrateFrom: string): Promise<string>;
+ registerWorkspaceBackup(workspaceInfo: unknown, migrateFrom?: unknown): string | Promise<string> { throw new Error('Method not implemented.'); }
registerFolderBackup(folder: IFolderBackupInfo): string { throw new Error('Method not implemented.'); }
registerEmptyWindowBackup(empty: IEmptyWindowBackupInfo): string { throw new Error('Method not implemented.'); }
async getDirtyWorkspaces(): Promise<(IWorkspaceBackupInfo | IFolderBackupInfo)[]> { return []; }
@@ -80,10 +82,6 @@ flakySuite('WorkspacesManagementMainService', () => {
fs.writeFileSync(workspaceConfigPath, JSON.stringify(ws));
}
- function createUntitledWorkspaceSync(folders: string[], names?: string[]) {
- return service.createUntitledWorkspaceSync(folders.map((folder, index) => ({ uri: URI.file(folder), name: names ? names[index] : undefined } as IWorkspaceFolderCreationData)));
- }
-
let testDir: string;
let untitledWorkspacesHomePath: string;
let environmentMainService: EnvironmentMainService;
@@ -184,54 +182,6 @@ flakySuite('WorkspacesManagementMainService', () => {
assert.strictEqual(ws.remoteAuthority, 'server');
});
- test('createWorkspaceSync (folders)', () => {
- const workspace = createUntitledWorkspaceSync([cwd, tmpDir]);
- assert.ok(workspace);
- assert.ok(fs.existsSync(workspace.configPath.fsPath));
- assert.ok(service.isUntitledWorkspace(workspace));
-
- const ws = JSON.parse(fs.readFileSync(workspace.configPath.fsPath).toString()) as IStoredWorkspace;
- assert.strictEqual(ws.folders.length, 2);
- assertPathEquals((<IRawFileWorkspaceFolder>ws.folders[0]).path, cwd);
- assertPathEquals((<IRawFileWorkspaceFolder>ws.folders[1]).path, tmpDir);
-
- assert.ok(!(<IRawFileWorkspaceFolder>ws.folders[0]).name);
- assert.ok(!(<IRawFileWorkspaceFolder>ws.folders[1]).name);
- });
-
- test('createWorkspaceSync (folders with names)', () => {
- const workspace = createUntitledWorkspaceSync([cwd, tmpDir], ['currentworkingdirectory', 'tempdir']);
- assert.ok(workspace);
- assert.ok(fs.existsSync(workspace.configPath.fsPath));
- assert.ok(service.isUntitledWorkspace(workspace));
-
- const ws = JSON.parse(fs.readFileSync(workspace.configPath.fsPath).toString()) as IStoredWorkspace;
- assert.strictEqual(ws.folders.length, 2);
- assertPathEquals((<IRawFileWorkspaceFolder>ws.folders[0]).path, cwd);
- assertPathEquals((<IRawFileWorkspaceFolder>ws.folders[1]).path, tmpDir);
-
- assert.strictEqual((<IRawFileWorkspaceFolder>ws.folders[0]).name, 'currentworkingdirectory');
- assert.strictEqual((<IRawFileWorkspaceFolder>ws.folders[1]).name, 'tempdir');
- });
-
- test('createUntitledWorkspaceSync (folders as other resource URIs)', () => {
- const folder1URI = URI.parse('myscheme://server/work/p/f1');
- const folder2URI = URI.parse('myscheme://server/work/o/f3');
-
- const workspace = service.createUntitledWorkspaceSync([{ uri: folder1URI }, { uri: folder2URI }]);
- assert.ok(workspace);
- assert.ok(fs.existsSync(workspace.configPath.fsPath));
- assert.ok(service.isUntitledWorkspace(workspace));
-
- const ws = JSON.parse(fs.readFileSync(workspace.configPath.fsPath).toString()) as IStoredWorkspace;
- assert.strictEqual(ws.folders.length, 2);
- assert.strictEqual((<IRawUriWorkspaceFolder>ws.folders[0]).uri, folder1URI.toString(true));
- assert.strictEqual((<IRawUriWorkspaceFolder>ws.folders[1]).uri, folder2URI.toString(true));
-
- assert.ok(!(<IRawFileWorkspaceFolder>ws.folders[0]).name);
- assert.ok(!(<IRawFileWorkspaceFolder>ws.folders[1]).name);
- });
-
test('resolveWorkspace', async () => {
const workspace = await createUntitledWorkspace([cwd, tmpDir]);
assert.ok(await service.resolveLocalWorkspace(workspace.configPath));
@@ -255,58 +205,35 @@ flakySuite('WorkspacesManagementMainService', () => {
assert.ok(resolvedTransient?.transient);
});
- test('resolveWorkspaceSync', async () => {
- const workspace = await createUntitledWorkspace([cwd, tmpDir]);
- assert.ok(service.resolveLocalWorkspaceSync(workspace.configPath));
-
- // make it a valid workspace path
- const newPath = path.join(path.dirname(workspace.configPath.fsPath), `workspace.${WORKSPACE_EXTENSION}`);
- fs.renameSync(workspace.configPath.fsPath, newPath);
- workspace.configPath = URI.file(newPath);
-
- const resolved = service.resolveLocalWorkspaceSync(workspace.configPath);
- assert.strictEqual(2, resolved!.folders.length);
- assertEqualURI(resolved!.configPath, workspace.configPath);
- assert.ok(resolved!.id);
- fs.writeFileSync(workspace.configPath.fsPath, JSON.stringify({ something: 'something' })); // invalid workspace
-
- const resolvedInvalid = service.resolveLocalWorkspaceSync(workspace.configPath);
- assert.ok(!resolvedInvalid);
-
- fs.writeFileSync(workspace.configPath.fsPath, JSON.stringify({ transient: true, folders: [] })); // transient worksapce
- const resolvedTransient = service.resolveLocalWorkspaceSync(workspace.configPath);
- assert.ok(resolvedTransient?.transient);
- });
-
- test('resolveWorkspaceSync (support relative paths)', async () => {
+ test('resolveWorkspace (support relative paths)', async () => {
const workspace = await createUntitledWorkspace([cwd, tmpDir]);
fs.writeFileSync(workspace.configPath.fsPath, JSON.stringify({ folders: [{ path: './ticino-playground/lib' }] }));
- const resolved = service.resolveLocalWorkspaceSync(workspace.configPath);
+ const resolved = await service.resolveLocalWorkspace(workspace.configPath);
assertEqualURI(resolved!.folders[0].uri, URI.file(path.join(path.dirname(workspace.configPath.fsPath), 'ticino-playground', 'lib')));
});
- test('resolveWorkspaceSync (support relative paths #2)', async () => {
+ test('resolveWorkspace (support relative paths #2)', async () => {
const workspace = await createUntitledWorkspace([cwd, tmpDir]);
fs.writeFileSync(workspace.configPath.fsPath, JSON.stringify({ folders: [{ path: './ticino-playground/lib/../other' }] }));
- const resolved = service.resolveLocalWorkspaceSync(workspace.configPath);
+ const resolved = await service.resolveLocalWorkspace(workspace.configPath);
assertEqualURI(resolved!.folders[0].uri, URI.file(path.join(path.dirname(workspace.configPath.fsPath), 'ticino-playground', 'other')));
});
- test('resolveWorkspaceSync (support relative paths #3)', async () => {
+ test('resolveWorkspace (support relative paths #3)', async () => {
const workspace = await createUntitledWorkspace([cwd, tmpDir]);
fs.writeFileSync(workspace.configPath.fsPath, JSON.stringify({ folders: [{ path: 'ticino-playground/lib' }] }));
- const resolved = service.resolveLocalWorkspaceSync(workspace.configPath);
+ const resolved = await service.resolveLocalWorkspace(workspace.configPath);
assertEqualURI(resolved!.folders[0].uri, URI.file(path.join(path.dirname(workspace.configPath.fsPath), 'ticino-playground', 'lib')));
});
- test('resolveWorkspaceSync (support invalid JSON via fault tolerant parsing)', async () => {
+ test('resolveWorkspace (support invalid JSON via fault tolerant parsing)', async () => {
const workspace = await createUntitledWorkspace([cwd, tmpDir]);
fs.writeFileSync(workspace.configPath.fsPath, '{ "folders": [ { "path": "./ticino-playground/lib" } , ] }'); // trailing comma
- const resolved = service.resolveLocalWorkspaceSync(workspace.configPath);
+ const resolved = await service.resolveLocalWorkspace(workspace.configPath);
assertEqualURI(resolved!.folders[0].uri, URI.file(path.join(path.dirname(workspace.configPath.fsPath), 'ticino-playground', 'lib')));
});
@@ -366,7 +293,7 @@ flakySuite('WorkspacesManagementMainService', () => {
const newContent = rewriteWorkspaceFileForNewLocation(origContent, workspace.configPath, false, workspaceConfigPath, extUriBiasedIgnorePathCase);
assert.strictEqual(0, newContent.indexOf('// this is a comment'));
- service.deleteUntitledWorkspaceSync(workspace);
+ await service.deleteUntitledWorkspace(workspace);
});
test('rewriteWorkspaceFileForNewLocation (preserves forward slashes)', async () => {
@@ -379,7 +306,7 @@ flakySuite('WorkspacesManagementMainService', () => {
const newContent = rewriteWorkspaceFileForNewLocation(origContent, workspace.configPath, false, workspaceConfigPath, extUriBiasedIgnorePathCase);
const ws = (JSON.parse(newContent) as IStoredWorkspace);
assert.ok(ws.folders.every(f => (<IRawFileWorkspaceFolder>f).path.indexOf('\\') < 0));
- service.deleteUntitledWorkspaceSync(workspace);
+ await service.deleteUntitledWorkspace(workspace);
});
(!isWindows ? test.skip : test)('rewriteWorkspaceFileForNewLocation (unc paths)', async () => {
@@ -397,34 +324,37 @@ flakySuite('WorkspacesManagementMainService', () => {
assertPathEquals((<IRawFileWorkspaceFolder>ws.folders[1]).path, folder2Location);
assertPathEquals((<IRawFileWorkspaceFolder>ws.folders[2]).path, 'inner/more');
- service.deleteUntitledWorkspaceSync(workspace);
+ await service.deleteUntitledWorkspace(workspace);
});
- test('deleteUntitledWorkspaceSync (untitled)', async () => {
+ test('deleteUntitledWorkspace (untitled)', async () => {
const workspace = await createUntitledWorkspace([cwd, tmpDir]);
assert.ok(fs.existsSync(workspace.configPath.fsPath));
- service.deleteUntitledWorkspaceSync(workspace);
+ await service.deleteUntitledWorkspace(workspace);
assert.ok(!fs.existsSync(workspace.configPath.fsPath));
});
- test('deleteUntitledWorkspaceSync (saved)', async () => {
+ test('deleteUntitledWorkspace (saved)', async () => {
const workspace = await createUntitledWorkspace([cwd, tmpDir]);
- service.deleteUntitledWorkspaceSync(workspace);
+ await service.deleteUntitledWorkspace(workspace);
});
- test('getUntitledWorkspaceSync', async function () {
- let untitled = service.getUntitledWorkspacesSync();
+ test('getUntitledWorkspace', async function () {
+ await service.initialize();
+ let untitled = service.getUntitledWorkspaces();
assert.strictEqual(untitled.length, 0);
const untitledOne = await createUntitledWorkspace([cwd, tmpDir]);
assert.ok(fs.existsSync(untitledOne.configPath.fsPath));
- untitled = service.getUntitledWorkspacesSync();
+ await service.initialize();
+ untitled = service.getUntitledWorkspaces();
assert.strictEqual(1, untitled.length);
assert.strictEqual(untitledOne.id, untitled[0].workspace.id);
- service.deleteUntitledWorkspaceSync(untitledOne);
- untitled = service.getUntitledWorkspacesSync();
+ await service.deleteUntitledWorkspace(untitledOne);
+ await service.initialize();
+ untitled = service.getUntitledWorkspaces();
assert.strictEqual(0, untitled.length);
});
});
diff --git a/src/vs/server/node/server.cli.ts b/src/vs/server/node/server.cli.ts
index db42c9ffe3b..d7b1c9b307e 100644
--- a/src/vs/server/node/server.cli.ts
+++ b/src/vs/server/node/server.cli.ts
@@ -12,7 +12,7 @@ import { cwd } from 'vs/base/common/process';
import { dirname, extname, resolve, join } from 'vs/base/common/path';
import { parseArgs, buildHelpMessage, buildVersionMessage, OPTIONS, OptionDescriptions, ErrorReporter } from 'vs/platform/environment/node/argv';
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
-import { createWaitMarkerFile } from 'vs/platform/environment/node/wait';
+import { createWaitMarkerFileSync } from 'vs/platform/environment/node/wait';
import { PipeCommand } from 'vs/workbench/api/node/extHostCLIServer';
import { hasStdinWithoutTty, getStdinFilePath, readFromStdin } from 'vs/platform/environment/node/stdin';
@@ -306,7 +306,7 @@ export async function main(desc: ProductDescription, args: string[]): Promise<vo
console.log('At least one file must be provided to wait for.');
return;
}
- waitMarkerFilePath = createWaitMarkerFile(verbose);
+ waitMarkerFilePath = createWaitMarkerFileSync(verbose);
}
sendToPipe({
diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts
index d6b554ebb17..51986a3ad3c 100644
--- a/src/vs/workbench/api/browser/mainThreadNotebook.ts
+++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts
@@ -11,7 +11,7 @@ import { URI } from 'vs/base/common/uri';
import { NotebookDto } from 'vs/workbench/api/browser/mainThreadNotebookDto';
import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService';
-import { INotebookCellStatusBarItemProvider, INotebookContributionData, NotebookData as NotebookData, NotebookExtensionDescription, TransientCellMetadata, TransientDocumentMetadata, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon';
+import { INotebookCellStatusBarItemProvider, INotebookContributionData, NotebookData as NotebookData, NotebookExtensionDescription, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookContentProvider, INotebookService, SimpleNotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookService';
import { SerializableObjectWithBuffers } from 'vs/workbench/services/extensions/common/proxyIdentifier';
import { ExtHostContext, ExtHostNotebookShape, MainContext, MainThreadNotebookShape } from '../common/extHost.protocol';
@@ -67,15 +67,7 @@ export class MainThreadNotebooks implements MainThreadNotebookShape {
transientOptions: contentOptions
};
},
- save: async (uri: URI, token: CancellationToken) => {
- return this._proxy.$saveNotebook(viewType, uri, token);
- },
- saveAs: async (uri: URI, target: URI, token: CancellationToken) => {
- return this._proxy.$saveNotebookAs(viewType, uri, target, token);
- },
- backup: async (uri: URI, token: CancellationToken) => {
- return this._proxy.$backupNotebook(viewType, uri, token);
- }
+ backup: async (uri: URI, token: CancellationToken) => ''
};
const disposable = new DisposableStore();
@@ -86,19 +78,6 @@ export class MainThreadNotebooks implements MainThreadNotebookShape {
this._notebookProviders.set(viewType, { controller, disposable });
}
- async $updateNotebookProviderOptions(viewType: string, options?: { transientOutputs: boolean; transientCellMetadata: TransientCellMetadata; transientDocumentMetadata: TransientDocumentMetadata }): Promise<void> {
- const provider = this._notebookProviders.get(viewType);
-
- if (provider && options) {
- provider.controller.options = options;
- this._notebookService.listNotebookDocuments().forEach(document => {
- if (document.viewType === viewType) {
- document.transientOptions = provider.controller.options;
- }
- });
- }
- }
-
async $unregisterNotebookProvider(viewType: string): Promise<void> {
const entry = this._notebookProviders.get(viewType);
if (entry) {
@@ -107,7 +86,6 @@ export class MainThreadNotebooks implements MainThreadNotebookShape {
}
}
-
$registerNotebookSerializer(handle: number, extension: NotebookExtensionDescription, viewType: string, options: TransientOptions, data: INotebookContributionData | undefined): void {
const registration = this._notebookService.registerNotebookSerializer(viewType, extension, {
options,
diff --git a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts
index 136d77a68c0..30702b46cd8 100644
--- a/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts
+++ b/src/vs/workbench/api/browser/mainThreadNotebookKernels.ts
@@ -90,6 +90,10 @@ abstract class MainThreadKernel implements INotebookKernel {
this.implementsExecutionOrder = data.supportsExecutionOrder;
event.hasExecutionOrder = true;
}
+ if (data.supportsInterrupt !== undefined) {
+ this.implementsInterrupt = data.supportsInterrupt;
+ event.hasInterruptHandler = true;
+ }
this._onDidChange.fire(event);
}
diff --git a/src/vs/workbench/api/browser/mainThreadSecretState.ts b/src/vs/workbench/api/browser/mainThreadSecretState.ts
index 1c49ff84612..7fc600da2fd 100644
--- a/src/vs/workbench/api/browser/mainThreadSecretState.ts
+++ b/src/vs/workbench/api/browser/mainThreadSecretState.ts
@@ -36,9 +36,11 @@ export class MainThreadSecretState extends Disposable implements MainThreadSecre
}
async $getPassword(extensionId: string, key: string): Promise<string | undefined> {
+ this.logService.trace(`Getting password for ${extensionId} extension:`, key);
const fullKey = await this.getFullKey(extensionId);
const password = await this.credentialsService.getPassword(fullKey, key);
if (!password) {
+ this.logService.trace('No password found for:', key);
return undefined;
}
@@ -63,6 +65,7 @@ export class MainThreadSecretState extends Disposable implements MainThreadSecre
try {
const value = JSON.parse(decrypted);
if (value.extensionId === extensionId) {
+ this.logService.trace('Password found for:', key);
return value.content;
}
} catch (parseError) {
@@ -79,6 +82,7 @@ export class MainThreadSecretState extends Disposable implements MainThreadSecre
}
}
+ this.logService.trace('No password found for:', key);
return undefined;
}
diff --git a/src/vs/workbench/api/browser/mainThreadTreeViews.ts b/src/vs/workbench/api/browser/mainThreadTreeViews.ts
index 9435d9b46fe..4fcd4b8c103 100644
--- a/src/vs/workbench/api/browser/mainThreadTreeViews.ts
+++ b/src/vs/workbench/api/browser/mainThreadTreeViews.ts
@@ -172,7 +172,7 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie
this._register(treeView.onDidChangeVisibility(isVisible => this._proxy.$setVisible(treeViewId, isVisible)));
this._register(treeView.onDidChangeCheckboxState(items => {
this._proxy.$changeCheckboxState(treeViewId, <CheckboxUpdate[]>items.map(item => {
- return { treeItemHandle: item.handle, newState: item.checkboxChecked ?? false };
+ return { treeItemHandle: item.handle, newState: item.checkbox?.isChecked ?? false };
}));
}));
}
diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts
index 9a7f2faa5b5..3de62a38d34 100644
--- a/src/vs/workbench/api/common/extHost.api.impl.ts
+++ b/src/vs/workbench/api/common/extHost.api.impl.ts
@@ -157,7 +157,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
const extHostDocuments = rpcProtocol.set(ExtHostContext.ExtHostDocuments, new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors));
const extHostDocumentContentProviders = rpcProtocol.set(ExtHostContext.ExtHostDocumentContentProviders, new ExtHostDocumentContentProvider(rpcProtocol, extHostDocumentsAndEditors, extHostLogService));
const extHostDocumentSaveParticipant = rpcProtocol.set(ExtHostContext.ExtHostDocumentSaveParticipant, new ExtHostDocumentSaveParticipant(extHostLogService, extHostDocuments, rpcProtocol.getProxy(MainContext.MainThreadBulkEdits)));
- const extHostNotebook = rpcProtocol.set(ExtHostContext.ExtHostNotebook, new ExtHostNotebookController(rpcProtocol, extHostCommands, extHostDocumentsAndEditors, extHostDocuments, extensionStoragePaths));
+ const extHostNotebook = rpcProtocol.set(ExtHostContext.ExtHostNotebook, new ExtHostNotebookController(rpcProtocol, extHostCommands, extHostDocumentsAndEditors, extHostDocuments));
const extHostNotebookDocuments = rpcProtocol.set(ExtHostContext.ExtHostNotebookDocuments, new ExtHostNotebookDocuments(extHostNotebook));
const extHostNotebookEditors = rpcProtocol.set(ExtHostContext.ExtHostNotebookEditors, new ExtHostNotebookEditors(extHostLogService, extHostNotebook));
const extHostNotebookKernels = rpcProtocol.set(ExtHostContext.ExtHostNotebookKernels, new ExtHostNotebookKernels(rpcProtocol, initData, extHostNotebook, extHostCommands, extHostLogService));
diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts
index 98e42625518..d355265276e 100644
--- a/src/vs/workbench/api/common/extHost.protocol.ts
+++ b/src/vs/workbench/api/common/extHost.protocol.ts
@@ -962,7 +962,6 @@ export interface INotebookCellStatusBarListDto {
export interface MainThreadNotebookShape extends IDisposable {
$registerNotebookProvider(extension: notebookCommon.NotebookExtensionDescription, viewType: string, options: notebookCommon.TransientOptions, registration: notebookCommon.INotebookContributionData | undefined): Promise<void>;
- $updateNotebookProviderOptions(viewType: string, options?: { transientOutputs: boolean; transientCellMetadata: notebookCommon.TransientCellMetadata; transientDocumentMetadata: notebookCommon.TransientDocumentMetadata }): Promise<void>;
$unregisterNotebookProvider(viewType: string): Promise<void>;
$registerNotebookSerializer(handle: number, extension: notebookCommon.NotebookExtensionDescription, viewType: string, options: notebookCommon.TransientOptions, registration: notebookCommon.INotebookContributionData | undefined): void;
@@ -2056,9 +2055,6 @@ export interface ExtHostNotebookShape extends ExtHostNotebookDocumentsAndEditors
$releaseNotebookCellStatusBarItems(id: number): void;
$openNotebook(viewType: string, uri: UriComponents, backupId: string | undefined, untitledDocumentData: VSBuffer | undefined, token: CancellationToken): Promise<SerializableObjectWithBuffers<NotebookDataDto>>;
- $saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise<boolean>;
- $saveNotebookAs(viewType: string, uri: UriComponents, target: UriComponents, token: CancellationToken): Promise<boolean>;
- $backupNotebook(viewType: string, uri: UriComponents, cancellation: CancellationToken): Promise<string>;
$dataToNotebook(handle: number, data: VSBuffer, token: CancellationToken): Promise<SerializableObjectWithBuffers<NotebookDataDto>>;
$notebookToData(handle: number, data: SerializableObjectWithBuffers<NotebookDataDto>, token: CancellationToken): Promise<VSBuffer>;
diff --git a/src/vs/workbench/api/common/extHostBulkEdits.ts b/src/vs/workbench/api/common/extHostBulkEdits.ts
index 144b992a360..e60b2ba1167 100644
--- a/src/vs/workbench/api/common/extHostBulkEdits.ts
+++ b/src/vs/workbench/api/common/extHostBulkEdits.ts
@@ -29,13 +29,12 @@ export class ExtHostBulkEdits {
}
applyWorkspaceEdit(edit: vscode.WorkspaceEdit, extension: IExtensionDescription, isRefactoring?: boolean): Promise<boolean> {
- const allowSnippetTextEdit = isProposedApiEnabled(extension, 'snippetWorkspaceEdit');
const allowIsRefactoring = isProposedApiEnabled(extension, 'workspaceEditIsRefactoring');
if (isRefactoring && !allowIsRefactoring) {
console.warn(`Extension '${extension.identifier.value}' uses a proposed API 'workspaceEditIsRefactoring' which is NOT enabled for it`);
isRefactoring = undefined;
}
- const dto = WorkspaceEdit.from(edit, this._versionInformationProvider, allowSnippetTextEdit);
+ const dto = WorkspaceEdit.from(edit, this._versionInformationProvider);
return this._proxy.$tryApplyWorkspaceEdit(dto, undefined, isRefactoring);
}
}
diff --git a/src/vs/workbench/api/common/extHostFileSystemEventService.ts b/src/vs/workbench/api/common/extHostFileSystemEventService.ts
index 79aa5cd22f4..05b6fb85b9b 100644
--- a/src/vs/workbench/api/common/extHostFileSystemEventService.ts
+++ b/src/vs/workbench/api/common/extHostFileSystemEventService.ts
@@ -16,7 +16,6 @@ import { FileOperation } from 'vs/platform/files/common/files';
import { CancellationToken } from 'vs/base/common/cancellation';
import { ILogService } from 'vs/platform/log/common/log';
import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
-import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
class FileSystemWatcher implements vscode.FileSystemWatcher {
@@ -250,11 +249,11 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ
// concat all WorkspaceEdits collected via waitUntil-call and send them over to the renderer
const dto: IWorkspaceEditDto = { edits: [] };
- for (const [extension, edit] of edits) {
+ for (const [, edit] of edits) {
const { edits } = typeConverter.WorkspaceEdit.from(edit, {
getTextDocumentVersion: uri => this._extHostDocumentsAndEditors.getDocument(uri)?.version,
getNotebookDocumentVersion: () => undefined,
- }, isProposedApiEnabled(extension, 'snippetWorkspaceEdit'));
+ });
dto.edits = dto.edits.concat(edits);
}
return { edit: dto, extensionNames: Array.from(extensionNames) };
diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts
index a2ee6cb6c45..49384cee4af 100644
--- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts
+++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts
@@ -446,7 +446,7 @@ class CodeActionAdapter {
title: candidate.title,
command: candidate.command && this._commands.toInternal(candidate.command, disposables),
diagnostics: candidate.diagnostics && candidate.diagnostics.map(typeConvert.Diagnostic.from),
- edit: candidate.edit && typeConvert.WorkspaceEdit.from(candidate.edit, undefined, isProposedApiEnabled(this._extension, 'snippetWorkspaceEdit')),
+ edit: candidate.edit && typeConvert.WorkspaceEdit.from(candidate.edit, undefined),
kind: candidate.kind && candidate.kind.value,
isPreferred: candidate.isPreferred,
disabled: candidate.disabled?.reason
@@ -467,7 +467,7 @@ class CodeActionAdapter {
}
const resolvedItem = (await this._provider.resolveCodeAction(item, token)) ?? item;
return resolvedItem?.edit
- ? typeConvert.WorkspaceEdit.from(resolvedItem.edit, undefined, isProposedApiEnabled(this._extension, 'snippetWorkspaceEdit'))
+ ? typeConvert.WorkspaceEdit.from(resolvedItem.edit, undefined)
: undefined;
}
@@ -522,7 +522,7 @@ class DocumentPasteEditProvider {
return {
insertText: typeof edit.insertText === 'string' ? edit.insertText : { snippet: edit.insertText.value },
- additionalEdit: edit.additionalEdit ? typeConvert.WorkspaceEdit.from(edit.additionalEdit, undefined, true) : undefined,
+ additionalEdit: edit.additionalEdit ? typeConvert.WorkspaceEdit.from(edit.additionalEdit, undefined) : undefined,
};
}
}
@@ -1804,7 +1804,7 @@ class DocumentOnDropEditAdapter {
}
return {
insertText: typeof edit.insertText === 'string' ? edit.insertText : { snippet: edit.insertText.value },
- additionalEdit: edit.additionalEdit ? typeConvert.WorkspaceEdit.from(edit.additionalEdit, undefined, true) : undefined,
+ additionalEdit: edit.additionalEdit ? typeConvert.WorkspaceEdit.from(edit.additionalEdit, undefined) : undefined,
};
}
}
diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts
index 6a1df44dc0e..4a3c51fa79b 100644
--- a/src/vs/workbench/api/common/extHostNotebook.ts
+++ b/src/vs/workbench/api/common/extHostNotebook.ts
@@ -7,7 +7,6 @@ import { VSBuffer } from 'vs/base/common/buffer';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Emitter, Event } from 'vs/base/common/event';
import { IRelativePattern } from 'vs/base/common/glob';
-import { hash } from 'vs/base/common/hash';
import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { ResourceMap } from 'vs/base/common/map';
import { MarshalledId } from 'vs/base/common/marshallingIds';
@@ -20,7 +19,6 @@ import { ExtHostNotebookShape, IMainContext, IModelAddedData, INotebookCellStatu
import { ApiCommand, ApiCommandArgument, ApiCommandResult, CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
-import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths';
import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters';
import * as extHostTypes from 'vs/workbench/api/common/extHostTypes';
import { INotebookExclusiveDocumentFilter, INotebookContributionData } from 'vs/workbench/contrib/notebook/common/notebookCommon';
@@ -75,7 +73,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
commands: ExtHostCommands,
private _textDocumentsAndEditors: ExtHostDocumentsAndEditors,
private _textDocuments: ExtHostDocuments,
- private readonly _extensionStoragePaths: IExtensionStoragePaths,
) {
this._notebookProxy = mainContext.getProxy(MainContext.MainThreadNotebook);
this._notebookDocumentsProxy = mainContext.getProxy(MainContext.MainThreadNotebookDocuments);
@@ -157,14 +154,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
this._notebookContentProviders.set(viewType, { extension, provider });
- let listener: IDisposable | undefined;
- if (provider.onDidChangeNotebookContentOptions) {
- listener = provider.onDidChangeNotebookContentOptions(() => {
- const internalOptions = typeConverters.NotebookDocumentContentOptions.from(provider.options);
- this._notebookProxy.$updateNotebookProviderOptions(viewType, internalOptions);
- });
- }
-
this._notebookProxy.$registerNotebookProvider(
{ id: extension.identifier, location: extension.extensionLocation },
viewType,
@@ -173,7 +162,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
);
return new extHostTypes.Disposable(() => {
- listener?.dispose();
this._notebookContentProviders.delete(viewType);
this._notebookProxy.$unregisterNotebookProvider(viewType);
});
@@ -356,36 +344,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
});
}
- async $saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise<boolean> {
- const document = this.getNotebookDocument(URI.revive(uri));
- const { provider } = this._getProviderData(viewType);
- await provider.saveNotebook(document.apiNotebook, token);
- return true;
- }
-
- async $saveNotebookAs(viewType: string, uri: UriComponents, target: UriComponents, token: CancellationToken): Promise<boolean> {
- const document = this.getNotebookDocument(URI.revive(uri));
- const { provider } = this._getProviderData(viewType);
- await provider.saveNotebookAs(URI.revive(target), document.apiNotebook, token);
- return true;
- }
-
- private _backupIdPool: number = 0;
-
- async $backupNotebook(viewType: string, uri: UriComponents, cancellation: CancellationToken): Promise<string> {
- const document = this.getNotebookDocument(URI.revive(uri));
- const provider = this._getProviderData(viewType);
-
- const storagePath = this._extensionStoragePaths.workspaceValue(provider.extension) ?? this._extensionStoragePaths.globalValue(provider.extension);
- const fileName = String(hash([document.uri.toString(), this._backupIdPool++]));
- const backupUri = URI.joinPath(storagePath, fileName);
-
- const backup = await provider.provider.backupNotebook(document.apiNotebook, { destination: backupUri }, cancellation);
- document.updateBackup(backup);
- return backup.id;
- }
-
-
private _createExtHostEditor(document: ExtHostNotebookDocument, editorId: string, data: INotebookEditorAddData) {
if (this._editors.has(editorId)) {
diff --git a/src/vs/workbench/api/common/extHostNotebookDocument.ts b/src/vs/workbench/api/common/extHostNotebookDocument.ts
index 240ae6eede7..c3ebc53b3d6 100644
--- a/src/vs/workbench/api/common/extHostNotebookDocument.ts
+++ b/src/vs/workbench/api/common/extHostNotebookDocument.ts
@@ -141,7 +141,6 @@ export class ExtHostNotebookDocument {
private _metadata: Record<string, any>;
private _versionId: number = 0;
private _isDirty: boolean = false;
- private _backup?: vscode.NotebookDocumentBackup;
private _disposed: boolean = false;
constructor(
@@ -190,16 +189,6 @@ export class ExtHostNotebookDocument {
return this._notebook;
}
- updateBackup(backup: vscode.NotebookDocumentBackup): void {
- this._backup?.delete();
- this._backup = backup;
- }
-
- disposeBackup(): void {
- this._backup?.delete();
- this._backup = undefined;
- }
-
acceptDocumentPropertiesChanged(data: extHostProtocol.INotebookDocumentPropertiesChangeData) {
if (data.metadata) {
this._metadata = Object.freeze({ ...this._metadata, ...data.metadata });
diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts
index 0f5ebe088fb..22dd7d190ad 100644
--- a/src/vs/workbench/api/common/extHostTerminalService.ts
+++ b/src/vs/workbench/api/common/extHostTerminalService.ts
@@ -23,6 +23,8 @@ import { TerminalDataBufferer } from 'vs/platform/terminal/common/terminalDataBu
import { ThemeColor } from 'vs/platform/theme/common/themeService';
import { withNullAsUndefined } from 'vs/base/common/types';
import { Promises } from 'vs/base/common/async';
+import { EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGroupColumn';
+import { ViewColumn } from 'vs/workbench/api/common/extHostTypeConverters';
export interface IExtHostTerminalService extends ExtHostTerminalServiceShape, IDisposable {
@@ -177,14 +179,14 @@ export class ExtHostTerminal {
return this._id;
}
- private _serializeParentTerminal(location?: TerminalLocation | vscode.TerminalEditorLocationOptions | vscode.TerminalSplitLocationOptions, parentTerminal?: ExtHostTerminalIdentifier): TerminalLocation | vscode.TerminalEditorLocationOptions | { parentTerminal: ExtHostTerminalIdentifier } | undefined {
+ private _serializeParentTerminal(location?: TerminalLocation | vscode.TerminalEditorLocationOptions | vscode.TerminalSplitLocationOptions, parentTerminal?: ExtHostTerminalIdentifier): TerminalLocation | { viewColumn: EditorGroupColumn; preserveFocus?: boolean } | { parentTerminal: ExtHostTerminalIdentifier } | undefined {
if (typeof location === 'object') {
if ('parentTerminal' in location && location.parentTerminal && parentTerminal) {
return { parentTerminal };
}
if ('viewColumn' in location) {
- return { viewColumn: location.viewColumn, preserveFocus: location.preserveFocus };
+ return { viewColumn: ViewColumn.from(location.viewColumn), preserveFocus: location.preserveFocus };
}
return undefined;
diff --git a/src/vs/workbench/api/common/extHostTreeViews.ts b/src/vs/workbench/api/common/extHostTreeViews.ts
index 9a74d786aae..8a560d56add 100644
--- a/src/vs/workbench/api/common/extHostTreeViews.ts
+++ b/src/vs/workbench/api/common/extHostTreeViews.ts
@@ -11,7 +11,7 @@ import { URI } from 'vs/base/common/uri';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { CheckboxUpdate, DataTransferDTO, ExtHostTreeViewsShape, MainThreadTreeViewsShape } from './extHost.protocol';
-import { ITreeItem, TreeViewItemHandleArg, ITreeItemLabel, IRevealOptions, TreeCommand, TreeViewPaneHandleArg } from 'vs/workbench/common/views';
+import { ITreeItem, TreeViewItemHandleArg, ITreeItemLabel, IRevealOptions, TreeCommand, TreeViewPaneHandleArg, ITreeItemCheckboxState } from 'vs/workbench/common/views';
import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/common/extHostCommands';
import { asPromise } from 'vs/base/common/async';
import { TreeItemCollapsibleState, TreeItemCheckboxState, ThemeIcon, MarkdownString as MarkdownStringType, TreeItem } from 'vs/workbench/api/common/extHostTypes';
@@ -91,8 +91,8 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape {
const dragMimeTypes = options.dragAndDropController?.dragMimeTypes ?? [];
const hasHandleDrag = !!options.dragAndDropController?.handleDrag;
const hasHandleDrop = !!options.dragAndDropController?.handleDrop;
- const registerPromise = this._proxy.$registerTreeViewDataProvider(viewId, { showCollapseAll: !!options.showCollapseAll, canSelectMany: !!options.canSelectMany, dropMimeTypes, dragMimeTypes, hasHandleDrag, hasHandleDrop });
const treeView = this.createExtHostTreeView(viewId, options, extension);
+ const registerPromise = this._proxy.$registerTreeViewDataProvider(viewId, { showCollapseAll: !!options.showCollapseAll, canSelectMany: !!options.canSelectMany, dropMimeTypes, dragMimeTypes, hasHandleDrag, hasHandleDrop });
return {
get onDidCollapseElement() { return treeView.onDidCollapseElement; },
get onDidExpandElement() { return treeView.onDidExpandElement; },
@@ -750,9 +750,20 @@ class ExtHostTreeView<T> extends Disposable {
return command ? { ...this.commands.toInternal(command, disposable), originalId: command.command } : undefined;
}
- private getCheckbox(extensionTreeItem: vscode.TreeItem2): boolean | undefined {
- return (extensionTreeItem.checkboxState !== undefined) ?
- extensionTreeItem.checkboxState === TreeItemCheckboxState.Checked : undefined;
+ private getCheckbox(extensionTreeItem: vscode.TreeItem2): ITreeItemCheckboxState | undefined {
+ if (!extensionTreeItem.checkboxState) {
+ return undefined;
+ }
+ let checkboxState: TreeItemCheckboxState;
+ let tooltip: string | undefined = undefined;
+ if (typeof extensionTreeItem.checkboxState === 'number') {
+ checkboxState = extensionTreeItem.checkboxState;
+ }
+ else {
+ checkboxState = extensionTreeItem.checkboxState.state;
+ tooltip = extensionTreeItem.checkboxState.tooltip;
+ }
+ return { isChecked: checkboxState === TreeItemCheckboxState.Checked, tooltip };
}
private validateTreeItem(extensionTreeItem: vscode.TreeItem) {
@@ -780,7 +791,7 @@ class ExtHostTreeView<T> extends Disposable {
themeIcon: this.getThemeIcon(extensionTreeItem),
collapsibleState: isUndefinedOrNull(extensionTreeItem.collapsibleState) ? TreeItemCollapsibleState.None : extensionTreeItem.collapsibleState,
accessibilityInformation: extensionTreeItem.accessibilityInformation,
- checkboxChecked: this.getCheckbox(extensionTreeItem)
+ checkbox: this.getCheckbox(extensionTreeItem),
};
return {
diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts
index aba6d2d85a2..3f6ffad9546 100644
--- a/src/vs/workbench/api/common/extHostTypeConverters.ts
+++ b/src/vs/workbench/api/common/extHostTypeConverters.ts
@@ -569,7 +569,7 @@ export namespace WorkspaceEdit {
getNotebookDocumentVersion(uri: URI): number | undefined;
}
- export function from(value: vscode.WorkspaceEdit, versionInfo?: IVersionInformationProvider, allowSnippetTextEdit?: boolean): extHostProtocol.IWorkspaceEditDto {
+ export function from(value: vscode.WorkspaceEdit, versionInfo?: IVersionInformationProvider): extHostProtocol.IWorkspaceEditDto {
const result: extHostProtocol.IWorkspaceEditDto = {
edits: []
};
@@ -605,11 +605,6 @@ export namespace WorkspaceEdit {
metadata: entry.metadata
});
} else if (entry._type === types.FileEditType.Snippet) {
- // snippet text edits
- if (!allowSnippetTextEdit) {
- console.warn(`DROPPING snippet text edit because proposal IS NOT ENABLED`, entry);
- continue;
- }
result.edits.push(<languages.IWorkspaceTextEdit>{
resource: entry.uri,
textEdit: {
diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts
index d7f804b0aff..926fde45ea1 100644
--- a/src/vs/workbench/api/common/extHostTypes.ts
+++ b/src/vs/workbench/api/common/extHostTypes.ts
@@ -10,14 +10,14 @@ import { MarkdownString as BaseMarkdownString } from 'vs/base/common/htmlContent
import { ResourceMap } from 'vs/base/common/map';
import { Mimes, normalizeMimeType } from 'vs/base/common/mime';
import { nextCharLength } from 'vs/base/common/strings';
-import { isString, isStringArray } from 'vs/base/common/types';
+import { isNumber, isObject, isString, isStringArray } from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { FileSystemProviderErrorCode, markAsFileSystemProviderError } from 'vs/platform/files/common/files';
import { RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IRelativePatternDto } from 'vs/workbench/api/common/extHost.protocol';
-import { CellEditType, ICellPartialMetadataEdit, IDocumentMetadataEdit } from 'vs/workbench/contrib/notebook/common/notebookCommon';
+import { CellEditType, ICellPartialMetadataEdit, IDocumentMetadataEdit, isTextStreamMime } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
import type * as vscode from 'vscode';
@@ -1321,6 +1321,7 @@ export class CodeActionKind {
public static Refactor: CodeActionKind;
public static RefactorExtract: CodeActionKind;
public static RefactorInline: CodeActionKind;
+ public static RefactorMove: CodeActionKind;
public static RefactorRewrite: CodeActionKind;
public static Source: CodeActionKind;
public static SourceOrganizeImports: CodeActionKind;
@@ -1347,6 +1348,7 @@ CodeActionKind.QuickFix = CodeActionKind.Empty.append('quickfix');
CodeActionKind.Refactor = CodeActionKind.Empty.append('refactor');
CodeActionKind.RefactorExtract = CodeActionKind.Refactor.append('extract');
CodeActionKind.RefactorInline = CodeActionKind.Refactor.append('inline');
+CodeActionKind.RefactorMove = CodeActionKind.Refactor.append('move');
CodeActionKind.RefactorRewrite = CodeActionKind.Refactor.append('rewrite');
CodeActionKind.Source = CodeActionKind.Empty.append('source');
CodeActionKind.SourceOrganizeImports = CodeActionKind.Source.append('organizeImports');
@@ -2519,7 +2521,10 @@ export class TreeItem {
}
if (treeItemThing.checkboxState !== undefined) {
checkProposedApiEnabled(extension, 'treeItemCheckbox');
- if (treeItemThing.checkboxState !== TreeItemCheckboxState.Checked && treeItemThing.checkboxState !== TreeItemCheckboxState.Unchecked) {
+ const checkbox = isNumber(treeItemThing.checkboxState) ? treeItemThing.checkboxState :
+ isObject(treeItemThing.checkboxState) && isNumber(treeItemThing.checkboxState.state) ? treeItemThing.checkboxState.state : undefined;
+ const tooltip = !isNumber(treeItemThing.checkboxState) && isObject(treeItemThing.checkboxState) ? treeItemThing.checkboxState.tooltip : undefined;
+ if (checkbox === undefined || (checkbox !== TreeItemCheckboxState.Checked && checkbox !== TreeItemCheckboxState.Unchecked) || (tooltip !== undefined && !isString(tooltip))) {
console.log('INVALID tree item, invalid checkboxState', treeItemThing.checkboxState);
return false;
}
@@ -3515,7 +3520,8 @@ export class NotebookCellOutput {
for (let i = 0; i < items.length; i++) {
const item = items[i];
const normalMime = normalizeMimeType(item.mime);
- if (!seen.has(normalMime)) {
+ // We can have multiple text stream mime types in the same output.
+ if (!seen.has(normalMime) || isTextStreamMime(normalMime)) {
seen.add(normalMime);
continue;
}
diff --git a/src/vs/workbench/api/test/browser/extHostNotebook.test.ts b/src/vs/workbench/api/test/browser/extHostNotebook.test.ts
index c61334ba316..09e785c307b 100644
--- a/src/vs/workbench/api/test/browser/extHostNotebook.test.ts
+++ b/src/vs/workbench/api/test/browser/extHostNotebook.test.ts
@@ -19,8 +19,6 @@ import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
import { isEqual } from 'vs/base/common/resources';
-import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths';
-import { generateUuid } from 'vs/base/common/uuid';
import { Event } from 'vs/base/common/event';
import { ExtHostNotebookDocuments } from 'vs/workbench/api/common/extHostNotebookDocuments';
import { SerializableObjectWithBuffers } from 'vs/workbench/services/extensions/common/proxyIdentifier';
@@ -54,12 +52,7 @@ suite('NotebookCell#Document', function () {
});
extHostDocumentsAndEditors = new ExtHostDocumentsAndEditors(rpcProtocol, new NullLogService());
extHostDocuments = new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors);
- const extHostStoragePaths = new class extends mock<IExtensionStoragePaths>() {
- override workspaceValue() {
- return URI.from({ scheme: 'test', path: generateUuid() });
- }
- };
- extHostNotebooks = new ExtHostNotebookController(rpcProtocol, new ExtHostCommands(rpcProtocol, new NullLogService()), extHostDocumentsAndEditors, extHostDocuments, extHostStoragePaths);
+ extHostNotebooks = new ExtHostNotebookController(rpcProtocol, new ExtHostCommands(rpcProtocol, new NullLogService()), extHostDocumentsAndEditors, extHostDocuments);
extHostNotebookDocuments = new ExtHostNotebookDocuments(extHostNotebooks);
const reg = extHostNotebooks.registerNotebookContentProvider(nullExtensionDescription, 'test', new class extends mock<vscode.NotebookContentProvider>() {
diff --git a/src/vs/workbench/api/test/browser/extHostNotebookKernel.test.ts b/src/vs/workbench/api/test/browser/extHostNotebookKernel.test.ts
index 54eb5519c6f..d159691ba03 100644
--- a/src/vs/workbench/api/test/browser/extHostNotebookKernel.test.ts
+++ b/src/vs/workbench/api/test/browser/extHostNotebookKernel.test.ts
@@ -7,7 +7,6 @@ import * as assert from 'assert';
import { Barrier } from 'vs/base/common/async';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { URI, UriComponents } from 'vs/base/common/uri';
-import { generateUuid } from 'vs/base/common/uuid';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { NullLogService } from 'vs/platform/log/common/log';
import { ICellExecuteUpdateDto, ICellExecutionCompleteDto, INotebookKernelDto2, MainContext, MainThreadCommandsShape, MainThreadNotebookDocumentsShape, MainThreadNotebookKernelsShape, MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol';
@@ -19,7 +18,6 @@ import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebo
import { ExtHostNotebookDocument } from 'vs/workbench/api/common/extHostNotebookDocument';
import { ExtHostNotebookDocuments } from 'vs/workbench/api/common/extHostNotebookDocuments';
import { ExtHostNotebookKernels } from 'vs/workbench/api/common/extHostNotebookKernels';
-import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths';
import { NotebookCellOutput, NotebookCellOutputItem } from 'vs/workbench/api/common/extHostTypes';
import { CellKind, CellUri, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellExecutionUpdateType } from 'vs/workbench/contrib/notebook/common/notebookExecutionService';
@@ -90,13 +88,8 @@ suite('NotebookKernel', function () {
});
extHostDocumentsAndEditors = new ExtHostDocumentsAndEditors(rpcProtocol, new NullLogService());
extHostDocuments = new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors);
- const extHostStoragePaths = new class extends mock<IExtensionStoragePaths>() {
- override workspaceValue() {
- return URI.from({ scheme: 'test', path: generateUuid() });
- }
- };
extHostCommands = new ExtHostCommands(rpcProtocol, new NullLogService());
- extHostNotebooks = new ExtHostNotebookController(rpcProtocol, extHostCommands, extHostDocumentsAndEditors, extHostDocuments, extHostStoragePaths);
+ extHostNotebooks = new ExtHostNotebookController(rpcProtocol, extHostCommands, extHostDocumentsAndEditors, extHostDocuments);
extHostNotebookDocuments = new ExtHostNotebookDocuments(extHostNotebooks);
diff --git a/src/vs/workbench/browser/checkbox.ts b/src/vs/workbench/browser/checkbox.ts
index dcea67bf6b1..3b11fe0f2c6 100644
--- a/src/vs/workbench/browser/checkbox.ts
+++ b/src/vs/workbench/browser/checkbox.ts
@@ -11,7 +11,7 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { localize } from 'vs/nls';
import { attachToggleStyler } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
-import { ITreeItem } from 'vs/workbench/common/views';
+import { ITreeItem, ITreeItemCheckboxState } from 'vs/workbench/common/views';
export class CheckboxStateHandler extends Disposable {
private readonly _onDidChangeCheckboxState = this._register(new Emitter<ITreeItem[]>());
@@ -38,23 +38,23 @@ export class TreeItemCheckbox extends Disposable {
}
public render(node: ITreeItem) {
- if (node.checkboxChecked !== undefined) {
+ if (node.checkbox) {
if (!this.toggle) {
this.createCheckbox(node);
}
else {
- this.toggle.checked = node.checkboxChecked;
+ this.toggle.checked = node.checkbox.isChecked;
this.toggle.setIcon(this.toggle.checked ? Codicon.check : undefined);
}
}
}
private createCheckbox(node: ITreeItem) {
- if (node.checkboxChecked !== undefined) {
+ if (node.checkbox) {
this.toggle = new Toggle({
- isChecked: node.checkboxChecked,
- title: localize('check', "Check"),
- icon: node.checkboxChecked ? Codicon.check : undefined
+ isChecked: node.checkbox.isChecked,
+ title: this.createCheckboxTitle(node.checkbox),
+ icon: node.checkbox.isChecked ? Codicon.check : undefined
});
this.toggle.domNode.classList.add(TreeItemCheckbox.checkboxClass);
@@ -75,14 +75,19 @@ export class TreeItemCheckbox extends Disposable {
}
private setCheckbox(node: ITreeItem) {
- if (this.toggle && node.checkboxChecked !== undefined) {
- node.checkboxChecked = this.toggle.checked;
+ if (this.toggle && node.checkbox) {
+ node.checkbox.isChecked = this.toggle.checked;
this.toggle.setIcon(this.toggle.checked ? Codicon.check : undefined);
- this.toggle.checked = this.toggle.checked;
+ this.toggle.setTitle(this.createCheckboxTitle(node.checkbox));
this.checkboxStateHandler.setCheckboxState(node);
}
}
+ private createCheckboxTitle(checkbox: ITreeItemCheckboxState) {
+ return checkbox.tooltip ? checkbox.tooltip :
+ checkbox.isChecked ? localize('checked', 'Checked') : localize('unchecked', 'Unchecked');
+ }
+
private removeCheckbox() {
const children = this.checkboxContainer.children;
for (const child of children) {
diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts
index 9304edeb49a..20edefb9528 100644
--- a/src/vs/workbench/browser/parts/editor/editorActions.ts
+++ b/src/vs/workbench/browser/parts/editor/editorActions.ts
@@ -31,6 +31,7 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
+import { ILogService } from 'vs/platform/log/common/log';
export class ExecuteCommandAction extends Action {
@@ -483,7 +484,8 @@ export class RevertAndCloseEditorAction extends Action {
constructor(
id: string,
label: string,
- @IEditorService private readonly editorService: IEditorService
+ @IEditorService private readonly editorService: IEditorService,
+ @ILogService private readonly logService: ILogService
) {
super(id, label);
}
@@ -498,10 +500,13 @@ export class RevertAndCloseEditorAction extends Action {
try {
await this.editorService.revert({ editor, groupId: group.id });
} catch (error) {
+ this.logService.error(error);
+
// if that fails, since we are about to close the editor, we accept that
// the editor cannot be reverted and instead do a soft revert that just
// enables us to close the editor. With this, a user can always close a
// dirty editor even when reverting fails.
+
await this.editorService.revert({ editor, groupId: group.id }, { soft: true });
}
diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts
index 02c836c98c8..3049500d7f8 100644
--- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts
+++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts
@@ -52,6 +52,7 @@ import { withNullAsUndefined } from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
import { isLinux, isNative, isWindows } from 'vs/base/common/platform';
+import { ILogService } from 'vs/platform/log/common/log';
export class EditorGroupView extends Themable implements IEditorGroupView {
@@ -145,7 +146,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
@IFileDialogService private readonly fileDialogService: IFileDialogService,
@IEditorService private readonly editorService: EditorServiceImpl,
@IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService,
- @IUriIdentityService private readonly uriIdentityService: IUriIdentityService
+ @IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
+ @ILogService private readonly logService: ILogService
) {
super(themeService);
@@ -1600,10 +1602,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
return editor.isDirty(); // veto if still dirty
} catch (error) {
+ this.logService.error(error);
+
// if that fails, since we are about to close the editor, we accept that
// the editor cannot be reverted and instead do a soft revert that just
// enables us to close the editor. With this, a user can always close a
// dirty editor even when reverting fails.
+
await editor.revert(this.id, { soft: true });
return editor.isDirty(); // veto if still dirty
diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts
index 9e3f74f3bd9..a1d39f06c68 100644
--- a/src/vs/workbench/browser/parts/editor/editorStatus.ts
+++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts
@@ -71,8 +71,8 @@ class SideBySideEditorLanguageSupport implements ILanguageSupport {
constructor(private primary: ILanguageSupport, private secondary: ILanguageSupport) { }
- setLanguageId(languageId: string): void {
- [this.primary, this.secondary].forEach(editor => editor.setLanguageId(languageId));
+ setLanguageId(languageId: string, source?: string): void {
+ [this.primary, this.secondary].forEach(editor => editor.setLanguageId(languageId, source));
}
}
@@ -1237,7 +1237,7 @@ export class ChangeLanguageAction extends Action {
// Change language
if (typeof languageSelection !== 'undefined') {
- languageSupport.setLanguageId(languageSelection.languageId);
+ languageSupport.setLanguageId(languageSelection.languageId, ChangeLanguageAction.ID);
if (resource?.scheme === Schemas.untitled) {
type SetUntitledDocumentLanguageEvent = { to: string; from: string; modelPreference: string };
diff --git a/src/vs/workbench/browser/parts/views/media/views.css b/src/vs/workbench/browser/parts/views/media/views.css
index 23cd87e04ef..9ed1f853826 100644
--- a/src/vs/workbench/browser/parts/views/media/views.css
+++ b/src/vs/workbench/browser/parts/views/media/views.css
@@ -151,6 +151,7 @@
display: block;
}
+.timeline-tree-view .monaco-list .monaco-list-row .custom-view-tree-node-item > .custom-view-tree-node-item-icon,
.customview-tree .monaco-list .monaco-list-row .custom-view-tree-node-item > .custom-view-tree-node-item-resourceLabel > .custom-view-tree-node-item-icon {
background-size: 16px;
background-position: left center;
@@ -184,6 +185,7 @@
.customview-tree .monaco-list .monaco-list-row .custom-view-tree-node-item .custom-view-tree-node-item-resourceLabel::after {
padding-right: 0px;
+ margin-right: 4px;
}
.customview-tree .monaco-list .monaco-list-row .custom-view-tree-node-item .actions {
diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts
index 8ede647b896..30b8f3772cc 100644
--- a/src/vs/workbench/browser/parts/views/treeView.ts
+++ b/src/vs/workbench/browser/parts/views/treeView.ts
@@ -650,7 +650,7 @@ abstract class AbstractTreeView extends Disposable implements ITreeView {
}
},
expandOnlyOnTwistieClick: (e: ITreeItem) => {
- return !!e.command || e.checkboxChecked !== undefined || this.configurationService.getValue<'singleClick' | 'doubleClick'>('workbench.tree.expandMode') === 'doubleClick';
+ return !!e.command || !!e.checkbox || this.configurationService.getValue<'singleClick' | 'doubleClick'>('workbench.tree.expandMode') === 'doubleClick';
},
collapseByDefault: (e: ITreeItem): boolean => {
return e.collapsibleState !== TreeItemCollapsibleState.Expanded;
@@ -858,11 +858,17 @@ abstract class AbstractTreeView extends Disposable implements ITreeView {
async expand(itemOrItems: ITreeItem | ITreeItem[]): Promise<void> {
const tree = this.tree;
- if (tree) {
+ if (!tree) {
+ return;
+ }
+ try {
itemOrItems = Array.isArray(itemOrItems) ? itemOrItems : [itemOrItems];
await Promise.all(itemOrItems.map(element => {
return tree.expand(element, false);
}));
+ } catch (e) {
+ // The extension could have changed the tree during the reveal.
+ // Because of that, we ignore errors.
}
}
@@ -1228,7 +1234,7 @@ class TreeRenderer extends Disposable implements ITreeRenderer<ITreeItem, FuzzyS
}
private renderCheckbox(node: ITreeItem, templateData: ITreeExplorerTemplateData, disposableStore: DisposableStore) {
- if (node.checkboxChecked !== undefined) {
+ if (node.checkbox) {
// The first time we find a checkbox we want to rerender the visible tree to adapt the alignment
if (!this._hasCheckbox) {
this._hasCheckbox = true;
diff --git a/src/vs/workbench/common/editor/textEditorModel.ts b/src/vs/workbench/common/editor/textEditorModel.ts
index 7b05513ce94..c182aa14e4a 100644
--- a/src/vs/workbench/common/editor/textEditorModel.ts
+++ b/src/vs/workbench/common/editor/textEditorModel.ts
@@ -13,7 +13,7 @@ import { IModelService } from 'vs/editor/common/services/model';
import { MutableDisposable } from 'vs/base/common/lifecycle';
import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry';
import { withUndefinedAsNull } from 'vs/base/common/types';
-import { ILanguageDetectionService } from 'vs/workbench/services/languageDetection/common/languageDetectionWorkerService';
+import { ILanguageDetectionService, LanguageDetectionLanguageEventSource } from 'vs/workbench/services/languageDetection/common/languageDetectionWorkerService';
import { ThrottledDelayer } from 'vs/base/common/async';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { localize } from 'vs/nls';
@@ -78,15 +78,15 @@ export class BaseTextEditorModel extends EditorModel implements ITextEditorModel
private _hasLanguageSetExplicitly: boolean = false;
get hasLanguageSetExplicitly(): boolean { return this._hasLanguageSetExplicitly; }
- setLanguageId(languageId: string): void {
+ setLanguageId(languageId: string, source?: string): void {
// Remember that an explicit language was set
this._hasLanguageSetExplicitly = true;
- this.setLanguageIdInternal(languageId);
+ this.setLanguageIdInternal(languageId, source);
}
- private setLanguageIdInternal(languageId: string): void {
+ private setLanguageIdInternal(languageId: string, source?: string): void {
if (!this.isResolved()) {
return;
}
@@ -95,7 +95,20 @@ export class BaseTextEditorModel extends EditorModel implements ITextEditorModel
return;
}
- this.modelService.setMode(this.textEditorModel, this.languageService.createById(languageId));
+ this.modelService.setMode(this.textEditorModel, this.languageService.createById(languageId), source);
+ }
+
+ protected installModelListeners(model: ITextModel): void {
+
+ // Setup listener for lower level language changes
+ const disposable = this._register(model.onDidChangeLanguage((e) => {
+ if (e.source === LanguageDetectionLanguageEventSource) {
+ return;
+ }
+
+ this._hasLanguageSetExplicitly = true;
+ disposable.dispose();
+ }));
}
getLanguageId(): string | undefined {
@@ -117,7 +130,7 @@ export class BaseTextEditorModel extends EditorModel implements ITextEditorModel
const lang = await this.languageDetectionService.detectLanguage(this.textEditorModelHandle);
if (lang && !this.isDisposed()) {
- this.setLanguageIdInternal(lang);
+ this.setLanguageIdInternal(lang, LanguageDetectionLanguageEventSource);
const languageName = this.languageService.getLanguageName(lang);
if (languageName) {
diff --git a/src/vs/workbench/common/editor/textResourceEditorInput.ts b/src/vs/workbench/common/editor/textResourceEditorInput.ts
index 7f30ec3e32c..b83ea8152a9 100644
--- a/src/vs/workbench/common/editor/textResourceEditorInput.ts
+++ b/src/vs/workbench/common/editor/textResourceEditorInput.ts
@@ -130,10 +130,10 @@ export class TextResourceEditorInput extends AbstractTextResourceEditorInput imp
}
}
- setLanguageId(languageId: string): void {
+ setLanguageId(languageId: string, source?: string): void {
this.setPreferredLanguageId(languageId);
- this.cachedModel?.setLanguageId(languageId);
+ this.cachedModel?.setLanguageId(languageId, source);
}
setPreferredLanguageId(languageId: string): void {
diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts
index acdb3fc1c9e..a1f9ace2e05 100644
--- a/src/vs/workbench/common/views.ts
+++ b/src/vs/workbench/common/views.ts
@@ -745,6 +745,11 @@ export interface ITreeItemLabel {
export type TreeCommand = Command & { originalId?: string };
+export interface ITreeItemCheckboxState {
+ isChecked: boolean;
+ tooltip?: string;
+}
+
export interface ITreeItem {
handle: string;
@@ -775,7 +780,7 @@ export interface ITreeItem {
accessibilityInformation?: IAccessibilityInformation;
- checkboxChecked?: boolean;
+ checkbox?: ITreeItemCheckboxState;
}
export class ResolvableTreeItem implements ITreeItem {
diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts
index c7efa028427..dfcdd381ef0 100644
--- a/src/vs/workbench/contrib/debug/browser/repl.ts
+++ b/src/vs/workbench/contrib/debug/browser/repl.ts
@@ -13,6 +13,7 @@ import { IAction } from 'vs/base/common/actions';
import { RunOnceScheduler } from 'vs/base/common/async';
import { CancellationToken } from 'vs/base/common/cancellation';
import { memoize } from 'vs/base/common/decorators';
+import { Emitter } from 'vs/base/common/event';
import { FuzzyScore } from 'vs/base/common/filters';
import { HistoryNavigator } from 'vs/base/common/history';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
@@ -62,7 +63,7 @@ import { debugConsoleClearAll, debugConsoleEvaluationPrompt } from 'vs/workbench
import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector';
import { ReplFilter, ReplFilterActionViewItem, ReplFilterState } from 'vs/workbench/contrib/debug/browser/replFilter';
import { ReplAccessibilityProvider, ReplDataSource, ReplDelegate, ReplEvaluationInputsRenderer, ReplEvaluationResultsRenderer, ReplGroupRenderer, ReplRawObjectsRenderer, ReplSimpleElementsRenderer, ReplVariablesRenderer } from 'vs/workbench/contrib/debug/browser/replViewer';
-import { CONTEXT_DEBUG_STATE, CONTEXT_IN_DEBUG_REPL, CONTEXT_MULTI_SESSION_REPL, DEBUG_SCHEME, getStateLabel, IDebugConfiguration, IDebugService, IDebugSession, IReplElement, REPL_VIEW_ID, State } from 'vs/workbench/contrib/debug/common/debug';
+import { CONTEXT_DEBUG_STATE, CONTEXT_IN_DEBUG_REPL, CONTEXT_MULTI_SESSION_REPL, DEBUG_SCHEME, getStateLabel, IDebugConfiguration, IDebugService, IDebugSession, IReplConfiguration, IReplElement, IReplOptions, REPL_VIEW_ID, State } from 'vs/workbench/contrib/debug/common/debug';
import { Variable } from 'vs/workbench/contrib/debug/common/debugModel';
import { ReplGroup } from 'vs/workbench/contrib/debug/common/replModel';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
@@ -91,6 +92,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
private history: HistoryNavigator<string>;
private tree!: WorkbenchAsyncDataTree<IDebugSession, IReplElement, FuzzyScore>;
+ private replOptions: ReplOptions;
private previousTreeScrollHeight: number = 0;
private replDelegate!: ReplDelegate;
private container!: HTMLElement;
@@ -141,6 +143,8 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
this.filterState = new ReplFilterState(this);
this.filter.filterQuery = this.filterState.filterText = this.storageService.get(FILTER_VALUE_STORAGE_KEY, StorageScope.WORKSPACE, '');
this.multiSessionRepl = CONTEXT_MULTI_SESSION_REPL.bindTo(contextKeyService);
+ this.replOptions = this._register(this.instantiationService.createInstance(ReplOptions, this.id, () => this.getBackgroundColor()));
+ this._register(this.replOptions.onDidChange(() => this.onDidStyleChange()));
codeEditorService.registerDecorationType('repl-decoration', DECORATION_KEY, {});
this.multiSessionRepl.set(this.isMultiSessionView);
@@ -191,8 +195,6 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
this.treeContainer.innerText = '';
dom.clearNode(this.treeContainer);
this.createReplTree();
- } else if (e.affectsConfiguration('debug.console.lineHeight') || e.affectsConfiguration('debug.console.fontSize') || e.affectsConfiguration('debug.console.fontFamily')) {
- this.onDidStyleChange();
}
if (e.affectsConfiguration('debug.console.acceptSuggestionOnEnter')) {
const config = this.configurationService.getValue<IDebugConfiguration>('debug');
@@ -202,16 +204,6 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
}
}));
- this._register(this.themeService.onDidColorThemeChange(e => {
- this.onDidStyleChange();
- }));
-
- this._register(this.viewDescriptorService.onDidChangeLocation(e => {
- if (e.views.some(v => v.id === this.id)) {
- this.onDidStyleChange();
- }
- }));
-
this._register(this.editorService.onDidActiveEditorChange(() => {
this.setMode();
}));
@@ -346,16 +338,10 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
private onDidStyleChange(): void {
if (this.styleElement) {
- const debugConsole = this.configurationService.getValue<IDebugConfiguration>('debug').console;
- const fontSize = debugConsole.fontSize;
- const fontFamily = debugConsole.fontFamily === 'default' ? 'var(--monaco-monospace-font)' : `${debugConsole.fontFamily}`;
- const lineHeight = debugConsole.lineHeight ? `${debugConsole.lineHeight}px` : '1.4em';
- const backgroundColor = this.themeService.getColorTheme().getColor(this.getBackgroundColor());
-
this.replInput.updateOptions({
- fontSize,
- lineHeight: debugConsole.lineHeight,
- fontFamily: debugConsole.fontFamily === 'default' ? EDITOR_FONT_DEFAULTS.fontFamily : debugConsole.fontFamily
+ fontSize: this.replOptions.replConfiguration.fontSize,
+ lineHeight: this.replOptions.replConfiguration.lineHeight,
+ fontFamily: this.replOptions.replConfiguration.fontFamily === 'default' ? EDITOR_FONT_DEFAULTS.fontFamily : this.replOptions.replConfiguration.fontFamily
});
const replInputLineHeight = this.replInput.getOption(EditorOption.lineHeight);
@@ -367,13 +353,14 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
}
.repl .repl-input-wrapper .monaco-editor .lines-content {
- background-color: ${backgroundColor};
+ background-color: ${this.replOptions.replConfiguration.backgroundColor};
}
`;
- this.container.style.setProperty(`--vscode-repl-font-family`, fontFamily);
- this.container.style.setProperty(`--vscode-repl-font-size`, `${fontSize}px`);
- this.container.style.setProperty(`--vscode-repl-font-size-for-twistie`, `${fontSize * 1.4 / 2 - 8}px`);
- this.container.style.setProperty(`--vscode-repl-line-height`, lineHeight);
+ const cssFontFamily = this.replOptions.replConfiguration.fontFamily === 'default' ? 'var(--monaco-monospace-font)' : this.replOptions.replConfiguration.fontFamily;
+ this.container.style.setProperty(`--vscode-repl-font-family`, cssFontFamily);
+ this.container.style.setProperty(`--vscode-repl-font-size`, `${this.replOptions.replConfiguration.fontSize}px`);
+ this.container.style.setProperty(`--vscode-repl-font-size-for-twistie`, `${this.replOptions.replConfiguration.fontSizeForTwistie}px`);
+ this.container.style.setProperty(`--vscode-repl-line-height`, this.replOptions.replConfiguration.cssLineHeight);
this.tree.rerender();
@@ -566,7 +553,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
}
private createReplTree(): void {
- this.replDelegate = new ReplDelegate(this.configurationService);
+ this.replDelegate = new ReplDelegate(this.configurationService, this.replOptions);
const wordWrap = this.configurationService.getValue<IDebugConfiguration>('debug').console.wordWrap;
this.treeContainer.classList.toggle('word-wrap', wordWrap);
const linkDetector = this.instantiationService.createInstance(LinkDetector);
@@ -601,7 +588,9 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
this._register(this.tree.onDidChangeContentHeight(() => {
if (this.tree.scrollHeight !== this.previousTreeScrollHeight) {
- const lastElementWasVisible = this.tree.scrollTop + this.tree.renderHeight >= this.previousTreeScrollHeight;
+ // Due to rounding, the scrollTop + renderHeight will not exactly match the scrollHeight.
+ // Consider the tree to be scrolled all the way down if it is within 2px of the bottom.
+ const lastElementWasVisible = this.tree.scrollTop + this.tree.renderHeight >= this.previousTreeScrollHeight - 2;
if (lastElementWasVisible) {
setTimeout(() => {
// Can't set scrollTop during this event listener, the list might overwrite the change
@@ -750,6 +739,54 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
}
}
+class ReplOptions extends Disposable implements IReplOptions {
+ private static readonly lineHeightEm = 1.4;
+
+ private readonly _onDidChange = this._register(new Emitter<void>());
+ readonly onDidChange = this._onDidChange.event;
+
+ private _replConfig!: IReplConfiguration;
+ public get replConfiguration(): IReplConfiguration {
+ return this._replConfig;
+ }
+
+ constructor(
+ viewId: string,
+ private readonly backgroundColorDelegate: () => string,
+ @IConfigurationService private readonly configurationService: IConfigurationService,
+ @IThemeService private readonly themeService: IThemeService,
+ @IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService
+ ) {
+ super();
+
+ this._register(this.themeService.onDidColorThemeChange(e => this.update()));
+ this._register(this.viewDescriptorService.onDidChangeLocation(e => {
+ if (e.views.some(v => v.id === viewId)) {
+ this.update();
+ }
+ }));
+ this._register(this.configurationService.onDidChangeConfiguration(e => {
+ if (e.affectsConfiguration('debug.console.lineHeight') || e.affectsConfiguration('debug.console.fontSize') || e.affectsConfiguration('debug.console.fontFamily')) {
+ this.update();
+ }
+ }));
+ this.update();
+ }
+
+ private update() {
+ const debugConsole = this.configurationService.getValue<IDebugConfiguration>('debug').console;
+ this._replConfig = {
+ fontSize: debugConsole.fontSize,
+ fontFamily: debugConsole.fontFamily,
+ lineHeight: debugConsole.lineHeight ? debugConsole.lineHeight : ReplOptions.lineHeightEm * debugConsole.fontSize,
+ cssLineHeight: debugConsole.lineHeight ? `${debugConsole.lineHeight}px` : `${ReplOptions.lineHeightEm}em`,
+ backgroundColor: this.themeService.getColorTheme().getColor(this.backgroundColorDelegate()),
+ fontSizeForTwistie: debugConsole.fontSize * ReplOptions.lineHeightEm / 2 - 8
+ };
+ this._onDidChange.fire();
+ }
+}
+
// Repl actions and commands
class AcceptReplInputAction extends EditorAction {
diff --git a/src/vs/workbench/contrib/debug/browser/replViewer.ts b/src/vs/workbench/contrib/debug/browser/replViewer.ts
index b714e1e1181..94b7ecd5b13 100644
--- a/src/vs/workbench/contrib/debug/browser/replViewer.ts
+++ b/src/vs/workbench/contrib/debug/browser/replViewer.ts
@@ -22,7 +22,7 @@ import { AbstractExpressionsRenderer, IExpressionTemplateData, IInputBoxOptions,
import { handleANSIOutput } from 'vs/workbench/contrib/debug/browser/debugANSIHandling';
import { debugConsoleEvaluationInput } from 'vs/workbench/contrib/debug/browser/debugIcons';
import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector';
-import { IDebugConfiguration, IDebugService, IDebugSession, IExpression, IExpressionContainer, IReplElement, IReplElementSource } from 'vs/workbench/contrib/debug/common/debug';
+import { IDebugConfiguration, IDebugService, IDebugSession, IExpression, IExpressionContainer, IReplElement, IReplElementSource, IReplOptions } from 'vs/workbench/contrib/debug/common/debug';
import { Variable } from 'vs/workbench/contrib/debug/common/debugModel';
import { RawObjectReplElement, ReplEvaluationInput, ReplEvaluationResult, ReplGroup, SimpleReplElement } from 'vs/workbench/contrib/debug/common/replModel';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
@@ -295,7 +295,10 @@ function isNestedVariable(element: IReplElement) {
export class ReplDelegate extends CachedListVirtualDelegate<IReplElement> {
- constructor(private configurationService: IConfigurationService) {
+ constructor(
+ private readonly configurationService: IConfigurationService,
+ private readonly replOptions: IReplOptions
+ ) {
super();
}
@@ -310,8 +313,7 @@ export class ReplDelegate extends CachedListVirtualDelegate<IReplElement> {
}
protected estimateHeight(element: IReplElement, ignoreValueLength = false): number {
- const config = this.configurationService.getValue<IDebugConfiguration>('debug');
- const rowHeight = Math.ceil(1.3 * config.console.fontSize);
+ const lineHeight = this.replOptions.replConfiguration.lineHeight;
const countNumberOfLines = (str: string) => Math.max(1, (str && str.match(/\r\n|\n/g) || []).length);
const hasValue = (e: any): e is { value: string } => typeof e.value === 'string';
@@ -321,10 +323,10 @@ export class ReplDelegate extends CachedListVirtualDelegate<IReplElement> {
const value = element.value;
const valueRows = countNumberOfLines(value) + (ignoreValueLength ? 0 : Math.floor(value.length / 70));
- return valueRows * rowHeight;
+ return valueRows * lineHeight;
}
- return rowHeight;
+ return lineHeight;
}
getTemplateId(element: IReplElement): string {
diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts
index ec6bb1652ae..dbd8289acf0 100644
--- a/src/vs/workbench/contrib/debug/common/debug.ts
+++ b/src/vs/workbench/contrib/debug/common/debug.ts
@@ -6,6 +6,7 @@
import { IAction } from 'vs/base/common/actions';
import { VSBuffer } from 'vs/base/common/buffer';
import { CancellationToken } from 'vs/base/common/cancellation';
+import { Color } from 'vs/base/common/color';
import { Event } from 'vs/base/common/event';
import { IJSONSchemaSnippet } from 'vs/base/common/jsonSchema';
import { IDisposable } from 'vs/base/common/lifecycle';
@@ -1142,3 +1143,16 @@ export interface IBreakpointEditorContribution extends editorCommon.IEditorContr
closeBreakpointWidget(): void;
getContextMenuActionsAtPosition(lineNumber: number, model: EditorIModel): IAction[];
}
+
+export interface IReplConfiguration {
+ readonly fontSize: number;
+ readonly fontFamily: string;
+ readonly lineHeight: number;
+ readonly cssLineHeight: string;
+ readonly backgroundColor: Color | undefined;
+ readonly fontSizeForTwistie: number;
+}
+
+export interface IReplOptions {
+ readonly replConfiguration: IReplConfiguration;
+}
diff --git a/src/vs/workbench/contrib/editSessions/browser/editSessionsStorageService.ts b/src/vs/workbench/contrib/editSessions/browser/editSessionsStorageService.ts
index e557f402a9a..9136ed15e08 100644
--- a/src/vs/workbench/contrib/editSessions/browser/editSessionsStorageService.ts
+++ b/src/vs/workbench/contrib/editSessions/browser/editSessionsStorageService.ts
@@ -89,7 +89,7 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes
throw new Error('Please sign in to store your edit session.');
}
- return this.storeClient!.writeResource('editSessions', JSON.stringify(editSession), null, createSyncHeaders(generateUuid()));
+ return this.storeClient!.writeResource('editSessions', JSON.stringify(editSession), null, undefined, createSyncHeaders(generateUuid()));
}
/**
@@ -108,9 +108,9 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes
const headers = createSyncHeaders(generateUuid());
try {
if (ref !== undefined) {
- content = await this.storeClient?.resolveResourceContent('editSessions', ref, headers);
+ content = await this.storeClient?.resolveResourceContent('editSessions', ref, undefined, headers);
} else {
- const result = await this.storeClient?.readResource('editSessions', null, headers);
+ const result = await this.storeClient?.readResource('editSessions', null, undefined, headers);
content = result?.content;
ref = result?.ref;
}
diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts
index 604aabbbbb8..6f0c73a98c1 100644
--- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts
+++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts
@@ -930,15 +930,23 @@ export class ExtensionEditor extends EditorPane {
if (gallery) {
append(moreInfo,
$('.more-info-entry', undefined,
- $('div', undefined, localize('release date', "Released on")),
+ $('div', undefined, localize('published', "Published")),
$('div', undefined, new Date(gallery.releaseDate).toLocaleString(locale, { hourCycle: 'h23' }))
),
$('.more-info-entry', undefined,
- $('div', undefined, localize('last updated', "Last updated")),
+ $('div', undefined, localize('last released', "Last released")),
$('div', undefined, new Date(gallery.lastUpdated).toLocaleString(locale, { hourCycle: 'h23' }))
)
);
}
+ if (extension.local && extension.local.installedTimestamp) {
+ append(moreInfo,
+ $('.more-info-entry', undefined,
+ $('div', undefined, localize('last updated', "Last updated")),
+ $('div', undefined, new Date(extension.local.installedTimestamp).toLocaleString(locale, { hourCycle: 'h23' }))
+ )
+ );
+ }
append(moreInfo,
$('.more-info-entry', undefined,
$('div', undefined, localize('id', "Identifier")),
diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts
index 6e15de5fffd..0352dcda3f8 100644
--- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts
+++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts
@@ -14,11 +14,11 @@ import { IExtensionIgnoredRecommendationsService, IExtensionRecommendationsServi
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IOutputChannelRegistry, Extensions as OutputExtensions } from 'vs/workbench/services/output/common/output';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
-import { VIEWLET_ID, IExtensionsWorkbenchService, IExtensionsViewPaneContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID, INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID, DefaultViewsContext, ExtensionsSortByContext, WORKSPACE_RECOMMENDATIONS_VIEW_ID, IWorkspaceRecommendedExtensionsView, AutoUpdateConfigurationKey, HasOutdatedExtensionsContext, SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID, LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID, ExtensionEditorTab, THEME_ACTIONS_GROUP, INSTALL_ACTIONS_GROUP } from 'vs/workbench/contrib/extensions/common/extensions';
+import { VIEWLET_ID, IExtensionsWorkbenchService, IExtensionsViewPaneContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID, INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID, WORKSPACE_RECOMMENDATIONS_VIEW_ID, IWorkspaceRecommendedExtensionsView, AutoUpdateConfigurationKey, HasOutdatedExtensionsContext, SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID, LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID, ExtensionEditorTab, THEME_ACTIONS_GROUP, INSTALL_ACTIONS_GROUP } from 'vs/workbench/contrib/extensions/common/extensions';
import { ReinstallAction, InstallSpecificVersionOfExtensionAction, ConfigureWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, PromptExtensionInstallFailureAction, SearchExtensionsAction, SwitchToPreReleaseVersionAction, SwitchToReleasedVersionAction, SetColorThemeAction, SetFileIconThemeAction, SetProductIconThemeAction, ClearLanguageAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput';
import { ExtensionEditor } from 'vs/workbench/contrib/extensions/browser/extensionEditor';
-import { StatusUpdater, MaliciousExtensionChecker, ExtensionsViewletViewsContribution, ExtensionsViewPaneContainer } from 'vs/workbench/contrib/extensions/browser/extensionsViewlet';
+import { StatusUpdater, MaliciousExtensionChecker, ExtensionsViewletViewsContribution, ExtensionsViewPaneContainer, BuiltInExtensionsContext, SearchMarketplaceExtensionsContext, RecommendedExtensionsContext, DefaultViewsContext, ExtensionsSortByContext, SearchHasTextContext } from 'vs/workbench/contrib/extensions/browser/extensionsViewlet';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import * as jsonContributionRegistry from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import { ExtensionsConfigurationSchema, ExtensionsConfigurationSchemaId } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate';
@@ -941,7 +941,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi
menuTitles: {
[extensionsFilterSubMenu.id]: localize('recently published filter', "Recently Published")
},
- run: () => runAction(this.instantiationService.createInstance(SearchExtensionsAction, '@sort:publishedDate '))
+ run: () => runAction(this.instantiationService.createInstance(SearchExtensionsAction, '@recentlyPublished '))
});
const extensionsCategoryFilterSubMenu = new MenuId('extensionsCategoryFilterSubMenu');
@@ -985,6 +985,23 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi
});
this.registerExtensionAction({
+ id: 'workbench.extensions.action.recentlyUpdatedExtensions',
+ title: { value: localize('recentlyUpdatedExtensions', "Show Recently Updated Extensions"), original: 'Show Recently Updated Extensions' },
+ category: ExtensionsLocalizedLabel,
+ menu: [{
+ id: MenuId.CommandPalette,
+ }, {
+ id: extensionsFilterSubMenu,
+ group: '3_installed',
+ order: 7,
+ }],
+ menuTitles: {
+ [extensionsFilterSubMenu.id]: localize('recently updated filter', "Recently Updated")
+ },
+ run: () => runAction(this.instantiationService.createInstance(SearchExtensionsAction, '@recentlyUpdated'))
+ });
+
+ this.registerExtensionAction({
id: LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID,
title: { value: localize('showWorkspaceUnsupportedExtensions', "Show Extensions Unsupported By Workspace"), original: 'Show Extensions Unsupported By Workspace' },
category: ExtensionsLocalizedLabel,
@@ -1080,24 +1097,25 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi
MenuRegistry.appendMenuItem(extensionsFilterSubMenu, <ISubmenuItem>{
submenu: extensionsSortSubMenu,
title: localize('sorty by', "Sort By"),
- when: CONTEXT_HAS_GALLERY,
+ when: ContextKeyExpr.and(ContextKeyExpr.or(CONTEXT_HAS_GALLERY, DefaultViewsContext)),
group: '4_sort',
order: 1,
});
[
- { id: 'installs', title: localize('sort by installs', "Install Count") },
- { id: 'rating', title: localize('sort by rating', "Rating") },
- { id: 'name', title: localize('sort by name', "Name") },
- { id: 'publishedDate', title: localize('sort by date', "Published Date") },
- ].map(({ id, title }, index) => {
+ { id: 'installs', title: localize('sort by installs', "Install Count"), precondition: BuiltInExtensionsContext.negate() },
+ { id: 'rating', title: localize('sort by rating', "Rating"), precondition: BuiltInExtensionsContext.negate() },
+ { id: 'name', title: localize('sort by name', "Name"), precondition: BuiltInExtensionsContext.negate() },
+ { id: 'publishedDate', title: localize('sort by published date', "Published Date"), precondition: BuiltInExtensionsContext.negate() },
+ { id: 'updateDate', title: localize('sort by update date', "Updated Date"), precondition: ContextKeyExpr.and(SearchMarketplaceExtensionsContext.negate(), RecommendedExtensionsContext.negate(), BuiltInExtensionsContext.negate()) },
+ ].map(({ id, title, precondition }, index) => {
this.registerExtensionAction({
id: `extensions.sort.${id}`,
title,
- precondition: DefaultViewsContext.toNegated(),
+ precondition: precondition,
menu: [{
id: extensionsSortSubMenu,
- when: CONTEXT_HAS_GALLERY,
+ when: ContextKeyExpr.or(CONTEXT_HAS_GALLERY, DefaultViewsContext),
order: index,
}],
toggled: ExtensionsSortByContext.isEqualTo(id),
@@ -1117,7 +1135,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi
category: ExtensionsLocalizedLabel,
icon: clearSearchResultsIcon,
f1: true,
- precondition: DefaultViewsContext.toNegated(),
+ precondition: SearchHasTextContext,
menu: {
id: MenuId.ViewContainerTitle,
when: ContextKeyExpr.equals('viewContainer', VIEWLET_ID),
diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts
index 27a701f84cb..50c81efc666 100644
--- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts
+++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts
@@ -16,7 +16,7 @@ import { append, $, Dimension, hide, show, DragAndDropObserver } from 'vs/base/b
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
-import { IExtensionsWorkbenchService, IExtensionsViewPaneContainer, VIEWLET_ID, CloseExtensionDetailsOnViewChangeKey, INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID, DefaultViewsContext, ExtensionsSortByContext, WORKSPACE_RECOMMENDATIONS_VIEW_ID, AutoCheckUpdatesConfigurationKey } from '../common/extensions';
+import { IExtensionsWorkbenchService, IExtensionsViewPaneContainer, VIEWLET_ID, CloseExtensionDetailsOnViewChangeKey, INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID, WORKSPACE_RECOMMENDATIONS_VIEW_ID, AutoCheckUpdatesConfigurationKey } from '../common/extensions';
import { InstallLocalExtensionsInRemoteAction, InstallRemoteExtensionsInLocalAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IWorkbenchExtensionEnablementService, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
@@ -61,17 +61,22 @@ import { extractEditorsAndFilesDropData } from 'vs/platform/dnd/browser/dnd';
import { extname } from 'vs/base/common/resources';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
-const SearchMarketplaceExtensionsContext = new RawContextKey<boolean>('searchMarketplaceExtensions', false);
-const SearchIntalledExtensionsContext = new RawContextKey<boolean>('searchInstalledExtensions', false);
+export const DefaultViewsContext = new RawContextKey<boolean>('defaultExtensionViews', true);
+export const ExtensionsSortByContext = new RawContextKey<string>('extensionsSortByValue', '');
+export const SearchMarketplaceExtensionsContext = new RawContextKey<boolean>('searchMarketplaceExtensions', false);
+export const SearchHasTextContext = new RawContextKey<boolean>('extensionSearchHasText', false);
+const SearchInstalledExtensionsContext = new RawContextKey<boolean>('searchInstalledExtensions', false);
+const SearchRecentlyUpdatedExtensionsContext = new RawContextKey<boolean>('searchRecentlyUpdatedExtensions', false);
const SearchOutdatedExtensionsContext = new RawContextKey<boolean>('searchOutdatedExtensions', false);
const SearchEnabledExtensionsContext = new RawContextKey<boolean>('searchEnabledExtensions', false);
const SearchDisabledExtensionsContext = new RawContextKey<boolean>('searchDisabledExtensions', false);
const HasInstalledExtensionsContext = new RawContextKey<boolean>('hasInstalledExtensions', true);
-const BuiltInExtensionsContext = new RawContextKey<boolean>('builtInExtensions', false);
+export const BuiltInExtensionsContext = new RawContextKey<boolean>('builtInExtensions', false);
const SearchBuiltInExtensionsContext = new RawContextKey<boolean>('searchBuiltInExtensions', false);
const SearchUnsupportedWorkspaceExtensionsContext = new RawContextKey<boolean>('searchUnsupportedWorkspaceExtensions', false);
const SearchDeprecatedExtensionsContext = new RawContextKey<boolean>('searchDeprecatedExtensions', false);
-const RecommendedExtensionsContext = new RawContextKey<boolean>('recommendedExtensions', false);
+export const RecommendedExtensionsContext = new RawContextKey<boolean>('recommendedExtensions', false);
+const SortByUpdateDateContext = new RawContextKey<boolean>('sortByUpdateDate', false);
export class ExtensionsViewletViewsContribution implements IWorkbenchContribution {
@@ -221,7 +226,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio
id: 'extensions.recommendedList',
name: localize('recommendedExtensions', "Recommended"),
ctorDescriptor: new SyncDescriptor(DefaultRecommendedExtensionsView, [{ flexibleHeight: true }]),
- when: ContextKeyExpr.and(DefaultViewsContext, ContextKeyExpr.not('config.extensions.showRecommendationsOnlyOnDemand')),
+ when: ContextKeyExpr.and(DefaultViewsContext, SortByUpdateDateContext.negate(), ContextKeyExpr.not('config.extensions.showRecommendationsOnlyOnDemand')),
weight: 40,
order: 3,
canToggleVisibility: true
@@ -288,6 +293,16 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio
});
/*
+ * View used for searching recently updated extensions
+ */
+ viewDescriptors.push({
+ id: 'workbench.views.extensions.searchRecentlyUpdated',
+ name: localize('recently updated', "Recently Updated"),
+ ctorDescriptor: new SyncDescriptor(ExtensionsListView, [{}]),
+ when: ContextKeyExpr.and(ContextKeyExpr.has('searchRecentlyUpdatedExtensions')),
+ });
+
+ /*
* View used for searching enabled extensions
*/
viewDescriptors.push({
@@ -443,7 +458,10 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
private defaultViewsContextKey: IContextKey<boolean>;
private sortByContextKey: IContextKey<string>;
private searchMarketplaceExtensionsContextKey: IContextKey<boolean>;
+ private searchHasTextContextKey: IContextKey<boolean>;
+ private sortByUpdateDateContextKey: IContextKey<boolean>;
private searchInstalledExtensionsContextKey: IContextKey<boolean>;
+ private searchRecentlyUpdatedExtensionsContextKey: IContextKey<boolean>;
private searchOutdatedExtensionsContextKey: IContextKey<boolean>;
private searchEnabledExtensionsContextKey: IContextKey<boolean>;
private searchDisabledExtensionsContextKey: IContextKey<boolean>;
@@ -486,7 +504,10 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
this.defaultViewsContextKey = DefaultViewsContext.bindTo(contextKeyService);
this.sortByContextKey = ExtensionsSortByContext.bindTo(contextKeyService);
this.searchMarketplaceExtensionsContextKey = SearchMarketplaceExtensionsContext.bindTo(contextKeyService);
- this.searchInstalledExtensionsContextKey = SearchIntalledExtensionsContext.bindTo(contextKeyService);
+ this.searchHasTextContextKey = SearchHasTextContext.bindTo(contextKeyService);
+ this.sortByUpdateDateContextKey = SortByUpdateDateContext.bindTo(contextKeyService);
+ this.searchInstalledExtensionsContextKey = SearchInstalledExtensionsContext.bindTo(contextKeyService);
+ this.searchRecentlyUpdatedExtensionsContextKey = SearchRecentlyUpdatedExtensionsContext.bindTo(contextKeyService);
this.searchWorkspaceUnsupportedExtensionsContextKey = SearchUnsupportedWorkspaceExtensionsContext.bindTo(contextKeyService);
this.searchDeprecatedExtensionsContextKey = SearchDeprecatedExtensionsContext.bindTo(contextKeyService);
this.searchOutdatedExtensionsContextKey = SearchOutdatedExtensionsContext.bindTo(contextKeyService);
@@ -633,7 +654,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
.replace(/@tag:/g, 'tag:')
.replace(/@ext:/g, 'ext:')
.replace(/@featured/g, 'featured')
- .replace(/@popular/g, this.extensionManagementServerService.webExtensionManagementServer && !this.extensionManagementServerService.localExtensionManagementServer && !this.extensionManagementServerService.remoteExtensionManagementServer ? '@web' : '@sort:installs')
+ .replace(/@popular/g, this.extensionManagementServerService.webExtensionManagementServer && !this.extensionManagementServerService.localExtensionManagementServer && !this.extensionManagementServerService.remoteExtensionManagementServer ? '@web' : '@popular')
: '';
}
@@ -651,7 +672,9 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
const value = this.normalizedQuery();
this.contextKeyService.bufferChangeEvents(() => {
const isRecommendedExtensionsQuery = ExtensionsListView.isRecommendedExtensionsQuery(value);
+ this.searchHasTextContextKey.set(value.trim() !== '');
this.searchInstalledExtensionsContextKey.set(ExtensionsListView.isInstalledExtensionsQuery(value));
+ this.searchRecentlyUpdatedExtensionsContextKey.set(ExtensionsListView.isSearchRecentlyUpdatedQuery(value));
this.searchOutdatedExtensionsContextKey.set(ExtensionsListView.isOutdatedExtensionsQuery(value));
this.searchEnabledExtensionsContextKey.set(ExtensionsListView.isEnabledExtensionsQuery(value));
this.searchDisabledExtensionsContextKey.set(ExtensionsListView.isDisabledExtensionsQuery(value));
@@ -661,7 +684,8 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
this.builtInExtensionsContextKey.set(ExtensionsListView.isBuiltInExtensionsQuery(value));
this.recommendedExtensionsContextKey.set(isRecommendedExtensionsQuery);
this.searchMarketplaceExtensionsContextKey.set(!!value && !ExtensionsListView.isLocalExtensionsQuery(value) && !isRecommendedExtensionsQuery);
- this.defaultViewsContextKey.set(!value);
+ this.sortByUpdateDateContextKey.set(ExtensionsListView.isSortUpdateDateQuery(value));
+ this.defaultViewsContextKey.set(!value || ExtensionsListView.isSortInstalledExtensionsQuery(value));
});
return this.progress(Promise.all(this.panes.map(view =>
diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts
index 1ed384b4e22..2e0cfa1e40c 100644
--- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts
+++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts
@@ -9,7 +9,7 @@ import { Event, Emitter } from 'vs/base/common/event';
import { isCancellationError, getErrorMessage } from 'vs/base/common/errors';
import { createErrorWithActions } from 'vs/base/common/errorMessage';
import { PagedModel, IPagedModel, IPager, DelayedPagedModel } from 'vs/base/common/paging';
-import { SortBy, SortOrder, IQueryOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
+import { SortOrder, IQueryOptions as IGalleryQueryOptions, SortBy as GallerySortBy } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionManagementServer, IExtensionManagementServerService, EnablementState, IWorkbenchExtensionManagementService, IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
import { areSameExtensions, getExtensionDependencies } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
@@ -90,8 +90,23 @@ interface IQueryResult {
readonly disposables: DisposableStore;
}
+const enum LocalSortBy {
+ UpdateDate = 'UpdateDate',
+}
+
+function isLocalSortBy(value: any): value is LocalSortBy {
+ switch (value as LocalSortBy) {
+ case LocalSortBy.UpdateDate: return true;
+ }
+}
+
+type SortBy = LocalSortBy | GallerySortBy;
+type IQueryOptions = Omit<IGalleryQueryOptions, 'sortBy'> & { sortBy?: SortBy };
+
export class ExtensionsListView extends ViewPane {
+ private static RECENT_UPDATE_DURATION = 7 * 24 * 60 * 60 * 1000; // 7 days
+
private bodyTemplate: {
messageContainer: HTMLElement;
messageSeverityIcon: HTMLElement;
@@ -238,10 +253,11 @@ export class ExtensionsListView extends ViewPane {
};
switch (parsedQuery.sortBy) {
- case 'installs': options.sortBy = SortBy.InstallCount; break;
- case 'rating': options.sortBy = SortBy.WeightedRating; break;
- case 'name': options.sortBy = SortBy.Title; break;
- case 'publishedDate': options.sortBy = SortBy.PublishedDate; break;
+ case 'installs': options.sortBy = GallerySortBy.InstallCount; break;
+ case 'rating': options.sortBy = GallerySortBy.WeightedRating; break;
+ case 'name': options.sortBy = GallerySortBy.Title; break;
+ case 'publishedDate': options.sortBy = GallerySortBy.PublishedDate; break;
+ case 'updateDate': options.sortBy = LocalSortBy.UpdateDate; break;
}
const request = createCancelablePromise(async token => {
@@ -324,11 +340,21 @@ export class ExtensionsListView extends ViewPane {
return { model, disposables: new DisposableStore() };
}
- if (ExtensionsListView.isLocalExtensionsQuery(query.value)) {
+ if (ExtensionsListView.isLocalExtensionsQuery(query.value, query.sortBy)) {
return this.queryLocal(query, options);
}
- const model = await this.queryGallery(query, options, token);
+ if (ExtensionsListView.isSearchPopularQuery(query.value)) {
+ query.value = query.value.replace('@popular', '');
+ options.sortBy = !options.sortBy ? GallerySortBy.InstallCount : options.sortBy;
+ }
+ else if (ExtensionsListView.isSearchRecentlyPublishedQuery(query.value)) {
+ query.value = query.value.replace('@recentlyPublished', '');
+ options.sortBy = !options.sortBy ? GallerySortBy.PublishedDate : options.sortBy;
+ }
+
+ const galleryQueryOptions: IGalleryQueryOptions = { ...options, sortBy: isLocalSortBy(options.sortBy) ? undefined : options.sortBy };
+ const model = await this.queryGallery(query, galleryQueryOptions, token);
return { model, disposables: new DisposableStore() };
}
@@ -415,6 +441,10 @@ export class ExtensionsListView extends ViewPane {
extensions = await this.filterDeprecatedExtensions(local, query, options);
}
+ else if (/@recentlyUpdated/i.test(query.value)) {
+ extensions = this.filterRecentlyUpdatedExtensions(local, query, options);
+ }
+
return { extensions, canIncludeInstalledExtensions };
}
@@ -633,6 +663,22 @@ export class ExtensionsListView extends ViewPane {
return this.sortExtensions(local, options);
}
+ private filterRecentlyUpdatedExtensions(local: IExtension[], query: Query, options: IQueryOptions): IExtension[] {
+ let { value, categories } = this.parseCategories(query.value);
+ const currentTime = Date.now();
+ local = local.filter(e => !e.isBuiltin && e.local?.installedTimestamp !== undefined && currentTime - e.local.installedTimestamp < ExtensionsListView.RECENT_UPDATE_DURATION);
+
+ value = value.replace(/@recentlyUpdated/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase();
+
+ const result = local.filter(e =>
+ (e.name.toLowerCase().indexOf(value) > -1 || e.displayName.toLowerCase().indexOf(value) > -1) &&
+ (!categories.length || categories.some(category => (e.local && e.local.manifest.categories || []).some(c => c.toLowerCase() === category))));
+
+ options.sortBy = options.sortBy ?? LocalSortBy.UpdateDate;
+
+ return this.sortExtensions(result, options);
+ }
+
private mergeAddedExtensions(extensions: IExtension[], newExtensions: IExtension[]): IExtension[] | undefined {
const oldExtensions = [...extensions];
const findPreviousExtensionIndex = (from: number): number => {
@@ -659,10 +705,10 @@ export class ExtensionsListView extends ViewPane {
return hasChanged ? extensions : undefined;
}
- private async queryGallery(query: Query, options: IQueryOptions, token: CancellationToken): Promise<IPagedModel<IExtension>> {
+ private async queryGallery(query: Query, options: IGalleryQueryOptions, token: CancellationToken): Promise<IPagedModel<IExtension>> {
const hasUserDefinedSortOrder = options.sortBy !== undefined;
if (!hasUserDefinedSortOrder && !query.value.trim()) {
- options.sortBy = SortBy.InstallCount;
+ options.sortBy = GallerySortBy.InstallCount;
}
if (this.isRecommendationsQuery(query)) {
@@ -733,11 +779,17 @@ export class ExtensionsListView extends ViewPane {
private sortExtensions(extensions: IExtension[], options: IQueryOptions): IExtension[] {
switch (options.sortBy) {
- case SortBy.InstallCount:
+ case GallerySortBy.InstallCount:
extensions = extensions.sort((e1, e2) => typeof e2.installCount === 'number' && typeof e1.installCount === 'number' ? e2.installCount - e1.installCount : NaN);
break;
- case SortBy.AverageRating:
- case SortBy.WeightedRating:
+ case LocalSortBy.UpdateDate:
+ extensions = extensions.sort((e1, e2) =>
+ typeof e2.local?.installedTimestamp === 'number' && typeof e1.local?.installedTimestamp === 'number' ? e2.local.installedTimestamp - e1.local.installedTimestamp :
+ typeof e2.local?.installedTimestamp === 'number' ? 1 :
+ typeof e1.local?.installedTimestamp === 'number' ? -1 : NaN);
+ break;
+ case GallerySortBy.AverageRating:
+ case GallerySortBy.WeightedRating:
extensions = extensions.sort((e1, e2) => typeof e2.rating === 'number' && typeof e1.rating === 'number' ? e2.rating - e1.rating : NaN);
break;
default:
@@ -1018,7 +1070,7 @@ export class ExtensionsListView extends ViewPane {
this.list = null;
}
- static isLocalExtensionsQuery(query: string): boolean {
+ static isLocalExtensionsQuery(query: string, sortBy?: string): boolean {
return this.isInstalledExtensionsQuery(query)
|| this.isOutdatedExtensionsQuery(query)
|| this.isEnabledExtensionsQuery(query)
@@ -1027,7 +1079,9 @@ export class ExtensionsListView extends ViewPane {
|| this.isSearchBuiltInExtensionsQuery(query)
|| this.isBuiltInGroupExtensionsQuery(query)
|| this.isSearchDeprecatedExtensionsQuery(query)
- || this.isSearchWorkspaceUnsupportedExtensionsQuery(query);
+ || this.isSearchWorkspaceUnsupportedExtensionsQuery(query)
+ || this.isSearchRecentlyUpdatedQuery(query)
+ || this.isSortInstalledExtensionsQuery(query, sortBy);
}
static isSearchBuiltInExtensionsQuery(query: string): boolean {
@@ -1090,6 +1144,26 @@ export class ExtensionsListView extends ViewPane {
return /@recommended:languages/i.test(query);
}
+ static isSortInstalledExtensionsQuery(query: string, sortBy?: string): boolean {
+ return (sortBy !== undefined && sortBy !== '' && query === '') || (!sortBy && /^@sort:\S*$/i.test(query));
+ }
+
+ static isSearchPopularQuery(query: string): boolean {
+ return /@popular/i.test(query);
+ }
+
+ static isSearchRecentlyPublishedQuery(query: string): boolean {
+ return /@recentlyPublished/i.test(query);
+ }
+
+ static isSearchRecentlyUpdatedQuery(query: string): boolean {
+ return /@recentlyUpdated/i.test(query);
+ }
+
+ static isSortUpdateDateQuery(query: string): boolean {
+ return /@sort:updateDate/i.test(query);
+ }
+
override focus(): void {
super.focus();
if (!this.list) {
@@ -1116,7 +1190,7 @@ export class ServerInstalledExtensionsView extends ExtensionsListView {
override async show(query: string): Promise<IPagedModel<IExtension>> {
query = query ? query : '@installed';
- if (!ExtensionsListView.isLocalExtensionsQuery(query)) {
+ if (!ExtensionsListView.isLocalExtensionsQuery(query) || ExtensionsListView.isSortInstalledExtensionsQuery(query)) {
query = query += ' @installed';
}
return super.show(query.trim());
@@ -1128,7 +1202,8 @@ export class EnabledExtensionsView extends ExtensionsListView {
override async show(query: string): Promise<IPagedModel<IExtension>> {
query = query || '@enabled';
- return ExtensionsListView.isEnabledExtensionsQuery(query) ? super.show(query) : this.showEmptyModel();
+ return ExtensionsListView.isEnabledExtensionsQuery(query) ? super.show(query) :
+ ExtensionsListView.isSortInstalledExtensionsQuery(query) ? super.show('@enabled ' + query) : this.showEmptyModel();
}
}
@@ -1136,7 +1211,8 @@ export class DisabledExtensionsView extends ExtensionsListView {
override async show(query: string): Promise<IPagedModel<IExtension>> {
query = query || '@disabled';
- return ExtensionsListView.isDisabledExtensionsQuery(query) ? super.show(query) : this.showEmptyModel();
+ return ExtensionsListView.isDisabledExtensionsQuery(query) ? super.show(query) :
+ ExtensionsListView.isSortInstalledExtensionsQuery(query) ? super.show('@disabled ' + query) : this.showEmptyModel();
}
}
diff --git a/src/vs/workbench/contrib/extensions/common/extensionQuery.ts b/src/vs/workbench/contrib/extensions/common/extensionQuery.ts
index 7c37b541e8a..d921466976b 100644
--- a/src/vs/workbench/contrib/extensions/common/extensionQuery.ts
+++ b/src/vs/workbench/contrib/extensions/common/extensionQuery.ts
@@ -13,9 +13,9 @@ export class Query {
}
static suggestions(query: string): string[] {
- const commands = ['installed', 'outdated', 'enabled', 'disabled', 'builtin', 'featured', 'popular', 'recommended', 'workspaceUnsupported', 'deprecated', 'sort', 'category', 'tag', 'ext', 'id'] as const;
+ const commands = ['installed', 'outdated', 'enabled', 'disabled', 'builtin', 'featured', 'popular', 'recommended', 'recentlyUpdated', 'recentlyPublished', 'workspaceUnsupported', 'deprecated', 'sort', 'category', 'tag', 'ext', 'id'] as const;
const subcommands = {
- 'sort': ['installs', 'rating', 'name', 'publishedDate'],
+ 'sort': ['installs', 'rating', 'name', 'publishedDate', 'updateDate'],
'category': EXTENSION_CATEGORIES.map(c => `"${c.toLowerCase()}"`),
'tag': [''],
'ext': [''],
diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts
index ca1a3a5ff3f..32c2becd92f 100644
--- a/src/vs/workbench/contrib/extensions/common/extensions.ts
+++ b/src/vs/workbench/contrib/extensions/common/extensions.ts
@@ -187,8 +187,6 @@ export const INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID = 'workbench.extensions.comm
export const LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID = 'workbench.extensions.action.listWorkspaceUnsupportedExtensions';
// Context Keys
-export const DefaultViewsContext = new RawContextKey<boolean>('defaultExtensionViews', true);
-export const ExtensionsSortByContext = new RawContextKey<string>('extensionsSortByValue', '');
export const HasOutdatedExtensionsContext = new RawContextKey<boolean>('hasOutdatedExtensions', false);
// Context Menu Groups
diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts
index 9d665e5b934..6e14b1bccec 100644
--- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts
+++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts
@@ -11,7 +11,7 @@ import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/com
import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/browser/extensionsWorkbenchService';
import {
IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions,
- DidUninstallExtensionEvent, InstallExtensionEvent, SortBy, InstallExtensionResult, getTargetPlatform, IExtensionInfo, UninstallExtensionEvent
+ DidUninstallExtensionEvent, InstallExtensionEvent, InstallExtensionResult, getTargetPlatform, IExtensionInfo, UninstallExtensionEvent, SortBy
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IProfileAwareExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { IExtensionRecommendationsService, ExtensionRecommendationReason } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
@@ -57,13 +57,13 @@ suite('ExtensionsListView Tests', () => {
uninstallEvent: Emitter<UninstallExtensionEvent>,
didUninstallEvent: Emitter<DidUninstallExtensionEvent>;
- const localEnabledTheme = aLocalExtension('first-enabled-extension', { categories: ['Themes', 'random'] });
- const localEnabledLanguage = aLocalExtension('second-enabled-extension', { categories: ['Programming languages'] });
- const localDisabledTheme = aLocalExtension('first-disabled-extension', { categories: ['themes'] });
- const localDisabledLanguage = aLocalExtension('second-disabled-extension', { categories: ['programming languages'] });
- const localRandom = aLocalExtension('random-enabled-extension', { categories: ['random'] });
- const builtInTheme = aLocalExtension('my-theme', { contributes: { themes: ['my-theme'] } }, { type: ExtensionType.System });
- const builtInBasic = aLocalExtension('my-lang', { contributes: { grammars: [{ language: 'my-language' }] } }, { type: ExtensionType.System });
+ const localEnabledTheme = aLocalExtension('first-enabled-extension', { categories: ['Themes', 'random'] }, { installedTimestamp: 123456 });
+ const localEnabledLanguage = aLocalExtension('second-enabled-extension', { categories: ['Programming languages'] }, { installedTimestamp: Date.now() });
+ const localDisabledTheme = aLocalExtension('first-disabled-extension', { categories: ['themes'] }, { installedTimestamp: 234567 });
+ const localDisabledLanguage = aLocalExtension('second-disabled-extension', { categories: ['programming languages'] }, { installedTimestamp: Date.now() - 50000 });
+ const localRandom = aLocalExtension('random-enabled-extension', { categories: ['random'] }, { installedTimestamp: 345678 });
+ const builtInTheme = aLocalExtension('my-theme', { contributes: { themes: ['my-theme'] } }, { type: ExtensionType.System, installedTimestamp: 222 });
+ const builtInBasic = aLocalExtension('my-lang', { contributes: { grammars: [{ language: 'my-language' }] } }, { type: ExtensionType.System, installedTimestamp: 666666 });
const workspaceRecommendationA = aGalleryExtension('workspace-recommendation-A');
const workspaceRecommendationB = aGalleryExtension('workspace-recommendation-B');
@@ -206,6 +206,8 @@ suite('ExtensionsListView Tests', () => {
assert.strictEqual(ExtensionsListView.isLocalExtensionsQuery('@enabled'), true);
assert.strictEqual(ExtensionsListView.isLocalExtensionsQuery('@disabled'), true);
assert.strictEqual(ExtensionsListView.isLocalExtensionsQuery('@outdated'), true);
+ assert.strictEqual(ExtensionsListView.isLocalExtensionsQuery('@sort:name'), true);
+ assert.strictEqual(ExtensionsListView.isLocalExtensionsQuery('@sort:updateDate'), true);
assert.strictEqual(ExtensionsListView.isLocalExtensionsQuery('@installed searchText'), true);
assert.strictEqual(ExtensionsListView.isLocalExtensionsQuery('@enabled searchText'), true);
assert.strictEqual(ExtensionsListView.isLocalExtensionsQuery('@disabled searchText'), true);
@@ -347,6 +349,26 @@ suite('ExtensionsListView Tests', () => {
});
});
+ test('Test local query with sorting order', async () => {
+ await testableView.show('@recentlyUpdated').then(result => {
+ assert.strictEqual(result.length, 2, 'Unexpected number of results for @recentlyUpdated');
+ const actual = [result.get(0).name, result.get(1).name];
+ const expected = [localEnabledLanguage.manifest.name, localDisabledLanguage.manifest.name];
+ for (let i = 0; i < actual.length; i++) {
+ assert.strictEqual(actual[i], expected[i], 'Unexpected default sort order of extensions for @recentlyUpdate query');
+ }
+ });
+
+ await testableView.show('@installed @sort:updateDate').then(result => {
+ assert.strictEqual(result.length, 5, 'Unexpected number of results for @sort:updateDate. Expected all localy installed Extension which are not builtin');
+ const actual = [result.get(0).local?.installedTimestamp, result.get(1).local?.installedTimestamp, result.get(2).local?.installedTimestamp, result.get(3).local?.installedTimestamp, result.get(4).local?.installedTimestamp];
+ const expected = [localEnabledLanguage.installedTimestamp, localDisabledLanguage.installedTimestamp, localRandom.installedTimestamp, localDisabledTheme.installedTimestamp, localEnabledTheme.installedTimestamp];
+ for (let i = 0; i < result.length; i++) {
+ assert.strictEqual(actual[i], expected[i], 'Unexpected extension sorting for @sort:updateDate query.');
+ }
+ });
+ });
+
test('Test @recommended:workspace query', () => {
const workspaceRecommendedExtensions = [
workspaceRecommendationA,
diff --git a/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts
index 016bacbe060..bfbc9108188 100644
--- a/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts
+++ b/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts
@@ -246,10 +246,10 @@ export class FileEditorInput extends AbstractTextResourceEditorInput implements
return this.preferredLanguageId;
}
- setLanguageId(languageId: string): void {
+ setLanguageId(languageId: string, source?: string): void {
this.setPreferredLanguageId(languageId);
- this.model?.setLanguageId(languageId);
+ this.model?.setLanguageId(languageId, source);
}
setPreferredLanguageId(languageId: string): void {
diff --git a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts
index e4d7aa3d2ac..e052feebca7 100644
--- a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts
+++ b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts
@@ -5,6 +5,7 @@
import { VSBuffer } from 'vs/base/common/buffer';
import { CancellationToken } from 'vs/base/common/cancellation';
+import { Iterable } from 'vs/base/common/iterator';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { parse } from 'vs/base/common/marshalling';
@@ -136,14 +137,6 @@ export class InteractiveDocumentContribution extends Disposable implements IWork
transientOptions: contentOptions
};
},
- save: async (uri: URI) => {
- // trigger backup always
- return false;
- },
- saveAs: async (uri: URI, target: URI, token: CancellationToken) => {
- // return this._proxy.$saveNotebookAs(viewType, uri, target, token);
- return false;
- },
backup: async (uri: URI, token: CancellationToken) => {
const doc = notebookService.listNotebookDocuments().find(document => document.uri.toString() === uri.toString());
if (doc) {
@@ -728,6 +721,21 @@ registerAction2(class extends Action2 {
if (editorControl && editorControl.notebookEditor && editorControl.codeEditor) {
editorService.activeEditorPane?.focus();
}
+ else {
+ // find and open the most recent interactive window
+ const openEditors = editorService.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE);
+ const interactiveWindow = Iterable.find(openEditors, identifier => { return identifier.editor.typeId === InteractiveEditorInput.ID; });
+ if (interactiveWindow) {
+ const editorInput = interactiveWindow.editor as InteractiveEditorInput;
+ const currentGroup = interactiveWindow.groupId;
+ const editor = await editorService.openEditor(editorInput, currentGroup);
+ const editorControl = editor?.getControl() as { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget } | undefined;
+
+ if (editorControl && editorControl.notebookEditor && editorControl.codeEditor) {
+ editorService.activeEditorPane?.focus();
+ }
+ }
+ }
}
});
diff --git a/src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts b/src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts
index 30b4d7f15ab..d5605600b90 100644
--- a/src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts
+++ b/src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts
@@ -11,7 +11,7 @@ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWo
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar';
-import { ILanguageDetectionService, LanguageDetectionHintConfig } from 'vs/workbench/services/languageDetection/common/languageDetectionWorkerService';
+import { ILanguageDetectionService, LanguageDetectionHintConfig, LanguageDetectionLanguageEventSource } from 'vs/workbench/services/languageDetection/common/languageDetectionWorkerService';
import { ThrottledDelayer } from 'vs/base/common/async';
import { ILanguageService } from 'vs/editor/common/languages/language';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
@@ -139,7 +139,7 @@ registerAction2(class extends Action2 {
if (editorUri) {
const lang = await languageDetectionService.detectLanguage(editorUri);
if (lang) {
- editor.getModel()?.setMode(lang);
+ editor.getModel()?.setMode(lang, LanguageDetectionLanguageEventSource);
} else {
notificationService.warn(localize('noDetection', "Unable to detect editor language"));
}
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts
index 8fd7ea76eb1..dfafb936be0 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts
@@ -194,8 +194,8 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
return Boolean(this._outTextModel?.isDirty());
}
- setLanguageId(languageId: string, _setExplicitly?: boolean): void {
- this._model?.setLanguageId(languageId);
+ setLanguageId(languageId: string, source?: string): void {
+ this._model?.setLanguageId(languageId, source);
}
// implement get/set languageId
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts
index a7dedab28a2..357ffdfa1fb 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts
@@ -400,12 +400,12 @@ export class MergeEditorModel extends EditorModel {
public readonly hasUnhandledConflicts = this.unhandledConflictsCount.map(value => /** @description hasUnhandledConflicts */ value > 0);
- public setLanguageId(languageId: string): void {
+ public setLanguageId(languageId: string, source?: string): void {
const language = this.languageService.createById(languageId);
- this.modelService.setMode(this.base, language);
- this.modelService.setMode(this.input1.textModel, language);
- this.modelService.setMode(this.input2.textModel, language);
- this.modelService.setMode(this.resultTextModel, language);
+ this.modelService.setMode(this.base, language, source);
+ this.modelService.setMode(this.input1.textModel, language, source);
+ this.modelService.setMode(this.input2.textModel, language, source);
+ this.modelService.setMode(this.resultTextModel, language, source);
}
public getInitialResultValue(): string {
diff --git a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts
index 70d6930241c..eefafb7edab 100644
--- a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts
+++ b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts
@@ -5,7 +5,7 @@
import { Iterable } from 'vs/base/common/iterator';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
-import { URI, UriComponents } from 'vs/base/common/uri';
+import { UriComponents } from 'vs/base/common/uri';
import { ILanguageService } from 'vs/editor/common/languages/language';
import { localize } from 'vs/nls';
import { MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
@@ -27,6 +27,7 @@ import { Schemas } from 'vs/base/common/network';
const EXECUTE_NOTEBOOK_COMMAND_ID = 'notebook.execute';
const CANCEL_NOTEBOOK_COMMAND_ID = 'notebook.cancelExecution';
+const INTERRUPT_NOTEBOOK_COMMAND_ID = 'notebook.interruptExecution';
const CANCEL_CELL_COMMAND_ID = 'notebook.cell.cancelExecution';
const EXECUTE_CELL_FOCUS_CONTAINER_COMMAND_ID = 'notebook.cell.executeAndFocusContainer';
const EXECUTE_CELL_SELECT_BELOW = 'notebook.cell.executeAndSelectBelow';
@@ -488,22 +489,61 @@ registerAction2(class ExecuteCellInsertBelow extends NotebookCellAction {
}
});
-registerAction2(class CancelNotebook extends NotebookAction {
+class CancelNotebook extends NotebookAction {
+ override getEditorContextFromArgsOrActive(accessor: ServicesAccessor, context?: UriComponents): INotebookActionContext | undefined {
+ return getContextFromUri(accessor, context) ?? getContextFromActiveEditor(accessor.get(IEditorService));
+ }
+
+ async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise<void> {
+ return context.notebookEditor.cancelNotebookCells();
+ }
+}
+
+registerAction2(class CancelAllNotebook extends CancelNotebook {
constructor() {
super({
id: CANCEL_NOTEBOOK_COMMAND_ID,
- title: localize('notebookActions.cancelNotebook', "Stop Execution"),
+ title: {
+ value: localize('notebookActions.cancelNotebook', "Stop Execution"),
+ original: 'Stop Execution'
+ },
icon: icons.stopIcon,
- description: {
- description: localize('notebookActions.cancelNotebook', "Stop Execution"),
- args: [
- {
- name: 'uri',
- description: 'The document uri',
- constraint: URI
- }
- ]
+ menu: [
+ {
+ id: MenuId.EditorTitle,
+ order: -1,
+ group: 'navigation',
+ when: ContextKeyExpr.and(
+ NOTEBOOK_IS_ACTIVE_EDITOR,
+ NOTEBOOK_HAS_RUNNING_CELL,
+ NOTEBOOK_INTERRUPTIBLE_KERNEL.toNegated(),
+ ContextKeyExpr.notEquals('config.notebook.globalToolbar', true)
+ )
+ },
+ {
+ id: MenuId.NotebookToolbar,
+ order: -1,
+ group: 'navigation/execute',
+ when: ContextKeyExpr.and(
+ NOTEBOOK_HAS_RUNNING_CELL,
+ NOTEBOOK_INTERRUPTIBLE_KERNEL.toNegated(),
+ ContextKeyExpr.equals('config.notebook.globalToolbar', true)
+ )
+ }
+ ]
+ });
+ }
+});
+
+registerAction2(class InterruptNotebook extends CancelNotebook {
+ constructor() {
+ super({
+ id: INTERRUPT_NOTEBOOK_COMMAND_ID,
+ title: {
+ value: localize('notebookActions.interruptNotebook', "Interrupt"),
+ original: 'Interrupt'
},
+ icon: icons.stopIcon,
menu: [
{
id: MenuId.EditorTitle,
@@ -529,14 +569,6 @@ registerAction2(class CancelNotebook extends NotebookAction {
]
});
}
-
- override getEditorContextFromArgsOrActive(accessor: ServicesAccessor, context?: UriComponents): INotebookActionContext | undefined {
- return getContextFromUri(accessor, context) ?? getContextFromActiveEditor(accessor.get(IEditorService));
- }
-
- async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise<void> {
- return context.notebookEditor.cancelNotebookCells();
- }
});
diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts
index 69e2a7e41ff..583ba78776d 100644
--- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts
+++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts
@@ -37,7 +37,7 @@ import { NOTEBOOK_WEBVIEW_BOUNDARY } from 'vs/workbench/contrib/notebook/browser
import { preloadsScriptStr } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads';
import { transformWebviewThemeVars } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewThemeMapping';
import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel';
-import { CellUri, INotebookRendererInfo, NotebookSetting, RendererMessagingSpec } from 'vs/workbench/contrib/notebook/common/notebookCommon';
+import { CellUri, INotebookRendererInfo, isTextStreamMime, NotebookSetting, RendererMessagingSpec } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookKernel } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
import { IScopedRendererMessaging } from 'vs/workbench/contrib/notebook/common/notebookRendererMessagingService';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
@@ -46,6 +46,7 @@ import { WebviewWindowDragMonitor } from 'vs/workbench/contrib/webview/browser/w
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { FromWebviewMessage, IAckOutputHeight, IClickedDataUrlMessage, ICodeBlockHighlightRequest, IContentWidgetTopRequest, IControllerPreload, ICreationContent, ICreationRequestMessage, IFindMatch, IMarkupCellInitialization, RendererMetadata, ToWebviewMessage } from './webviewMessages';
+import { compressOutputItemStreams } from 'vs/workbench/contrib/notebook/browser/view/renderers/stdOutErrorPreProcessor';
export interface ICachedInset<K extends ICommonCellInfo> {
outputId: string;
@@ -1278,12 +1279,14 @@ var requirejs = (function() {
let updatedContent: ICreationContent | undefined = undefined;
if (content.type === RenderOutputType.Extension) {
const output = content.source.model;
- const first = output.outputs.find(op => op.mime === content.mimeType)!;
+ const firstBuffer = isTextStreamMime(content.mimeType) ?
+ compressOutputItemStreams(content.mimeType, output.outputs) :
+ output.outputs.find(op => op.mime === content.mimeType)!.data.buffer;
updatedContent = {
type: RenderOutputType.Extension,
outputId: outputCache.outputId,
- mimeType: first.mime,
- valueBytes: first.data.buffer,
+ mimeType: content.mimeType,
+ valueBytes: firstBuffer,
metadata: output.metadata,
};
}
diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/stdOutErrorPreProcessor.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/stdOutErrorPreProcessor.ts
new file mode 100644
index 00000000000..7f20c6316fc
--- /dev/null
+++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/stdOutErrorPreProcessor.ts
@@ -0,0 +1,56 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { VSBuffer } from 'vs/base/common/buffer';
+import type { IOutputItemDto } from 'vs/workbench/contrib/notebook/common/notebookCommon';
+
+
+/**
+ * Given a stream of individual stdout outputs, this function will return the compressed lines, escaping some of the common terminal escape codes.
+ * E.g. some terminal escape codes would result in the previous line getting cleared, such if we had 3 lines and
+ * last line contained such a code, then the result string would be just the first two lines.
+ */
+export function compressOutputItemStreams(mimeType: string, outputs: IOutputItemDto[]) {
+ const buffers: Uint8Array[] = [];
+ let startAppending = false;
+
+ // Pick the first set of outputs with the same mime type.
+ for (const output of outputs) {
+ if (output.mime === mimeType) {
+ if ((buffers.length === 0 || startAppending)) {
+ buffers.push(output.data.buffer);
+ startAppending = true;
+ }
+ } else if (startAppending) {
+ startAppending = false;
+ }
+ }
+ compressStreamBuffer(buffers);
+ return VSBuffer.concat(buffers.map(buffer => VSBuffer.wrap(buffer))).buffer;
+}
+const MOVE_CURSOR_1_LINE_COMMAND = `${String.fromCharCode(27)}[A`;
+const MOVE_CURSOR_1_LINE_COMMAND_BYTES = MOVE_CURSOR_1_LINE_COMMAND.split('').map(c => c.charCodeAt(0));
+const LINE_FEED = 10;
+function compressStreamBuffer(streams: Uint8Array[]) {
+ streams.forEach((stream, index) => {
+ if (index === 0 || stream.length < MOVE_CURSOR_1_LINE_COMMAND.length) {
+ return;
+ }
+
+ const previousStream = streams[index - 1];
+
+ // Remove the previous line if required.
+ const command = stream.subarray(0, MOVE_CURSOR_1_LINE_COMMAND.length);
+ if (command[0] === MOVE_CURSOR_1_LINE_COMMAND_BYTES[0] && command[1] === MOVE_CURSOR_1_LINE_COMMAND_BYTES[1] && command[2] === MOVE_CURSOR_1_LINE_COMMAND_BYTES[2]) {
+ const lastIndexOfLineFeed = previousStream.lastIndexOf(LINE_FEED);
+ if (lastIndexOfLineFeed === -1) {
+ return;
+ }
+ streams[index - 1] = previousStream.subarray(0, lastIndexOfLineFeed);
+ streams[index] = stream.subarray(MOVE_CURSOR_1_LINE_COMMAND.length);
+ }
+ });
+ return streams;
+}
diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts
index 294f43e558b..35506ece47f 100644
--- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts
+++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts
@@ -30,6 +30,7 @@ export class NotebookEditorContextKeys {
private readonly _disposables = new DisposableStore();
private readonly _viewModelDisposables = new DisposableStore();
private readonly _cellOutputsListeners: IDisposable[] = [];
+ private readonly _selectedKernelDisposables = new DisposableStore();
constructor(
private readonly _editor: INotebookEditorDelegate,
@@ -174,6 +175,13 @@ export class NotebookEditorContextKeys {
this._interruptibleKernel.set(selected?.implementsInterrupt ?? false);
this._notebookKernelSelected.set(Boolean(selected));
this._notebookKernel.set(selected?.id ?? '');
+
+ this._selectedKernelDisposables.clear();
+ if (selected) {
+ this._selectedKernelDisposables.add(selected.onDidChange(() => {
+ this._interruptibleKernel.set(selected?.implementsInterrupt ?? false);
+ }));
+ }
}
private _updateForNotebookOptions(): void {
diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts
index 361c0baa2b1..af7f455c8af 100644
--- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts
+++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts
@@ -947,3 +947,11 @@ export interface NotebookExtensionDescription {
readonly id: ExtensionIdentifier;
readonly location: UriComponents | undefined;
}
+
+/**
+ * Whether the provided mime type is a text streamn like `stdout`, `stderr`.
+ */
+export function isTextStreamMime(mimeType: string) {
+ return ['application/vnd.code.notebook.stdout', 'application/x.notebook.stdout', 'application/x.notebook.stream', 'application/vnd.code.notebook.stderr', 'application/x.notebook.stderr'].includes(mimeType);
+}
+
diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts
index 7cd345036a7..362123693b2 100644
--- a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts
+++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts
@@ -377,7 +377,7 @@ export class ComplexNotebookEditorModel extends EditorModel implements INotebook
if (!this.isResolved()) {
return;
}
- const success = await this._contentProvider.save(this.notebook.uri, CancellationToken.None);
+ const success = false;
this._logService.debug(`[notebook editor model] save(${versionId}) - document saved saved, start updating file stats`, this.resource.toString(true), success);
this._lastResolvedFileStat = await this._resolveStats(this.resource);
if (success) {
@@ -407,7 +407,7 @@ export class ComplexNotebookEditorModel extends EditorModel implements INotebook
return undefined;
}
- const success = await this._contentProvider.saveAs(this.notebook.uri, targetResource, CancellationToken.None);
+ const success = false;
this._logService.debug(`[notebook editor model] saveAs - document saved, start updating file stats`, this.resource.toString(true), success);
this._lastResolvedFileStat = await this._resolveStats(this.resource);
if (!success) {
diff --git a/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts b/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts
index 756377bdd86..a32d1ba2815 100644
--- a/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts
+++ b/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts
@@ -30,6 +30,7 @@ export interface INotebookKernelChangeEvent {
kind?: true;
supportedLanguages?: true;
hasExecutionOrder?: true;
+ hasInterruptHandler?: true;
}
export interface INotebookKernel {
diff --git a/src/vs/workbench/contrib/notebook/common/notebookService.ts b/src/vs/workbench/contrib/notebook/common/notebookService.ts
index 0c37ffe127c..f67d9be987d 100644
--- a/src/vs/workbench/contrib/notebook/common/notebookService.ts
+++ b/src/vs/workbench/contrib/notebook/common/notebookService.ts
@@ -22,8 +22,6 @@ export interface INotebookContentProvider {
options: TransientOptions;
open(uri: URI, backupId: string | VSBuffer | undefined, untitledDocumentData: VSBuffer | undefined, token: CancellationToken): Promise<{ data: NotebookData; transientOptions: TransientOptions }>;
- save(uri: URI, token: CancellationToken): Promise<boolean>;
- saveAs(uri: URI, target: URI, token: CancellationToken): Promise<boolean>;
backup(uri: URI, token: CancellationToken): Promise<string | VSBuffer>;
}
diff --git a/src/vs/workbench/contrib/performance/browser/performance.contribution.ts b/src/vs/workbench/contrib/performance/browser/performance.contribution.ts
index d001722562c..ac2fe448888 100644
--- a/src/vs/workbench/contrib/performance/browser/performance.contribution.ts
+++ b/src/vs/workbench/contrib/performance/browser/performance.contribution.ts
@@ -13,6 +13,8 @@ import { Extensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common
import { EditorExtensions, IEditorSerializer, IEditorFactoryRegistry } from 'vs/workbench/common/editor';
import { PerfviewContrib, PerfviewInput } from 'vs/workbench/contrib/performance/browser/perfviewEditor';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
+import { InstantiationService, Trace } from 'vs/platform/instantiation/common/instantiationService';
+import { EventProfiling } from 'vs/base/common/event';
// -- startup performance view
@@ -55,3 +57,74 @@ registerAction2(class extends Action2 {
return editorService.openEditor(instaService.createInstance(PerfviewInput), { pinned: true });
}
});
+
+
+registerAction2(class PrintServiceCycles extends Action2 {
+
+ constructor() {
+ super({
+ id: 'perf.insta.printAsyncCycles',
+ title: { value: localize('cycles', "Print Service Cycles"), original: 'Print Service Cycles' },
+ category: CATEGORIES.Developer,
+ f1: true
+ });
+ }
+
+ run(accessor: ServicesAccessor) {
+ const instaService = accessor.get(IInstantiationService);
+ if (instaService instanceof InstantiationService) {
+ const cycle = instaService._globalGraph?.findCycleSlow();
+ if (cycle) {
+ console.warn(`CYCLE`, cycle);
+ } else {
+ console.warn(`YEAH, no more cycles`);
+ }
+ }
+ }
+});
+
+registerAction2(class PrintServiceTraces extends Action2 {
+
+ constructor() {
+ super({
+ id: 'perf.insta.printTraces',
+ title: { value: localize('insta.trace', "Print Service Traces"), original: 'Print Service Traces' },
+ category: CATEGORIES.Developer,
+ f1: true
+ });
+ }
+
+ run() {
+ if (Trace.all.size === 0) {
+ console.log('Enable via `instantiationService.ts#_enableAllTracing`');
+ return;
+ }
+
+ for (const item of Trace.all) {
+ console.log(item);
+ }
+ }
+});
+
+
+registerAction2(class PrintEventProfiling extends Action2 {
+
+ constructor() {
+ super({
+ id: 'perf.event.profiling',
+ title: { value: localize('emitter', "Print Emitter Profiles"), original: 'Print Emitter Profiles' },
+ category: CATEGORIES.Developer,
+ f1: true
+ });
+ }
+
+ run(): void {
+ if (EventProfiling.all.size === 0) {
+ console.log('USE `EmitterOptions._profName` to enable profiling');
+ return;
+ }
+ for (const item of EventProfiling.all) {
+ console.log(`${item.name}: ${item.invocationCount}invocations COST ${item.elapsedOverall}ms, ${item.listenerCount} listeners, avg cost is ${item.durations.reduce((a, b) => a + b, 0) / item.durations.length}ms`);
+ }
+ }
+});
diff --git a/src/vs/workbench/contrib/snippets/browser/commands/fileTemplateSnippets.ts b/src/vs/workbench/contrib/snippets/browser/commands/fileTemplateSnippets.ts
index 2074eef20bd..afc896d37fb 100644
--- a/src/vs/workbench/contrib/snippets/browser/commands/fileTemplateSnippets.ts
+++ b/src/vs/workbench/contrib/snippets/browser/commands/fileTemplateSnippets.ts
@@ -62,7 +62,7 @@ export class ApplyFileSnippetAction extends SnippetsAction {
}]);
// set language if possible
- modelService.setMode(editor.getModel(), langService.createById(selection.langId));
+ modelService.setMode(editor.getModel(), langService.createById(selection.langId), ApplyFileSnippetAction.Id);
}
}
diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts
index 18cd0e959dd..a6f47c08802 100644
--- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts
+++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts
@@ -80,6 +80,7 @@ import { IPathService } from 'vs/workbench/services/path/common/pathService';
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
import { TerminalExitReason } from 'vs/platform/terminal/common/terminal';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
+import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
const QUICKOPEN_HISTORY_LIMIT_CONFIG = 'task.quickOpen.history';
const PROBLEM_MATCHER_NEVER_CONFIG = 'task.problemMatchers.neverPrompt';
@@ -261,7 +262,8 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
@ILogService private readonly _logService: ILogService,
@IThemeService private readonly _themeService: IThemeService,
@ILifecycleService private readonly _lifecycleService: ILifecycleService,
- @IRemoteAgentService remoteAgentService: IRemoteAgentService
+ @IRemoteAgentService remoteAgentService: IRemoteAgentService,
+ @IInstantiationService private readonly _instantiationService: IInstantiationService
) {
super();
@@ -1953,7 +1955,6 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
private async _getGroupedTasks(filter?: ITaskFilter): Promise<TaskMap> {
const type = filter?.type;
- const name = filter?.task;
const needsRecentTasksMigration = this._needsRecentTasksMigration();
await this._activateTaskProviders(filter?.type);
const validTypes: IStringDictionary<boolean> = Object.create(null);
@@ -2006,9 +2007,6 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
if ((task.type !== 'shell') && (task.type !== 'process')) {
this._showOutput();
}
- if (task.getDefinition(true)?._key === name || task._label === name) {
- return done({ tasks: [task], extension: taskSet.extension });
- }
break;
}
}
@@ -2639,7 +2637,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
return entries;
}
private async _showTwoLevelQuickPick(placeHolder: string, defaultEntry?: ITaskQuickPickEntry, type?: string, name?: string) {
- return TaskQuickPick.show(this, this._configurationService, this._quickInputService, this._notificationService, this._dialogService, this._themeService, placeHolder, defaultEntry, type, name);
+ return this._instantiationService.createInstance(TaskQuickPick).show(placeHolder, defaultEntry, type, name);
}
private async _showQuickPick(tasks: Promise<Task[]> | Task[], placeHolder: string, defaultEntry?: ITaskQuickPickEntry, group: boolean = false, sort: boolean = false, selectedEntry?: ITaskQuickPickEntry, additionalEntries?: ITaskQuickPickEntry[], type?: string, name?: string): Promise<ITaskQuickPickEntry | undefined | null> {
@@ -2696,7 +2694,6 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
picker.busy = true;
pickEntries.then(entries => {
picker.busy = false;
- picker.items = entries.filter(e => e.type === type);
});
picker.show();
@@ -2783,17 +2780,16 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
private _runTaskCommand(arg?: any): void {
const identifier = this._getTaskIdentifier(arg);
const type = arg && typeof arg !== 'string' && 'type' in arg ? arg.type : undefined;
- let task = arg && typeof arg !== 'string' && 'task' in arg ? arg.task : arg === 'string' ? arg : undefined;
- if (identifier) {
- this._getGroupedTasks({ task, type }).then(async (grouped) => {
- const resolver = this._createResolver(grouped);
- const tasks = grouped.all();
- const folderURIs: (URI | string)[] = this._contextService.getWorkspace().folders.map(folder => folder.uri);
- if (this._contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
- folderURIs.push(this._contextService.getWorkspace().configuration!);
- }
- folderURIs.push(USER_TASKS_GROUP_KEY);
- // match by identifier
+ const task = arg && typeof arg !== 'string' && 'task' in arg ? arg.task : arg === 'string' ? arg : undefined;
+ this._getGroupedTasks().then(async (grouped) => {
+ const tasks = grouped.all();
+ const resolver = this._createResolver(grouped);
+ const folderURIs: (URI | string)[] = this._contextService.getWorkspace().folders.map(folder => folder.uri);
+ if (this._contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
+ folderURIs.push(this._contextService.getWorkspace().configuration!);
+ }
+ folderURIs.push(USER_TASKS_GROUP_KEY);
+ if (identifier) {
for (const uri of folderURIs) {
const task = await resolver.resolve(uri, identifier);
if (task) {
@@ -2801,22 +2797,26 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
return;
}
}
- // match by label
- if (!!task) {
- const taskToRun = tasks.find(g => g._label === task);
- if (taskToRun) {
- this.run(taskToRun).then(undefined, () => { });
- return;
+ }
+ const exactMatchTask = tasks.find(t => task && (t.getDefinition(true)?.configurationProperties?.identifier === task || t.configurationProperties?.identifier === task || t._label === task));
+ const filteredTasks = tasks.filter(t => t._label.includes(task));
+ if (exactMatchTask) {
+ const id = exactMatchTask.configurationProperties?.identifier || exactMatchTask.getDefinition(true)?.configurationProperties?.identifier;
+ if (id) {
+ for (const uri of folderURIs) {
+ const task = await resolver.resolve(uri, id);
+ if (task) {
+ this.run(task, { attachProblemMatcher: true }, TaskRunSource.User).then(undefined, () => { });
+ return;
+ }
}
}
- if (task && !tasks.some(g => g._label.includes(task))) {
- task = undefined;
- }
- // if task is defined, will be used as a filter
- this._doRunTaskCommand(tasks, type, task);
- });
- }
- this._doRunTaskCommand();
+ } else if (filteredTasks?.length > 1) {
+ return this._doRunTaskCommand(tasks, type, task);
+ } else {
+ return this._doRunTaskCommand();
+ }
+ });
}
private _tasksAndGroupedTasks(filter?: ITaskFilter): { tasks: Promise<Task[]>; grouped: Promise<TaskMap> } {
@@ -3268,7 +3268,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
if (this._isTaskEntry(selection)) {
this._configureTask(selection.task);
} else if (this._isSettingEntry(selection)) {
- const taskQuickPick = new TaskQuickPick(this, this._configurationService, this._quickInputService, this._notificationService, this._themeService, this._dialogService);
+ const taskQuickPick = this._instantiationService.createInstance(TaskQuickPick);
taskQuickPick.handleSettingOption(selection.settingType);
} else if (selection.folder && (this._contextService.getWorkbenchState() !== WorkbenchState.EMPTY)) {
this._openTaskFile(selection.folder.toResource('.vscode/tasks.json'), TaskSourceKind.Workspace);
diff --git a/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts b/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts
index de37cba16a0..dc5551150c3 100644
--- a/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts
+++ b/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts
@@ -21,6 +21,8 @@ import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { getColorClass, getColorStyleElement } from 'vs/workbench/contrib/terminal/browser/terminalIcon';
import { TaskQuickPickEntryType } from 'vs/workbench/contrib/tasks/browser/abstractTaskService';
+import { showWithPinnedItems } from 'vs/platform/quickinput/browser/quickPickPin';
+import { IStorageService } from 'vs/platform/storage/common/storage';
export const QUICKOPEN_DETAIL_CONFIG = 'task.quickOpen.detail';
export const QUICKOPEN_SKIP_CONFIG = 'task.quickOpen.skip';
@@ -42,16 +44,19 @@ const SHOW_ALL: string = nls.localize('taskQuickPick.showAll', "Show All Tasks..
export const configureTaskIcon = registerIcon('tasks-list-configure', Codicon.gear, nls.localize('configureTaskIcon', 'Configuration icon in the tasks selection list.'));
const removeTaskIcon = registerIcon('tasks-remove', Codicon.close, nls.localize('removeTaskIcon', 'Icon for remove in the tasks selection list.'));
+const runTaskStorageKey = 'runTaskStorageKey';
+
export class TaskQuickPick extends Disposable {
private _sorter: TaskSorter;
private _topLevelEntries: QuickPickInput<ITaskTwoLevelQuickPickEntry>[] | undefined;
constructor(
- private _taskService: ITaskService,
- private _configurationService: IConfigurationService,
- private _quickInputService: IQuickInputService,
- private _notificationService: INotificationService,
- private _themeService: IThemeService,
- private _dialogService: IDialogService) {
+ @ITaskService private _taskService: ITaskService,
+ @IConfigurationService private _configurationService: IConfigurationService,
+ @IQuickInputService private _quickInputService: IQuickInputService,
+ @INotificationService private _notificationService: INotificationService,
+ @IThemeService private _themeService: IThemeService,
+ @IDialogService private _dialogService: IDialogService,
+ @IStorageService private _storageService: IStorageService) {
super();
this._sorter = this._taskService.createSorter();
}
@@ -225,8 +230,6 @@ export class TaskQuickPick extends Disposable {
picker.placeholder = placeHolder;
picker.matchOnDescription = true;
picker.ignoreFocusOut = false;
- picker.show();
-
picker.onDidTriggerItemButton(async (context) => {
const task = context.item.task;
if (context.button.iconClass === ThemeIcon.asClassName(removeTaskIcon)) {
@@ -238,7 +241,7 @@ export class TaskQuickPick extends Disposable {
if (indexToRemove >= 0) {
picker.items = [...picker.items.slice(0, indexToRemove), ...picker.items.slice(indexToRemove + 1)];
}
- } else {
+ } else if (context.button.iconClass === ThemeIcon.asClassName(configureTaskIcon)) {
this._quickInputService.cancel();
if (ContributedTask.is(task)) {
this._taskService.customize(task, undefined, true);
@@ -255,6 +258,9 @@ export class TaskQuickPick extends Disposable {
}
}
});
+ if (name) {
+ picker.value = name;
+ }
let firstLevelTask: Task | ConfiguringTask | string | undefined | null = startAtType;
if (!firstLevelTask) {
// First show recent tasks configured tasks. Other tasks will be available at a second level
@@ -264,15 +270,18 @@ export class TaskQuickPick extends Disposable {
return this._toTask(topLevelEntriesResult.isSingleConfigured);
}
const taskQuickPickEntries: QuickPickInput<ITaskTwoLevelQuickPickEntry>[] = topLevelEntriesResult.entries;
- if (name) {
- picker.value = name;
- }
firstLevelTask = await this._doPickerFirstLevel(picker, taskQuickPickEntries);
}
do {
+
if (Types.isString(firstLevelTask)) {
+ if (name) {
+ await this._doPickerFirstLevel(picker, (await this.getTopLevelEntries(defaultEntry)).entries);
+ picker.dispose();
+ return undefined;
+ }
+ const selectedEntry = await this.doPickerSecondLevel(picker, firstLevelTask);
// Proceed to second level of quick pick
- const selectedEntry = await this.doPickerSecondLevel(picker, firstLevelTask, name);
if (selectedEntry && !selectedEntry.settingType && selectedEntry.task === null) {
// The user has chosen to go back to the first level
firstLevelTask = await this._doPickerFirstLevel(picker, (await this.getTopLevelEntries(defaultEntry)).entries);
@@ -299,6 +308,7 @@ export class TaskQuickPick extends Disposable {
private async _doPickerFirstLevel(picker: IQuickPick<ITaskTwoLevelQuickPickEntry>, taskQuickPickEntries: QuickPickInput<ITaskTwoLevelQuickPickEntry>[]): Promise<Task | ConfiguringTask | string | null | undefined> {
picker.items = taskQuickPickEntries;
+ showWithPinnedItems(this._storageService, runTaskStorageKey, picker, true);
const firstLevelPickerResult = await new Promise<ITaskTwoLevelQuickPickEntry | undefined | null>(resolve => {
Event.once(picker.onDidAccept)(async () => {
resolve(picker.selectedItems ? picker.selectedItems[0] : undefined);
@@ -317,7 +327,7 @@ export class TaskQuickPick extends Disposable {
picker.value = name || '';
picker.items = await this._getEntriesForProvider(type);
}
- picker.show();
+ await picker.show();
picker.busy = false;
const secondLevelPickerResult = await new Promise<ITaskTwoLevelQuickPickEntry | undefined | null>(resolve => {
Event.once(picker.onDidAccept)(async () => {
@@ -400,11 +410,4 @@ export class TaskQuickPick extends Disposable {
}
return resolvedTask;
}
-
- static async show(taskService: ITaskService, configurationService: IConfigurationService,
- quickInputService: IQuickInputService, notificationService: INotificationService,
- dialogService: IDialogService, themeService: IThemeService, placeHolder: string, defaultEntry?: ITaskQuickPickEntry, type?: string, name?: string) {
- const taskQuickPick = new TaskQuickPick(taskService, configurationService, quickInputService, notificationService, themeService, dialogService);
- return taskQuickPick.show(placeHolder, defaultEntry, type, name);
- }
}
diff --git a/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts b/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts
index 7c3731d171d..dd605f15b54 100644
--- a/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts
+++ b/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts
@@ -18,6 +18,7 @@ import { isString } from 'vs/base/common/types';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IThemeService } from 'vs/platform/theme/common/themeService';
+import { IStorageService } from 'vs/platform/storage/common/storage';
export class TasksQuickAccessProvider extends PickerQuickAccessProvider<IPickerQuickAccessItem> {
@@ -30,7 +31,8 @@ export class TasksQuickAccessProvider extends PickerQuickAccessProvider<IPickerQ
@IQuickInputService private _quickInputService: IQuickInputService,
@INotificationService private _notificationService: INotificationService,
@IDialogService private _dialogService: IDialogService,
- @IThemeService private _themeService: IThemeService
+ @IThemeService private _themeService: IThemeService,
+ @IStorageService private _storageService: IStorageService
) {
super(TasksQuickAccessProvider.PREFIX, {
noResultsPick: {
@@ -44,7 +46,7 @@ export class TasksQuickAccessProvider extends PickerQuickAccessProvider<IPickerQ
return [];
}
- const taskQuickPick = new TaskQuickPick(this._taskService, this._configurationService, this._quickInputService, this._notificationService, this._themeService, this._dialogService);
+ const taskQuickPick = new TaskQuickPick(this._taskService, this._configurationService, this._quickInputService, this._notificationService, this._themeService, this._dialogService, this._storageService);
const topLevelPicks = await taskQuickPick.getTopLevelEntries();
const taskPicks: Array<IPickerQuickAccessItem | IQuickPickSeparator> = [];
diff --git a/src/vs/workbench/contrib/tasks/electron-sandbox/taskService.ts b/src/vs/workbench/contrib/tasks/electron-sandbox/taskService.ts
index 44eee05cdac..eb0befa657e 100644
--- a/src/vs/workbench/contrib/tasks/electron-sandbox/taskService.ts
+++ b/src/vs/workbench/contrib/tasks/electron-sandbox/taskService.ts
@@ -125,7 +125,8 @@ export class TaskService extends AbstractTaskService {
logService,
themeService,
lifecycleService,
- remoteAgentService
+ remoteAgentService,
+ instantiationService
);
this._register(lifecycleService.onBeforeShutdown(event => event.veto(this.beforeShutdown(), 'veto.tasks')));
}
diff --git a/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts b/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts
index e1efdfa0089..16238ea7baf 100644
--- a/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts
+++ b/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts
@@ -289,8 +289,8 @@ class RemoteTerminalBackend extends BaseTerminalBackend implements ITerminalBack
await this._remoteTerminalChannel?.updateTitle(id, title, titleSource);
}
- async updateIcon(id: number, icon: TerminalIcon, color?: string): Promise<void> {
- await this._remoteTerminalChannel?.updateIcon(id, icon, color);
+ async updateIcon(id: number, userInitiated: boolean, icon: TerminalIcon, color?: string): Promise<void> {
+ await this._remoteTerminalChannel?.updateIcon(id, userInitiated, icon, color);
}
async getDefaultSystemShell(osOverride?: OperatingSystem): Promise<string> {
diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts
index b17009729d6..ee8797508f0 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminal.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts
@@ -144,8 +144,8 @@ export interface ITerminalService extends ITerminalInstanceHost {
onDidMaximumDimensionsChange: Event<ITerminalInstance>;
onDidRequestStartExtensionTerminal: Event<IStartExtensionTerminalRequest>;
onDidChangeInstanceTitle: Event<ITerminalInstance | undefined>;
- onDidChangeInstanceIcon: Event<ITerminalInstance | undefined>;
- onDidChangeInstanceColor: Event<ITerminalInstance | undefined>;
+ onDidChangeInstanceIcon: Event<{ instance: ITerminalInstance; userInitiated: boolean }>;
+ onDidChangeInstanceColor: Event<{ instance: ITerminalInstance; userInitiated: boolean }>;
onDidChangeInstancePrimaryStatus: Event<ITerminalInstance>;
onDidInputInstanceData: Event<ITerminalInstance>;
onDidRegisterProcessSupport: Event<void>;
@@ -524,7 +524,7 @@ export interface ITerminalInstance {
/**
* An event that fires when the terminal instance's icon changes.
*/
- onIconChanged: Event<ITerminalInstance>;
+ onIconChanged: Event<{ instance: ITerminalInstance; userInitiated: boolean }>;
/**
* An event that fires when the terminal instance is disposed.
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts
index 43eae42dee2..eae0e132b05 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts
@@ -319,7 +319,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
readonly onLinksReady = this._onLinksReady.event;
private readonly _onTitleChanged = this._register(new Emitter<ITerminalInstance>());
readonly onTitleChanged = this._onTitleChanged.event;
- private readonly _onIconChanged = this._register(new Emitter<ITerminalInstance>());
+ private readonly _onIconChanged = this._register(new Emitter<{ instance: ITerminalInstance; userInitiated: boolean }>());
readonly onIconChanged = this._onIconChanged.event;
private readonly _onData = this._register(new Emitter<string>());
readonly onData = this._onData.event;
@@ -1464,7 +1464,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
}
if (originalIcon !== this.shellLaunchConfig.icon || this.shellLaunchConfig.color) {
this._icon = this._shellLaunchConfig.attachPersistentProcess?.icon || this._shellLaunchConfig.icon;
- this._onIconChanged.fire(this);
+ this._onIconChanged.fire({ instance: this, userInitiated: false });
}
}
@@ -2211,7 +2211,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
});
if (result) {
this._icon = result.icon;
- this._onIconChanged.fire(this);
+ this._onIconChanged.fire({ instance: this, userInitiated: true });
}
}
@@ -2248,7 +2248,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
if (result) {
this.shellLaunchConfig.color = result.id;
- this._onIconChanged.fire(this);
+ this._onIconChanged.fire({ instance: this, userInitiated: true });
}
quickPick.hide();
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts
index 7e7e9acb703..d4a0e680568 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts
@@ -14,6 +14,7 @@ import { URI } from 'vs/base/common/uri';
import { IKeyMods } from 'vs/base/parts/quickinput/common/quickInput';
import * as nls from 'vs/nls';
import { ICommandService } from 'vs/platform/commands/common/commands';
+import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -38,8 +39,9 @@ import { getInstanceFromResource, getTerminalUri, parseTerminalUri } from 'vs/wo
import { TerminalViewPane } from 'vs/workbench/contrib/terminal/browser/terminalView';
import { IRemoteTerminalAttachTarget, IStartExtensionTerminalRequest, ITerminalBackend, ITerminalConfigHelper, ITerminalProcessExtHostProxy, ITerminalProfileService, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal';
import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey';
+import { columnToEditorGroup } from 'vs/workbench/services/editor/common/editorGroupColumn';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
-import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
+import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { ILifecycleService, ShutdownReason, StartupKind, WillShutdownEvent } from 'vs/workbench/services/lifecycle/common/lifecycle';
@@ -132,10 +134,10 @@ export class TerminalService implements ITerminalService {
get onDidChangeInstances(): Event<void> { return this._onDidChangeInstances.event; }
private readonly _onDidChangeInstanceTitle = new Emitter<ITerminalInstance | undefined>();
get onDidChangeInstanceTitle(): Event<ITerminalInstance | undefined> { return this._onDidChangeInstanceTitle.event; }
- private readonly _onDidChangeInstanceIcon = new Emitter<ITerminalInstance | undefined>();
- get onDidChangeInstanceIcon(): Event<ITerminalInstance | undefined> { return this._onDidChangeInstanceIcon.event; }
- private readonly _onDidChangeInstanceColor = new Emitter<ITerminalInstance | undefined>();
- get onDidChangeInstanceColor(): Event<ITerminalInstance | undefined> { return this._onDidChangeInstanceColor.event; }
+ private readonly _onDidChangeInstanceIcon = new Emitter<{ instance: ITerminalInstance; userInitiated: boolean }>();
+ get onDidChangeInstanceIcon(): Event<{ instance: ITerminalInstance; userInitiated: boolean }> { return this._onDidChangeInstanceIcon.event; }
+ private readonly _onDidChangeInstanceColor = new Emitter<{ instance: ITerminalInstance; userInitiated: boolean }>();
+ get onDidChangeInstanceColor(): Event<{ instance: ITerminalInstance; userInitiated: boolean }> { return this._onDidChangeInstanceColor.event; }
private readonly _onDidChangeActiveInstance = new Emitter<ITerminalInstance | undefined>();
get onDidChangeActiveInstance(): Event<ITerminalInstance | undefined> { return this._onDidChangeActiveInstance.event; }
private readonly _onDidChangeInstancePrimaryStatus = new Emitter<ITerminalInstance>();
@@ -159,6 +161,7 @@ export class TerminalService implements ITerminalService {
@IInstantiationService private _instantiationService: IInstantiationService,
@IRemoteAgentService private _remoteAgentService: IRemoteAgentService,
@IViewsService private _viewsService: IViewsService,
+ @IConfigurationService private readonly _configurationService: IConfigurationService,
@IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService,
@ITerminalEditorService private readonly _terminalEditorService: ITerminalEditorService,
@ITerminalGroupService private readonly _terminalGroupService: ITerminalGroupService,
@@ -498,7 +501,7 @@ export class TerminalService implements ITerminalService {
// terminal ID will be stale and the process will be leaked.
this.onDidReceiveProcessId(() => this._saveState());
this.onDidChangeInstanceTitle(instance => this._updateTitle(instance));
- this.onDidChangeInstanceIcon(instance => this._updateIcon(instance));
+ this.onDidChangeInstanceIcon(e => this._updateIcon(e.instance, e.userInitiated));
}
private _handleInstanceContextKeys(): void {
@@ -673,7 +676,7 @@ export class TerminalService implements ITerminalService {
}
@debounce(500)
- private _updateTitle(instance?: ITerminalInstance): void {
+ private _updateTitle(instance: ITerminalInstance | undefined): void {
if (!this.configHelper.config.enablePersistentSessions || !instance || !instance.persistentProcessId || !instance.title || instance.isDisposed) {
return;
}
@@ -685,11 +688,11 @@ export class TerminalService implements ITerminalService {
}
@debounce(500)
- private _updateIcon(instance?: ITerminalInstance): void {
+ private _updateIcon(instance: ITerminalInstance, userInitiated: boolean): void {
if (!this.configHelper.config.enablePersistentSessions || !instance || !instance.persistentProcessId || !instance.icon || instance.isDisposed) {
return;
}
- this._primaryBackend?.updateIcon(instance.persistentProcessId, instance.icon, instance.color);
+ this._primaryBackend?.updateIcon(instance.persistentProcessId, userInitiated, instance.icon, instance.color);
}
refreshActiveGroup(): void {
@@ -1111,11 +1114,7 @@ export class TerminalService implements ITerminalService {
private _getEditorOptions(location?: ITerminalLocationOptions): TerminalEditorLocation | undefined {
if (location && typeof location === 'object' && 'viewColumn' in location) {
- // When ACTIVE_GROUP is used, resolve it to an actual group to ensure the is created in
- // the active group even if it is locked
- if (location.viewColumn === ACTIVE_GROUP) {
- location.viewColumn = this._editorGroupsService.activeGroup.index;
- }
+ location.viewColumn = columnToEditorGroup(this._editorGroupsService, this._configurationService, location.viewColumn);
return location;
}
return undefined;
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts
index b9690635096..966f327339c 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts
@@ -398,8 +398,8 @@ class SingleTerminalTabActionViewItem extends MenuEntryActionViewItem {
// Register listeners to update the tab
this._register(this._terminalService.onDidChangeInstancePrimaryStatus(e => this.updateLabel(e)));
this._register(this._terminalGroupService.onDidChangeActiveInstance(() => this.updateLabel()));
- this._register(this._terminalService.onDidChangeInstanceIcon(e => this.updateLabel(e)));
- this._register(this._terminalService.onDidChangeInstanceColor(e => this.updateLabel(e)));
+ this._register(this._terminalService.onDidChangeInstanceIcon(e => this.updateLabel(e.instance)));
+ this._register(this._terminalService.onDidChangeInstanceColor(e => this.updateLabel(e.instance)));
this._register(this._terminalService.onDidChangeInstanceTitle(e => {
if (e === this._terminalGroupService.activeInstance) {
this._action.tooltip = getSingleTabTooltip(e, this._terminalService.configHelper.config.tabs.separator);
diff --git a/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts b/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts
index 4568a2a7dcc..377973047c3 100644
--- a/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts
+++ b/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts
@@ -276,8 +276,8 @@ export class RemoteTerminalChannelClient implements IPtyHostController {
return this._channel.call('$updateTitle', [id, title, titleSource]);
}
- updateIcon(id: number, icon: TerminalIcon, color?: string): Promise<string> {
- return this._channel.call('$updateIcon', [id, icon, color]);
+ updateIcon(id: number, userInitiated: boolean, icon: TerminalIcon, color?: string): Promise<string> {
+ return this._channel.call('$updateIcon', [id, userInitiated, icon, color]);
}
refreshProperty<T extends ProcessPropertyType>(id: number, property: T): Promise<IProcessPropertyMap[T]> {
diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts
index 25eb53c77e9..0e68e807b59 100644
--- a/src/vs/workbench/contrib/terminal/common/terminal.ts
+++ b/src/vs/workbench/contrib/terminal/common/terminal.ts
@@ -127,7 +127,7 @@ export interface ITerminalBackend {
getShellEnvironment(): Promise<IProcessEnvironment | undefined>;
setTerminalLayoutInfo(layoutInfo?: ITerminalsLayoutInfoById): Promise<void>;
updateTitle(id: number, title: string, titleSource: TitleEventSource): Promise<void>;
- updateIcon(id: number, icon: TerminalIcon, color?: string): Promise<void>;
+ updateIcon(id: number, userInitiated: boolean, icon: TerminalIcon, color?: string): Promise<void>;
getTerminalLayoutInfo(): Promise<ITerminalsLayoutInfo | undefined>;
reduceConnectionGraceTime(): Promise<void>;
requestDetachInstance(workspaceId: string, instanceId: number): Promise<IProcessDetails | undefined>;
diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts
index 410cb5de5ae..42fe6ff3212 100644
--- a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts
+++ b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts
@@ -131,8 +131,8 @@ class LocalTerminalBackend extends BaseTerminalBackend implements ITerminalBacke
await this._localPtyService.updateTitle(id, title, titleSource);
}
- async updateIcon(id: number, icon: URI | { light: URI; dark: URI } | { id: string; color?: { id: string } }, color?: string): Promise<void> {
- await this._localPtyService.updateIcon(id, icon, color);
+ async updateIcon(id: number, userInitiated: boolean, icon: URI | { light: URI; dark: URI } | { id: string; color?: { id: string } }, color?: string): Promise<void> {
+ await this._localPtyService.updateIcon(id, userInitiated, icon, color);
}
updateProperty<T extends ProcessPropertyType>(id: number, property: ProcessPropertyType, value: IProcessPropertyMap[T]): Promise<void> {
diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts
index 29254634a6d..95646185780 100644
--- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts
+++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts
@@ -277,6 +277,10 @@ export class GettingStartedPage extends EditorPane {
}));
this.recentlyOpened = workspacesService.getRecentlyOpened();
+ this._register(workspacesService.onDidChangeRecentlyOpened(() => {
+ this.recentlyOpened = workspacesService.getRecentlyOpened();
+ rerender();
+ }));
}
// remove when 'workbench.welcomePage.preferReducedMotion' deprecated
diff --git a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts
index 16385a9895a..8d694700fd9 100644
--- a/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts
+++ b/src/vs/workbench/services/configuration/test/browser/configurationService.test.ts
@@ -1435,7 +1435,7 @@ suite('WorkspaceConfigurationService - Profiles', () => {
fileService.registerProvider(Schemas.vscodeUserData, disposables.add(new FileUserDataProvider(ROOT.scheme, fileSystemProvider, Schemas.vscodeUserData, new NullLogService())));
const uriIdentityService = new UriIdentityService(fileService);
const userDataProfilesService = instantiationService.stub(IUserDataProfilesService, new UserDataProfilesService(environmentService, fileService, uriIdentityService, logService));
- userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(toUserDataProfile('custom', joinPath(environmentService.userRoamingDataHome, 'profiles', 'temp')), userDataProfilesService));
+ userDataProfileService = instantiationService.stub(IUserDataProfileService, new UserDataProfileService(toUserDataProfile('custom', 'custom', joinPath(environmentService.userRoamingDataHome, 'profiles', 'temp')), userDataProfilesService));
workspaceService = testObject = disposables.add(new WorkspaceService({ configurationCache: new ConfigurationCache() }, environmentService, userDataProfileService, userDataProfilesService, fileService, remoteAgentService, uriIdentityService, new NullLogService(), new FilePolicyService(environmentService.policyFile, fileService, logService)));
instantiationService.stub(IFileService, fileService);
instantiationService.stub(IWorkspaceContextService, testObject);
@@ -1531,7 +1531,7 @@ suite('WorkspaceConfigurationService - Profiles', () => {
await fileService.writeFile(userDataProfileService.currentProfile.settingsResource, VSBuffer.fromString('{ "configurationService.profiles.applicationSetting": "profileValue", "configurationService.profiles.testSetting": "profileValue" }'));
await testObject.reloadConfiguration();
- const profile = toUserDataProfile('custom2', joinPath(environmentService.userRoamingDataHome, 'profiles', 'custom2'));
+ const profile = toUserDataProfile('custom2', 'custom2', joinPath(environmentService.userRoamingDataHome, 'profiles', 'custom2'));
await fileService.writeFile(profile.settingsResource, VSBuffer.fromString('{ "configurationService.profiles.applicationSetting": "profileValue2", "configurationService.profiles.testSetting": "profileValue2" }'));
const promise = Event.toPromise(testObject.onDidChangeConfiguration);
await userDataProfileService.updateCurrentProfile(profile, false);
diff --git a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts
index 8ff9cecf63b..fabbda76cb7 100644
--- a/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts
+++ b/src/vs/workbench/services/dialogs/browser/abstractFileDialogService.ts
@@ -167,7 +167,7 @@ export abstract class AbstractFileDialogService implements IFileDialogService {
}
protected async pickFileFolderAndOpenSimplified(schema: string, options: IPickAndOpenOptions, preferNewWindow: boolean): Promise<void> {
- const title = nls.localize('openFileOrFolder.title', 'Open File Or Folder');
+ const title = nls.localize('openFileOrFolder.title', 'Open File or Folder');
const availableFileSystems = this.addFileSchemaIfNeeded(schema);
const uri = await this.pickResource({ canSelectFiles: true, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems });
diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts
index e6b24f4013c..3c8446b2b12 100644
--- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts
+++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts
@@ -52,7 +52,6 @@ export const allApiProposals = Object.freeze({
scmActionButton: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmActionButton.d.ts',
scmSelectedProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmSelectedProvider.d.ts',
scmValidation: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.scmValidation.d.ts',
- snippetWorkspaceEdit: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.snippetWorkspaceEdit.d.ts',
tabInputTextMerge: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tabInputTextMerge.d.ts',
taskPresentationGroup: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.taskPresentationGroup.d.ts',
telemetry: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.telemetry.d.ts',
diff --git a/src/vs/workbench/services/languageDetection/common/languageDetectionWorkerService.ts b/src/vs/workbench/services/languageDetection/common/languageDetectionWorkerService.ts
index 36ffefdd42c..d036cb033b1 100644
--- a/src/vs/workbench/services/languageDetection/common/languageDetectionWorkerService.ts
+++ b/src/vs/workbench/services/languageDetection/common/languageDetectionWorkerService.ts
@@ -8,6 +8,8 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
export const ILanguageDetectionService = createDecorator<ILanguageDetectionService>('ILanguageDetectionService');
+export const LanguageDetectionLanguageEventSource = 'languageDetection';
+
export interface ILanguageDetectionService {
readonly _serviceBrand: undefined;
diff --git a/src/vs/workbench/services/search/common/ignoreFile.ts b/src/vs/workbench/services/search/common/ignoreFile.ts
index 769c8aa8273..9de868fdeff 100644
--- a/src/vs/workbench/services/search/common/ignoreFile.ts
+++ b/src/vs/workbench/services/search/common/ignoreFile.ts
@@ -133,7 +133,13 @@ export class IgnoreFile {
line = '**/' + line;
} else {
if (firstSep === 0) {
- line = line.slice(1);
+ if (dirPath.slice(-1) === '/') {
+ line = line.slice(1);
+ }
+ } else {
+ if (dirPath.slice(-1) !== '/') {
+ line = '/' + line;
+ }
}
line = dirPath + line;
}
diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts
index 6672a7126d3..6dc44f932c3 100644
--- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts
+++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts
@@ -197,8 +197,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
this.modelService.setMode(this.textEditorModel, languageSelection);
}
- override setLanguageId(languageId: string): void {
- super.setLanguageId(languageId);
+ override setLanguageId(languageId: string, source?: string): void {
+ super.setLanguageId(languageId, source);
this.preferredLanguageId = languageId;
}
@@ -556,15 +556,16 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
}
}
- private installModelListeners(model: ITextModel): void {
+ protected override installModelListeners(model: ITextModel): void {
// See https://github.com/microsoft/vscode/issues/30189
// This code has been extracted to a different method because it caused a memory leak
// where `value` was captured in the content change listener closure scope.
- // Listen to text model events
this._register(model.onDidChangeContent(e => this.onModelContentChanged(model, e.isUndoing || e.isRedoing)));
this._register(model.onDidChangeLanguage(() => this.onMaybeShouldChangeEncoding())); // detect possible encoding change via language specific settings
+
+ super.installModelListeners(model);
}
private onModelContentChanged(model: ITextModel, isUndoingOrRedoing: boolean): void {
diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts
index 8cb717b2fca..e961ed75e2f 100644
--- a/src/vs/workbench/services/textfile/common/textfiles.ts
+++ b/src/vs/workbench/services/textfile/common/textfiles.ts
@@ -468,7 +468,7 @@ export interface ILanguageSupport {
/**
* Sets the language id of the object.
*/
- setLanguageId(languageId: string, setExplicitly?: boolean): void;
+ setLanguageId(languageId: string, source?: string): void;
}
export interface ITextFileEditorModelSaveEvent extends IWorkingCopySaveEvent {
diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts
index 326624d3ae0..c3d68a86cf7 100644
--- a/src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts
+++ b/src/vs/workbench/services/untitled/common/untitledTextEditorInput.ts
@@ -108,8 +108,8 @@ export class UntitledTextEditorInput extends AbstractTextResourceEditorInput imp
return this.model.setEncoding(encoding);
}
- setLanguageId(languageId: string): void {
- this.model.setLanguageId(languageId);
+ setLanguageId(languageId: string, source?: string): void {
+ this.model.setLanguageId(languageId, source);
}
getLanguageId(): string | undefined {
diff --git a/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts b/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts
index 5d3e956cadb..1d3657cf103 100644
--- a/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts
+++ b/src/vs/workbench/services/untitled/common/untitledTextEditorModel.ts
@@ -190,14 +190,14 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt
//#region Language
- override setLanguageId(languageId: string): void {
+ override setLanguageId(languageId: string, source?: string): void {
const actualLanguage: string | undefined = languageId === UntitledTextEditorModel.ACTIVE_EDITOR_LANGUAGE_ID
? this.editorService.activeTextEditorLanguageId
: languageId;
this.preferredLanguageId = actualLanguage;
if (actualLanguage) {
- super.setLanguageId(actualLanguage);
+ super.setLanguageId(actualLanguage, source);
}
}
@@ -333,8 +333,7 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt
// Listen to text model events
const textEditorModel = assertIsDefined(this.textEditorModel);
- this._register(textEditorModel.onDidChangeContent(e => this.onModelContentChanged(textEditorModel, e)));
- this._register(textEditorModel.onDidChangeLanguage(() => this.onConfigurationChange(true))); // language change can have impact on config
+ this.installModelListeners(textEditorModel);
// Only adjust name and dirty state etc. if we
// actually created the untitled model
@@ -358,6 +357,13 @@ export class UntitledTextEditorModel extends BaseTextEditorModel implements IUnt
return super.resolve();
}
+ protected override installModelListeners(model: ITextModel): void {
+ this._register(model.onDidChangeContent(e => this.onModelContentChanged(model, e)));
+ this._register(model.onDidChangeLanguage(() => this.onConfigurationChange(true))); // language change can have impact on config
+
+ super.installModelListeners(model);
+ }
+
private onModelContentChanged(textEditorModel: ITextModel, e: IModelContentChangedEvent): void {
// mark the untitled text editor as non-dirty once its content becomes empty and we do
diff --git a/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts b/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts
index 2ab69b9299f..b9bd7078da9 100644
--- a/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts
+++ b/src/vs/workbench/services/untitled/test/browser/untitledTextEditor.test.ts
@@ -20,6 +20,7 @@ import { EditorInputCapabilities } from 'vs/workbench/common/editor';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { isReadable, isReadableStream } from 'vs/base/common/stream';
import { readableToBuffer, streamToBuffer, VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer';
+import { LanguageDetectionLanguageEventSource } from 'vs/workbench/services/languageDetection/common/languageDetectionWorkerService';
suite('Untitled text editors', () => {
@@ -334,6 +335,55 @@ suite('Untitled text editors', () => {
registration.dispose();
});
+ // Issue #159202
+ test('remembers that language was set explicitly if set by another source (i.e. ModelService)', async () => {
+ const language = 'untitled-input-test';
+
+ const registration = accessor.languageService.registerLanguage({
+ id: language,
+ });
+
+ const service = accessor.untitledTextEditorService;
+ const model = service.create();
+ const input = instantiationService.createInstance(UntitledTextEditorInput, model);
+ await input.resolve();
+
+ assert.ok(!input.model.hasLanguageSetExplicitly);
+ accessor.modelService.setMode(model.textEditorModel!, accessor.languageService.createById(language));
+ assert.ok(input.model.hasLanguageSetExplicitly);
+
+ assert.strictEqual(model.getLanguageId(), language);
+
+ model.dispose();
+ registration.dispose();
+ });
+
+ test('Language is not set explicitly if set by language detection source', async () => {
+ const language = 'untitled-input-test';
+
+ const registration = accessor.languageService.registerLanguage({
+ id: language,
+ });
+
+ const service = accessor.untitledTextEditorService;
+ const model = service.create();
+ const input = instantiationService.createInstance(UntitledTextEditorInput, model);
+ await input.resolve();
+
+ assert.ok(!input.model.hasLanguageSetExplicitly);
+ accessor.modelService.setMode(
+ model.textEditorModel!,
+ accessor.languageService.createById(language),
+ // This is really what this is testing
+ LanguageDetectionLanguageEventSource);
+ assert.ok(!input.model.hasLanguageSetExplicitly);
+
+ assert.strictEqual(model.getLanguageId(), language);
+
+ model.dispose();
+ registration.dispose();
+ });
+
test('service#onDidChangeEncoding', async () => {
const service = accessor.untitledTextEditorService;
const input = instantiationService.createInstance(UntitledTextEditorInput, service.create());
diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts
index e9cd97c1bc0..1e8af9e7fb7 100644
--- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts
+++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts
@@ -53,7 +53,7 @@ export class UserDataProfileManagementService extends Disposable implements IUse
}
async createAndEnterProfile(name: string, useDefaultFlags?: UseDefaultProfileFlags, fromExisting?: boolean): Promise<IUserDataProfile> {
- const profile = await this.userDataProfilesService.createProfile(name, useDefaultFlags, this.getWorkspaceIdentifier());
+ const profile = await this.userDataProfilesService.createNamedProfile(name, useDefaultFlags, this.getWorkspaceIdentifier());
await this.enterProfile(profile, !!fromExisting);
return profile;
}
diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts
index e0465423c62..b955a8c3efb 100644
--- a/src/vs/workbench/test/browser/workbenchTestServices.ts
+++ b/src/vs/workbench/test/browser/workbenchTestServices.ts
@@ -1608,7 +1608,7 @@ export class TestFileEditorInput extends EditorInput implements IFileEditorInput
setPreferredDescription(description: string): void { }
setPreferredEncoding(encoding: string) { }
setPreferredContents(contents: string): void { }
- setLanguageId(languageId: string) { }
+ setLanguageId(languageId: string, source?: string) { }
setPreferredLanguageId(languageId: string) { }
setForceOpenAsBinary(): void { }
setFailToOpen(): void {
@@ -2010,7 +2010,7 @@ export class TestUserDataProfileService implements IUserDataProfileService {
readonly _serviceBrand: undefined;
readonly onDidUpdateCurrentProfile = Event.None;
readonly onDidChangeCurrentProfile = Event.None;
- readonly currentProfile = toUserDataProfile('test', URI.file('tests').with({ scheme: 'vscode-tests' }));
+ readonly currentProfile = toUserDataProfile('test', 'test', URI.file('tests').with({ scheme: 'vscode-tests' }));
async updateCurrentProfile(): Promise<void> { }
}
diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts
index 2dad23406e6..d1c831212b1 100644
--- a/src/vscode-dts/vscode.d.ts
+++ b/src/vscode-dts/vscode.d.ts
@@ -2276,6 +2276,18 @@ declare module 'vscode' {
static readonly RefactorInline: CodeActionKind;
/**
+ * Base kind for refactoring move actions: `refactor.move`
+ *
+ * Example move actions:
+ *
+ * - Move a function to a new file
+ * - Move a property between classes
+ * - Move method to base class
+ * - ...
+ */
+ static readonly RefactorMove: CodeActionKind;
+
+ /**
* Base kind for refactoring rewrite actions: `refactor.rewrite`
*
* Example rewrite actions:
@@ -2284,7 +2296,6 @@ declare module 'vscode' {
* - Add or remove parameter
* - Encapsulate field
* - Make method static
- * - Move method to base class
* - ...
*/
static readonly RefactorRewrite: CodeActionKind;
@@ -3427,6 +3438,54 @@ declare module 'vscode' {
}
/**
+ * A snippet edit represents an interactive edit that is performed by
+ * the editor.
+ *
+ * *Note* that a snippet edit can always be performed as a normal {@link TextEdit text edit}.
+ * This will happen when no matching editor is open or when a {@link WorkspaceEdit workspace edit}
+ * contains snippet edits for multiple files. In that case only those that match the active editor
+ * will be performed as snippet edits and the others as normal text edits.
+ */
+ export class SnippetTextEdit {
+
+ /**
+ * Utility to create a replace snippet edit.
+ *
+ * @param range A range.
+ * @param snippet A snippet string.
+ * @return A new snippet edit object.
+ */
+ static replace(range: Range, snippet: SnippetString): SnippetTextEdit;
+
+ /**
+ * Utility to create an insert snippet edit.
+ *
+ * @param position A position, will become an empty range.
+ * @param snippet A snippet string.
+ * @return A new snippet edit object.
+ */
+ static insert(position: Position, snippet: SnippetString): SnippetTextEdit;
+
+ /**
+ * The range this edit applies to.
+ */
+ range: Range;
+
+ /**
+ * The {@link SnippetString snippet} this edit will perform.
+ */
+ snippet: SnippetString;
+
+ /**
+ * Create a new snippet edit.
+ *
+ * @param range A range.
+ * @param snippet A snippet string.
+ */
+ constructor(range: Range, snippet: SnippetString);
+ }
+
+ /**
* A notebook edit represents edits that should be applied to the contents of a notebook.
*/
export class NotebookEdit {
@@ -3571,12 +3630,36 @@ declare module 'vscode' {
has(uri: Uri): boolean;
/**
- * Set (and replace) text edits for a resource.
+ * Set (and replace) notebook edits for a resource.
+ *
+ * @param uri A resource identifier.
+ * @param edits An array of edits.
+ */
+ set(uri: Uri, edits: NotebookEdit[]): void;
+
+ /**
+ * Set (and replace) notebook edits with metadata for a resource.
+ *
+ * @param uri A resource identifier.
+ * @param edits An array of edits.
+ */
+ set(uri: Uri, edits: [NotebookEdit, WorkspaceEditEntryMetadata][]): void;
+
+ /**
+ * Set (and replace) text edits or snippet edits for a resource.
+ *
+ * @param uri A resource identifier.
+ * @param edits An array of edits.
+ */
+ set(uri: Uri, edits: (TextEdit | SnippetTextEdit)[]): void;
+
+ /**
+ * Set (and replace) text edits or snippet edits with metadata for a resource.
*
* @param uri A resource identifier.
* @param edits An array of edits.
*/
- set(uri: Uri, edits: TextEdit[] | NotebookEdit[]): void;
+ set(uri: Uri, edits: [TextEdit | SnippetTextEdit, WorkspaceEditEntryMetadata][]): void;
/**
* Get the text edits for a resource.
diff --git a/src/vscode-dts/vscode.proposed.notebookContentProvider.d.ts b/src/vscode-dts/vscode.proposed.notebookContentProvider.d.ts
index b1e9211ceb2..d336c2ccf54 100644
--- a/src/vscode-dts/vscode.proposed.notebookContentProvider.d.ts
+++ b/src/vscode-dts/vscode.proposed.notebookContentProvider.d.ts
@@ -8,29 +8,6 @@ declare module 'vscode' {
// https://github.com/microsoft/vscode/issues/147248
/** @deprecated */
- interface NotebookDocumentBackup {
- /**
- * Unique identifier for the backup.
- *
- * This id is passed back to your extension in `openNotebook` when opening a notebook editor from a backup.
- */
- readonly id: string;
-
- /**
- * Delete the current backup.
- *
- * This is called by the editor when it is clear the current backup is no longer needed, such as when a new backup
- * is made or when the file is saved.
- */
- delete(): void;
- }
-
- /** @deprecated */
- interface NotebookDocumentBackupContext {
- readonly destination: Uri;
- }
-
- /** @deprecated */
interface NotebookDocumentOpenContext {
readonly backupId?: string;
readonly untitledDocumentData?: Uint8Array;
@@ -39,26 +16,13 @@ declare module 'vscode' {
// todo@API use openNotebookDOCUMENT to align with openCustomDocument etc?
// todo@API rename to NotebookDocumentContentProvider
/** @deprecated */
-
export interface NotebookContentProvider {
- readonly options?: NotebookDocumentContentOptions;
- readonly onDidChangeNotebookContentOptions?: Event<NotebookDocumentContentOptions>;
-
/**
* Content providers should always use {@link FileSystemProvider file system providers} to
* resolve the raw content for `uri` as the resource is not necessarily a file on disk.
*/
openNotebook(uri: Uri, openContext: NotebookDocumentOpenContext, token: CancellationToken): NotebookData | Thenable<NotebookData>;
-
- // todo@API use NotebookData instead
- saveNotebook(document: NotebookDocument, token: CancellationToken): Thenable<void>;
-
- // todo@API use NotebookData instead
- saveNotebookAs(targetResource: Uri, document: NotebookDocument, token: CancellationToken): Thenable<void>;
-
- // todo@API use NotebookData instead
- backupNotebook(document: NotebookDocument, context: NotebookDocumentBackupContext, token: CancellationToken): Thenable<NotebookDocumentBackup>;
}
export namespace workspace {
diff --git a/src/vscode-dts/vscode.proposed.notebookLiveShare.d.ts b/src/vscode-dts/vscode.proposed.notebookLiveShare.d.ts
index c78f1f7a3b7..986f7fac85c 100644
--- a/src/vscode-dts/vscode.proposed.notebookLiveShare.d.ts
+++ b/src/vscode-dts/vscode.proposed.notebookLiveShare.d.ts
@@ -14,8 +14,12 @@ declare module 'vscode' {
}
export namespace workspace {
- // SPECIAL overload with NotebookRegistrationData
+ /**
+ * SPECIAL overload with NotebookRegistrationData
+ * @deprecated
+ */
export function registerNotebookContentProvider(notebookType: string, provider: NotebookContentProvider, options?: NotebookDocumentContentOptions, registrationData?: NotebookRegistrationData): Disposable;
+
// SPECIAL overload with NotebookRegistrationData
export function registerNotebookSerializer(notebookType: string, serializer: NotebookSerializer, options?: NotebookDocumentContentOptions, registration?: NotebookRegistrationData): Disposable;
}
diff --git a/src/vscode-dts/vscode.proposed.snippetWorkspaceEdit.d.ts b/src/vscode-dts/vscode.proposed.snippetWorkspaceEdit.d.ts
deleted file mode 100644
index eb67ff251ba..00000000000
--- a/src/vscode-dts/vscode.proposed.snippetWorkspaceEdit.d.ts
+++ /dev/null
@@ -1,85 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-declare module 'vscode' {
-
- // https://github.com/microsoft/vscode/issues/145374
-
- // TODO@API - WorkspaceEditEntryMetadata
-
- export class SnippetTextEdit {
-
- /**
- * Utility to create an replace snippet edit.
- *
- * @param range A range.
- * @param snippet A snippet string.
- * @return A new snippet edit object.
- */
- static replace(range: Range, snippet: SnippetString): SnippetTextEdit;
-
- /**
- * Utility to create an insert snippet edit.
- *
- * @param position A position, will become an empty range.
- * @param snippet A snippet string.
- * @return A new snippet edit object.
- */
- static insert(position: Position, snippet: SnippetString): SnippetTextEdit;
-
- /**
- * The range this edit applies to.
- */
- range: Range;
-
- /**
- * The {@link SnippetString snippet} this edit will perform.
- */
- snippet: SnippetString;
-
- /**
- * Create a new snippet edit.
- *
- * @param range A range.
- * @param snippet A snippet string.
- */
- constructor(range: Range, snippet: SnippetString);
- }
-
- interface WorkspaceEdit {
-
- /**
- * Set (and replace) notebook edits for a resource.
- *
- * @param uri A resource identifier.
- * @param edits An array of edits.
- */
- set(uri: Uri, edits: NotebookEdit[]): void;
-
- /**
- * Set (and replace) notebook edits with metadata for a resource.
- *
- * @param uri A resource identifier.
- * @param edits An array of edits.
- */
- set(uri: Uri, edits: [NotebookEdit, WorkspaceEditEntryMetadata][]): void;
-
- /**
- * Set (and replace) text edits or snippet edits for a resource.
- *
- * @param uri A resource identifier.
- * @param edits An array of edits.
- */
- set(uri: Uri, edits: (TextEdit | SnippetTextEdit)[]): void;
-
- /**
- * Set (and replace) text edits or snippet edits with metadata for a resource.
- *
- * @param uri A resource identifier.
- * @param edits An array of edits.
- */
- set(uri: Uri, edits: [TextEdit | SnippetTextEdit, WorkspaceEditEntryMetadata][]): void;
- }
-}
diff --git a/src/vscode-dts/vscode.proposed.treeItemCheckbox.d.ts b/src/vscode-dts/vscode.proposed.treeItemCheckbox.d.ts
index a1c5cb1df12..597e95db9da 100644
--- a/src/vscode-dts/vscode.proposed.treeItemCheckbox.d.ts
+++ b/src/vscode-dts/vscode.proposed.treeItemCheckbox.d.ts
@@ -9,7 +9,7 @@ declare module 'vscode' {
/**
* [TreeItemCheckboxState](#TreeItemCheckboxState) of the tree item.
*/
- checkboxState?: TreeItemCheckboxState;
+ checkboxState?: TreeItemCheckboxState | { state: TreeItemCheckboxState; tooltip?: string };
}
/**
diff --git a/yarn.lock b/yarn.lock
index 83cc7d0a1ed..4c8f3c14e8d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -11499,10 +11499,10 @@ windows-mutex@0.4.1:
bindings "^1.2.1"
nan "^2.14.0"
-windows-process-tree@0.3.3:
- version "0.3.3"
- resolved "https://registry.yarnpkg.com/windows-process-tree/-/windows-process-tree-0.3.3.tgz#7c178815f02bf4cfbcac1f93b2f3a3cc10bc9245"
- integrity sha512-rkiAMP0AS27xikFyn7i4gPbOK16UdjY8X/C6eo37CnfNLqTvK2eEaT+Dh0e5xnvmlsi0lEKd60O+4ajzfDkq7A==
+windows-process-tree@0.3.4:
+ version "0.3.4"
+ resolved "https://registry.yarnpkg.com/windows-process-tree/-/windows-process-tree-0.3.4.tgz#6bc4b8010129c30ff95bcd333b9f94744dd3c4fb"
+ integrity sha512-rtSX73i9OnkDxSdBP9c1YBunEwheZdO/hjRwNk9uSoWqO92x0zDRGfIIK0MtUn8gZZD+2kPEVpj5MmfNl7JpJA==
dependencies:
nan "^2.13.2"
@@ -11628,40 +11628,40 @@ xtend@~2.1.1:
dependencies:
object-keys "~0.4.0"
-xterm-addon-canvas@0.2.0-beta.23:
- version "0.2.0-beta.23"
- resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.2.0-beta.23.tgz#f5ee0db3b029ea705ef3c1228825c28ec6368b48"
- integrity sha512-414qLxMlOzC3LyAt1qHmvrcW2VIPAsFQkXTGcSzX42XCOTF4lA9Jf8ePVNgokQAyvlGK3j3K0y0d7lTTR5I/Zw==
+xterm-addon-canvas@0.2.0-beta.26:
+ version "0.2.0-beta.26"
+ resolved "https://registry.yarnpkg.com/xterm-addon-canvas/-/xterm-addon-canvas-0.2.0-beta.26.tgz#db6d134177bac58d24e02d11c123f0cefb0e95b9"
+ integrity sha512-OZctolm/iUjSG11iYERJSu9ax2GBXe96ASYcHfJAeq19IMHadQvD3AWaJl25/MMChmvJ0qT1Q/+6p0ElgfV77Q==
-xterm-addon-search@0.10.0-beta.6:
- version "0.10.0-beta.6"
- resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.10.0-beta.6.tgz#a475d793a13b378f56b439b8c7eeeff2095831ae"
- integrity sha512-fDS0dbM/ZuVBfieWyXJgFvQwNk95rpVbaBRcVpUM9sM/R5+ePQr+uhcaicfuWAku7urP7P/QNnkeAkeQjf8E6w==
+xterm-addon-search@0.10.0-beta.7:
+ version "0.10.0-beta.7"
+ resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.10.0-beta.7.tgz#77812514c4aa84668d9e247a9172618ccd2517d7"
+ integrity sha512-58dFGbLQc3C0Iww/Jq65HcXC9/RL+57duY5+rijts6KBZqAlGQCN3f2ORFKRvJEQDTgxOcnK9o9welyKK+PQ3Q==
-xterm-addon-serialize@0.8.0-beta.6:
- version "0.8.0-beta.6"
- resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.8.0-beta.6.tgz#fe21a74a0ca3ecdf12843136115f074a431b3876"
- integrity sha512-hb3TRqvg36MW5H4ZnYjw4EHb55iZ4rOOuH+Hx4ZTBDI1pszPtryFqXbS93NBLKgsOqDovIDsH8fWvNfhPdGmsQ==
+xterm-addon-serialize@0.8.0-beta.7:
+ version "0.8.0-beta.7"
+ resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.8.0-beta.7.tgz#73a71834a687c825ff3d3c824229fbd856d1570f"
+ integrity sha512-cghmB/2DYwX4HvjGMWmbxYO3NrvgfYWrQt0QGb0oToZh1gOgoEkUxZVZiOl5WlqFYpI+jHXXX48XgfFONZ1rMA==
xterm-addon-unicode11@0.4.0-beta.5:
version "0.4.0-beta.5"
resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.4.0-beta.5.tgz#3900e66f10d2e506133b61d7421aab6878d32665"
integrity sha512-+g+fuxAd/tkCEJ/jhdnebXKtdPrhsu4VKWNnB/3qM35GbuGQOasmYFYnKL+HYZMpbQ6YqeZcXTVg/wnCTttz0g==
-xterm-addon-webgl@0.13.0-beta.49:
- version "0.13.0-beta.49"
- resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.13.0-beta.49.tgz#0cbffccccec06f5638ddc793ae7a0ff8ce0a891b"
- integrity sha512-c1/8hLrw3PuPAnyPVLNg8i2FDkyu5SkU654DPEEgKgHHeAh3sfil28LleBpPhpP24531i7XNt1LLHCGMJ+gkFw==
+xterm-addon-webgl@0.13.0-beta.55:
+ version "0.13.0-beta.55"
+ resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.13.0-beta.55.tgz#d116fbb8d2e2bbfa562876f90d1aeedf48cc7eb8"
+ integrity sha512-i595z+lcbJaxLM7WTk845440lfyc3RERn/yWqTql+gnoA1YoP3gAnl/qdluFrKndM8sQGWmCsz9qACANXRjLbA==
-xterm-headless@5.0.0-beta.5:
- version "5.0.0-beta.5"
- resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-5.0.0-beta.5.tgz#e29b6c5081f31f887122b7263ba996b0c46b3c22"
- integrity sha512-CMQ1+prBNF92oBMeZzc2rfTcmOaCGfwwSaoPYNTjyziZT6mZsEg7amajYkb0YAnqJ29MFm4kPGZbU78/dX4k2A==
+xterm-headless@4.20.0-beta.74:
+ version "4.20.0-beta.74"
+ resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.20.0-beta.74.tgz#1eade8cdfbf4389cadf0ae8b8b2cb536862323d2"
+ integrity sha512-WwHcSrnHGbqcRKJTDJgEJT4y4X5KPJxcMbi5RGj/T1FoXg/uYU23DO1RtvJV8ZnRKLbcY/Ru0wWf7ZGDrEk1DA==
-xterm@5.0.0-beta.54:
- version "5.0.0-beta.54"
- resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.0.0-beta.54.tgz#2c353221f289af22327aae6318bc6422c636fd41"
- integrity sha512-wRzs1NbVCkZUzAqvglQcDVreT7RLLFkpdBi0oOLbZXgTaYr/Be93aCuuEjOVp7lnV0hi1gEP5K9Ugn621QffNw==
+xterm@5.0.0-beta.60:
+ version "5.0.0-beta.60"
+ resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.0.0-beta.60.tgz#1d16d6828f125c18c6d6e7db8769d1d848707a35"
+ integrity sha512-wkMXXfmwF9jIBtjSoEy7nyh54lDJz4wE0CuYzyBP/cjbTnjAkheeZcY9cJBlDRtP4NoZ7EhsA9GyXNeIrviiJg==
y18n@^3.2.1:
version "3.2.2"