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:
authorJohannes Rieken <johannes.rieken@gmail.com>2022-07-19 16:47:57 +0300
committerGitHub <noreply@github.com>2022-07-19 16:47:57 +0300
commit44e25ba96e244b10b1021e04b392bc761ea61d62 (patch)
tree13f1135724989f5997bf18013f60d211193d6469 /src/vs/workbench
parent78693265be2fa394330df097c3fd99925dff21d4 (diff)
parent34f1bc679dadbd07104234ab03eacdd7b8d157f0 (diff)
Merge branch 'main' into joh/cellUri
Diffstat (limited to 'src/vs/workbench')
-rw-r--r--src/vs/workbench/api/browser/mainThreadEditorTabs.ts11
-rw-r--r--src/vs/workbench/api/browser/mainThreadTesting.ts9
-rw-r--r--src/vs/workbench/api/common/extHost.api.impl.ts2
-rw-r--r--src/vs/workbench/api/common/extHost.protocol.ts11
-rw-r--r--src/vs/workbench/api/common/extHostEditorTabs.ts8
-rw-r--r--src/vs/workbench/api/common/extHostSCM.ts3
-rw-r--r--src/vs/workbench/api/common/extHostTypes.ts92
-rw-r--r--src/vs/workbench/api/node/extHostCLIServer.ts5
-rw-r--r--src/vs/workbench/api/test/browser/extHostEditorTabs.test.ts33
-rw-r--r--src/vs/workbench/api/test/browser/mainThreadEditors.test.ts4
-rw-r--r--src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts2
-rw-r--r--src/vs/workbench/browser/actions/layoutActions.ts3
-rw-r--r--src/vs/workbench/browser/actions/listCommands.ts54
-rw-r--r--src/vs/workbench/browser/labels.ts21
-rw-r--r--src/vs/workbench/browser/layout.ts91
-rw-r--r--src/vs/workbench/browser/part.ts2
-rw-r--r--src/vs/workbench/browser/parts/activitybar/activitybarActions.ts5
-rw-r--r--src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts2
-rw-r--r--src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts8
-rw-r--r--src/vs/workbench/browser/parts/editor/editorCommands.ts8
-rw-r--r--src/vs/workbench/browser/parts/editor/editorDropTarget.ts2
-rw-r--r--src/vs/workbench/browser/parts/editor/editorGroupView.ts2
-rw-r--r--src/vs/workbench/browser/parts/editor/editorWithViewState.ts4
-rw-r--r--src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts18
-rw-r--r--src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css3
-rw-r--r--src/vs/workbench/browser/parts/titlebar/menubarControl.ts16
-rw-r--r--src/vs/workbench/browser/parts/titlebar/titlebarPart.ts140
-rw-r--r--src/vs/workbench/browser/parts/views/viewPane.ts5
-rw-r--r--src/vs/workbench/browser/workbench.contribution.ts11
-rw-r--r--src/vs/workbench/common/editor.ts68
-rw-r--r--src/vs/workbench/common/editor/editorInput.ts6
-rw-r--r--src/vs/workbench/common/editor/sideBySideEditorInput.ts10
-rw-r--r--src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts9
-rw-r--r--src/vs/workbench/contrib/codeEditor/browser/untitledTextEditorHint.ts106
-rw-r--r--src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts9
-rw-r--r--src/vs/workbench/contrib/debug/browser/debugCommands.ts44
-rw-r--r--src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts18
-rw-r--r--src/vs/workbench/contrib/debug/browser/debugHover.ts2
-rw-r--r--src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts2
-rw-r--r--src/vs/workbench/contrib/debug/browser/debugService.ts6
-rw-r--r--src/vs/workbench/contrib/debug/browser/debugViewlet.ts2
-rw-r--r--src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts5
-rw-r--r--src/vs/workbench/contrib/debug/common/debug.ts2
-rw-r--r--src/vs/workbench/contrib/deprecatedExtensionMigrator/browser/deprecatedExtensionMigrator.contribution.ts103
-rw-r--r--src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts36
-rw-r--r--src/vs/workbench/contrib/experiments/common/experimentService.ts3
-rw-r--r--src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts21
-rw-r--r--src/vs/workbench/contrib/extensions/browser/extensionEditor.ts5
-rw-r--r--src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts20
-rw-r--r--src/vs/workbench/contrib/extensions/browser/extensionsActions.ts50
-rw-r--r--src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts6
-rw-r--r--src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts7
-rw-r--r--src/vs/workbench/contrib/extensions/browser/media/extensionActions.css1
-rw-r--r--src/vs/workbench/contrib/files/browser/fileCommands.ts9
-rw-r--r--src/vs/workbench/contrib/files/browser/files.contribution.ts2
-rw-r--r--src/vs/workbench/contrib/files/browser/views/explorerView.ts44
-rw-r--r--src/vs/workbench/contrib/format/browser/formatActionsNone.ts5
-rw-r--r--src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts10
-rw-r--r--src/vs/workbench/contrib/list/browser/list.contribution.ts12
-rw-r--r--src/vs/workbench/contrib/markers/browser/markersView.ts6
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts34
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts3
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts9
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts59
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts2
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts12
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts69
-rw-r--r--src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.css7
-rw-r--r--src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts78
-rw-r--r--src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts4
-rw-r--r--src/vs/workbench/contrib/notebook/browser/controller/editActions.ts37
-rw-r--r--src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts52
-rw-r--r--src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts2
-rw-r--r--src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts16
-rw-r--r--src/vs/workbench/contrib/notebook/browser/media/notebookToolbar.css4
-rw-r--r--src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts1
-rw-r--r--src/vs/workbench/contrib/notebook/browser/notebookEditor.ts33
-rw-r--r--src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts19
-rw-r--r--src/vs/workbench/contrib/notebook/browser/notebookExecutionStateServiceImpl.ts36
-rw-r--r--src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts45
-rw-r--r--src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts16
-rw-r--r--src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts2
-rw-r--r--src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts13
-rw-r--r--src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts1
-rw-r--r--src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts6
-rw-r--r--src/vs/workbench/contrib/notebook/common/notebookExecutionStateService.ts6
-rw-r--r--src/vs/workbench/contrib/notebook/common/notebookPerformance.ts36
-rw-r--r--src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts9
-rw-r--r--src/vs/workbench/contrib/outline/browser/outlinePane.ts13
-rw-r--r--src/vs/workbench/contrib/preferences/browser/media/settingsWidgets.css3
-rw-r--r--src/vs/workbench/contrib/preferences/browser/settingsTree.ts6
-rw-r--r--src/vs/workbench/contrib/preferences/browser/tocTree.ts7
-rw-r--r--src/vs/workbench/contrib/remote/browser/remote.ts38
-rw-r--r--src/vs/workbench/contrib/remote/common/remote.contribution.ts10
-rw-r--r--src/vs/workbench/contrib/remote/electron-sandbox/remote.contribution.ts4
-rw-r--r--src/vs/workbench/contrib/snippets/browser/commands/abstractSnippetsActions.ts29
-rw-r--r--src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts (renamed from src/vs/workbench/contrib/snippets/browser/configureSnippets.ts)29
-rw-r--r--src/vs/workbench/contrib/snippets/browser/commands/emptyFileSnippets.ts116
-rw-r--r--src/vs/workbench/contrib/snippets/browser/commands/insertSnippet.ts (renamed from src/vs/workbench/contrib/snippets/browser/insertSnippet.ts)37
-rw-r--r--src/vs/workbench/contrib/snippets/browser/commands/surroundWithSnippet.ts159
-rw-r--r--src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts20
-rw-r--r--src/vs/workbench/contrib/snippets/browser/snippetPicker.ts4
-rw-r--r--src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts61
-rw-r--r--src/vs/workbench/contrib/snippets/browser/snippets.ts33
-rw-r--r--src/vs/workbench/contrib/snippets/browser/snippetsFile.ts34
-rw-r--r--src/vs/workbench/contrib/snippets/browser/snippetsService.ts128
-rw-r--r--src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts60
-rw-r--r--src/vs/workbench/contrib/snippets/browser/tabCompletion.ts2
-rw-r--r--src/vs/workbench/contrib/snippets/test/browser/snippetFile.test.ts21
-rw-r--r--src/vs/workbench/contrib/snippets/test/browser/snippetsRewrite.test.ts5
-rw-r--r--src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts127
-rw-r--r--src/vs/workbench/contrib/tags/electron-sandbox/workspaceTags.ts2
-rw-r--r--src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts8
-rw-r--r--src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts150
-rw-r--r--src/vs/workbench/contrib/tasks/browser/task.contribution.ts6
-rw-r--r--src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts15
-rw-r--r--src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts190
-rw-r--r--src/vs/workbench/contrib/tasks/common/taskService.ts1
-rw-r--r--src/vs/workbench/contrib/tasks/common/taskSystem.ts1
-rw-r--r--src/vs/workbench/contrib/tasks/common/tasks.ts3
-rw-r--r--src/vs/workbench/contrib/tasks/electron-sandbox/taskService.ts7
-rw-r--r--src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts53
-rw-r--r--src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts2
-rw-r--r--src/vs/workbench/contrib/terminal/browser/media/terminal.css4
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminal.ts8
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts4
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalInstance.ts5
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts9
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalService.ts19
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalView.ts33
-rw-r--r--src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts208
-rw-r--r--src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts19
-rw-r--r--src/vs/workbench/contrib/terminal/test/browser/xterm/decorationAddon.test.ts9
-rw-r--r--src/vs/workbench/contrib/testing/browser/testingExplorerView.ts1
-rw-r--r--src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts3
-rw-r--r--src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts2
-rw-r--r--src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts22
-rw-r--r--src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts19
-rw-r--r--src/vs/workbench/electron-sandbox/desktop.contribution.ts4
-rw-r--r--src/vs/workbench/electron-sandbox/desktop.main.ts2
-rw-r--r--src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts18
-rw-r--r--src/vs/workbench/electron-sandbox/window.ts32
-rw-r--r--src/vs/workbench/services/configuration/browser/configurationService.ts5
-rw-r--r--src/vs/workbench/services/configuration/common/configuration.ts1
-rw-r--r--src/vs/workbench/services/editor/browser/editorResolverService.ts24
-rw-r--r--src/vs/workbench/services/editor/browser/editorService.ts60
-rw-r--r--src/vs/workbench/services/editor/common/editorResolverService.ts9
-rw-r--r--src/vs/workbench/services/environment/browser/environmentService.ts20
-rw-r--r--src/vs/workbench/services/environment/common/environmentService.ts1
-rw-r--r--src/vs/workbench/services/environment/electron-sandbox/environmentService.ts3
-rw-r--r--src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts3
-rw-r--r--src/vs/workbench/services/extensions/common/abstractExtensionService.ts12
-rw-r--r--src/vs/workbench/services/extensions/common/extensionHostManager.ts12
-rw-r--r--src/vs/workbench/services/extensions/common/extensionsApiProposals.ts2
-rw-r--r--src/vs/workbench/services/extensions/electron-sandbox/electronExtensionService.ts12
-rw-r--r--src/vs/workbench/services/extensions/test/browser/extensionService.test.ts5
-rw-r--r--src/vs/workbench/services/host/browser/browserHostService.ts34
-rw-r--r--src/vs/workbench/services/request/browser/requestService.ts2
-rw-r--r--src/vs/workbench/services/storage/test/browser/storageService.test.ts2
-rw-r--r--src/vs/workbench/services/textfile/common/textEditorService.ts7
-rw-r--r--src/vs/workbench/services/themes/browser/productIconThemeData.ts31
-rw-r--r--src/vs/workbench/services/themes/browser/workbenchThemeService.ts11
-rw-r--r--src/vs/workbench/services/workingCopy/common/workingCopyHistoryTracker.ts3
-rw-r--r--src/vs/workbench/services/workingCopy/test/browser/resourceWorkingCopy.test.ts25
-rw-r--r--src/vs/workbench/services/workingCopy/test/browser/storedFileWorkingCopy.test.ts139
-rw-r--r--src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyHistoryService.test.ts8
-rw-r--r--src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyHistoryTracker.test.ts68
-rw-r--r--src/vs/workbench/services/workspaces/common/workspaceTrust.ts4
-rw-r--r--src/vs/workbench/test/browser/parts/editor/editor.test.ts179
-rw-r--r--src/vs/workbench/test/browser/parts/editor/editorInput.test.ts3
-rw-r--r--src/vs/workbench/test/browser/workbenchTestServices.ts10
-rw-r--r--src/vs/workbench/test/electron-browser/workbenchTestServices.ts1
-rw-r--r--src/vs/workbench/workbench.common.main.ts8
173 files changed, 3011 insertions, 1313 deletions
diff --git a/src/vs/workbench/api/browser/mainThreadEditorTabs.ts b/src/vs/workbench/api/browser/mainThreadEditorTabs.ts
index c410ad77dc0..3b9b4dec1fe 100644
--- a/src/vs/workbench/api/browser/mainThreadEditorTabs.ts
+++ b/src/vs/workbench/api/browser/mainThreadEditorTabs.ts
@@ -23,6 +23,7 @@ import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEdit
import { isEqual } from 'vs/base/common/resources';
import { isGroupEditorMoveEvent } from 'vs/workbench/common/editor/editorGroupModel';
import { InteractiveEditorInput } from 'vs/workbench/contrib/interactive/browser/interactiveEditorInput';
+import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput';
interface TabInfo {
tab: IEditorTabDto;
@@ -91,6 +92,16 @@ export class MainThreadEditorTabs implements MainThreadEditorTabsShape {
private _editorInputToDto(editor: EditorInput): AnyInputDto {
+ if (editor instanceof MergeEditorInput) {
+ return {
+ kind: TabInputKind.TextMergeInput,
+ base: editor.base,
+ input1: editor.input1.uri,
+ input2: editor.input2.uri,
+ result: editor.resource
+ };
+ }
+
if (editor instanceof AbstractTextResourceEditorInput) {
return {
kind: TabInputKind.TextInput,
diff --git a/src/vs/workbench/api/browser/mainThreadTesting.ts b/src/vs/workbench/api/browser/mainThreadTesting.ts
index 01fd391e965..280741c14a9 100644
--- a/src/vs/workbench/api/browser/mainThreadTesting.ts
+++ b/src/vs/workbench/api/browser/mainThreadTesting.ts
@@ -19,6 +19,7 @@ import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResu
import { IMainThreadTestController, ITestRootProvider, ITestService } from 'vs/workbench/contrib/testing/common/testService';
import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
import { ExtHostContext, ExtHostTestingShape, ILocationDto, ITestControllerPatch, MainContext, MainThreadTestingShape } from '../common/extHost.protocol';
+import { onUnexpectedError } from 'vs/base/common/errors';
@extHostNamedCustomer(MainContext.MainThreadTesting)
export class MainThreadTesting extends Disposable implements MainThreadTestingShape, ITestRootProvider {
@@ -42,7 +43,13 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
const prevResults = resultService.results.map(r => r.toJSON()).filter(isDefined);
if (prevResults.length) {
- this.proxy.$publishTestResults(prevResults);
+ try {
+ this.proxy.$publishTestResults(prevResults);
+ } catch (err) {
+ // See https://github.com/microsoft/vscode/issues/151147
+ // Trying to send more than 1GB of data can cause the method to throw.
+ onUnexpectedError(err);
+ }
}
this._register(this.testService.onDidCancelTestRun(({ runId }) => {
diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts
index 82f4a40688d..2be02ab4797 100644
--- a/src/vs/workbench/api/common/extHost.api.impl.ts
+++ b/src/vs/workbench/api/common/extHost.api.impl.ts
@@ -577,7 +577,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
return extHostLanguages.createLanguageStatusItem(extension, id, selector);
},
registerDocumentDropEditProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentDropEditProvider): vscode.Disposable {
- checkProposedApiEnabled(extension, 'textEditorDrop');
return extHostLanguageFeatures.registerDocumentOnDropEditProvider(extension, selector, provider);
}
};
@@ -1343,6 +1342,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
InputBoxValidationSeverity: extHostTypes.InputBoxValidationSeverity,
TabInputText: extHostTypes.TextTabInput,
TabInputTextDiff: extHostTypes.TextDiffTabInput,
+ TabInputTextMerge: extHostTypes.TextMergeTabInput,
TabInputCustom: extHostTypes.CustomEditorTabInput,
TabInputNotebook: extHostTypes.NotebookEditorTabInput,
TabInputNotebookDiff: extHostTypes.NotebookDiffEditorTabInput,
diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts
index 8df19f84b76..16f0fd0ac13 100644
--- a/src/vs/workbench/api/common/extHost.protocol.ts
+++ b/src/vs/workbench/api/common/extHost.protocol.ts
@@ -620,6 +620,7 @@ export const enum TabInputKind {
UnknownInput,
TextInput,
TextDiffInput,
+ TextMergeInput,
NotebookInput,
NotebookDiffInput,
CustomEditorInput,
@@ -650,6 +651,14 @@ export interface TextDiffInputDto {
modified: UriComponents;
}
+export interface TextMergeInputDto {
+ kind: TabInputKind.TextMergeInput;
+ base: UriComponents;
+ input1: UriComponents;
+ input2: UriComponents;
+ result: UriComponents;
+}
+
export interface NotebookInputDto {
kind: TabInputKind.NotebookInput;
notebookType: string;
@@ -684,7 +693,7 @@ export interface TabInputDto {
kind: TabInputKind.TerminalEditorInput;
}
-export type AnyInputDto = UnknownInputDto | TextInputDto | TextDiffInputDto | NotebookInputDto | NotebookDiffInputDto | CustomInputDto | WebviewInputDto | InteractiveEditorInputDto | TabInputDto;
+export type AnyInputDto = UnknownInputDto | TextInputDto | TextDiffInputDto | TextMergeInputDto | NotebookInputDto | NotebookDiffInputDto | CustomInputDto | WebviewInputDto | InteractiveEditorInputDto | TabInputDto;
export interface MainThreadEditorTabsShape extends IDisposable {
// manage tabs: move, close, rearrange etc
diff --git a/src/vs/workbench/api/common/extHostEditorTabs.ts b/src/vs/workbench/api/common/extHostEditorTabs.ts
index 1df76fd88c7..3282c7cc746 100644
--- a/src/vs/workbench/api/common/extHostEditorTabs.ts
+++ b/src/vs/workbench/api/common/extHostEditorTabs.ts
@@ -9,7 +9,7 @@ import { IEditorTabDto, IEditorTabGroupDto, IExtHostEditorTabsShape, MainContext
import { URI } from 'vs/base/common/uri';
import { Emitter } from 'vs/base/common/event';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
-import { CustomEditorTabInput, InteractiveWindowInput, NotebookDiffEditorTabInput, NotebookEditorTabInput, TerminalEditorTabInput, TextDiffTabInput, TextTabInput, WebviewEditorTabInput } from 'vs/workbench/api/common/extHostTypes';
+import { CustomEditorTabInput, InteractiveWindowInput, NotebookDiffEditorTabInput, NotebookEditorTabInput, TerminalEditorTabInput, TextDiffTabInput, TextMergeTabInput, TextTabInput, WebviewEditorTabInput } from 'vs/workbench/api/common/extHostTypes';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { assertIsDefined } from 'vs/base/common/types';
import { diffSets } from 'vs/base/common/collections';
@@ -84,6 +84,8 @@ class ExtHostEditorTab {
return new TextTabInput(URI.revive(this._dto.input.uri));
case TabInputKind.TextDiffInput:
return new TextDiffTabInput(URI.revive(this._dto.input.original), URI.revive(this._dto.input.modified));
+ case TabInputKind.TextMergeInput:
+ return new TextMergeTabInput(URI.revive(this._dto.input.base), URI.revive(this._dto.input.input1), URI.revive(this._dto.input.input2), URI.revive(this._dto.input.result));
case TabInputKind.CustomEditorInput:
return new CustomEditorTabInput(URI.revive(this._dto.input.uri), this._dto.input.viewType);
case TabInputKind.WebviewEditorInput:
@@ -110,7 +112,7 @@ class ExtHostEditorTabGroup {
private _activeTabId: string = '';
private _activeGroupIdGetter: () => number | undefined;
- constructor(dto: IEditorTabGroupDto, proxy: MainThreadEditorTabsShape, activeGroupIdGetter: () => number | undefined) {
+ constructor(dto: IEditorTabGroupDto, activeGroupIdGetter: () => number | undefined) {
this._dto = dto;
this._activeGroupIdGetter = activeGroupIdGetter;
// Construct all tabs from the given dto
@@ -284,7 +286,7 @@ export class ExtHostEditorTabs implements IExtHostEditorTabs {
this._extHostTabGroups = tabGroups.map(tabGroup => {
- const group = new ExtHostEditorTabGroup(tabGroup, this._proxy, () => this._activeGroupId);
+ const group = new ExtHostEditorTabGroup(tabGroup, () => this._activeGroupId);
if (diff.added.includes(group.groupId)) {
opened.push(group.apiObject);
} else {
diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts
index ee9ec8daf69..14f71883223 100644
--- a/src/vs/workbench/api/common/extHostSCM.ts
+++ b/src/vs/workbench/api/common/extHostSCM.ts
@@ -745,7 +745,8 @@ export class ExtHostSCM implements ExtHostSCMShape {
type TEvent = { extensionId: string };
type TMeta = {
owner: 'joaomoreno';
- extensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
+ extensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The ID of the extension contributing to the Source Control API.' };
+ comment: 'This is used to know what extensions contribute to the Source Control API.';
};
this._telemetry.$publicLog2<TEvent, TMeta>('api/scm/createSourceControl', {
extensionId: extension.identifier.value,
diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts
index e50faecd595..987ca4c4a30 100644
--- a/src/vs/workbench/api/common/extHostTypes.ts
+++ b/src/vs/workbench/api/common/extHostTypes.ts
@@ -19,6 +19,22 @@ import { IRelativePatternDto } from 'vs/workbench/api/common/extHost.protocol';
import { CellEditType, ICellPartialMetadataEdit, IDocumentMetadataEdit } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import type * as vscode from 'vscode';
+/**
+ * @deprecated
+ *
+ * This utility ensures that old JS code that uses functions for classes still works. Existing usages cannot be removed
+ * but new ones must not be added
+ * */
+function es5ClassCompat(target: Function): any {
+ ///@ts-expect-error
+ function _() { return Reflect.construct(target, arguments, this.constructor); }
+ Object.defineProperty(_, 'name', Object.getOwnPropertyDescriptor(target, 'name')!);
+ Object.setPrototypeOf(_, target);
+ Object.setPrototypeOf(_.prototype, target.prototype);
+ return _;
+}
+
+@es5ClassCompat
export class Disposable {
static from(...inDisposables: { dispose(): any }[]): Disposable {
@@ -49,6 +65,7 @@ export class Disposable {
}
}
+@es5ClassCompat
export class Position {
static Min(...positions: Position[]): Position {
@@ -229,6 +246,7 @@ export class Position {
}
}
+@es5ClassCompat
export class Range {
static isRange(thing: any): thing is vscode.Range {
@@ -374,6 +392,7 @@ export class Range {
}
}
+@es5ClassCompat
export class Selection extends Range {
static isSelection(thing: any): thing is Selection {
@@ -502,6 +521,7 @@ export enum EnvironmentVariableMutatorType {
Prepend = 3
}
+@es5ClassCompat
export class TextEdit {
static isTextEdit(thing: any): thing is TextEdit {
@@ -584,6 +604,7 @@ export class TextEdit {
}
}
+@es5ClassCompat
export class NotebookEdit implements vscode.NotebookEdit {
static isNotebookCellEdit(thing: any): thing is NotebookEdit {
@@ -690,6 +711,7 @@ export interface ICellEdit {
type WorkspaceEditEntry = IFileOperation | IFileTextEdit | IFileSnippetTextEdit | IFileCellEdit | ICellEdit;
+@es5ClassCompat
export class WorkspaceEdit implements vscode.WorkspaceEdit {
private readonly _edits: WorkspaceEditEntry[] = [];
@@ -840,6 +862,7 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit {
}
}
+@es5ClassCompat
export class SnippetString {
static isSnippetString(thing: any): thing is SnippetString {
@@ -946,6 +969,7 @@ export enum DiagnosticSeverity {
Error = 0
}
+@es5ClassCompat
export class Location {
static isLocation(thing: any): thing is vscode.Location {
@@ -984,6 +1008,7 @@ export class Location {
}
}
+@es5ClassCompat
export class DiagnosticRelatedInformation {
static is(thing: any): thing is DiagnosticRelatedInformation {
@@ -1017,6 +1042,7 @@ export class DiagnosticRelatedInformation {
}
}
+@es5ClassCompat
export class Diagnostic {
range: Range;
@@ -1067,6 +1093,7 @@ export class Diagnostic {
}
}
+@es5ClassCompat
export class Hover {
public contents: (vscode.MarkdownString | vscode.MarkedString)[];
@@ -1094,6 +1121,7 @@ export enum DocumentHighlightKind {
Write = 2
}
+@es5ClassCompat
export class DocumentHighlight {
range: Range;
@@ -1145,6 +1173,7 @@ export enum SymbolTag {
Deprecated = 1,
}
+@es5ClassCompat
export class SymbolInformation {
static validate(candidate: SymbolInformation): void {
@@ -1189,6 +1218,7 @@ export class SymbolInformation {
}
}
+@es5ClassCompat
export class DocumentSymbol {
static validate(candidate: DocumentSymbol): void {
@@ -1227,6 +1257,7 @@ export enum CodeActionTriggerKind {
Automatic = 2,
}
+@es5ClassCompat
export class CodeAction {
title: string;
@@ -1247,6 +1278,7 @@ export class CodeAction {
}
+@es5ClassCompat
export class CodeActionKind {
private static readonly sep = '.';
@@ -1286,6 +1318,7 @@ CodeActionKind.Source = CodeActionKind.Empty.append('source');
CodeActionKind.SourceOrganizeImports = CodeActionKind.Source.append('organizeImports');
CodeActionKind.SourceFixAll = CodeActionKind.Source.append('fixAll');
+@es5ClassCompat
export class SelectionRange {
range: Range;
@@ -1352,6 +1385,7 @@ export enum LanguageStatusSeverity {
}
+@es5ClassCompat
export class CodeLens {
range: Range;
@@ -1368,6 +1402,7 @@ export class CodeLens {
}
}
+@es5ClassCompat
export class MarkdownString implements vscode.MarkdownString {
readonly #delegate: BaseMarkdownString;
@@ -1438,6 +1473,7 @@ export class MarkdownString implements vscode.MarkdownString {
}
}
+@es5ClassCompat
export class ParameterInformation {
label: string | [number, number];
@@ -1449,6 +1485,7 @@ export class ParameterInformation {
}
}
+@es5ClassCompat
export class SignatureInformation {
label: string;
@@ -1463,6 +1500,7 @@ export class SignatureInformation {
}
}
+@es5ClassCompat
export class SignatureHelp {
signatures: SignatureInformation[];
@@ -1486,6 +1524,7 @@ export enum InlayHintKind {
Parameter = 2,
}
+@es5ClassCompat
export class InlayHintLabelPart {
value: string;
@@ -1498,6 +1537,7 @@ export class InlayHintLabelPart {
}
}
+@es5ClassCompat
export class InlayHint implements vscode.InlayHint {
label: string | InlayHintLabelPart[];
@@ -1566,6 +1606,7 @@ export interface CompletionItemLabel {
description?: string;
}
+@es5ClassCompat
export class CompletionItem implements vscode.CompletionItem {
label: string | CompletionItemLabel;
@@ -1604,6 +1645,7 @@ export class CompletionItem implements vscode.CompletionItem {
}
}
+@es5ClassCompat
export class CompletionList {
isIncomplete?: boolean;
@@ -1615,6 +1657,7 @@ export class CompletionList {
}
}
+@es5ClassCompat
export class InlineSuggestion implements vscode.InlineCompletionItem {
filterText?: string;
@@ -1629,6 +1672,7 @@ export class InlineSuggestion implements vscode.InlineCompletionItem {
}
}
+@es5ClassCompat
export class InlineSuggestionList implements vscode.InlineCompletionList {
items: vscode.InlineCompletionItemNew[];
@@ -1639,6 +1683,7 @@ export class InlineSuggestionList implements vscode.InlineCompletionList {
}
}
+@es5ClassCompat
export class InlineSuggestionNew implements vscode.InlineCompletionItemNew {
insertText: string;
range?: Range;
@@ -1651,6 +1696,7 @@ export class InlineSuggestionNew implements vscode.InlineCompletionItemNew {
}
}
+@es5ClassCompat
export class InlineSuggestionsNew implements vscode.InlineCompletionListNew {
items: vscode.InlineCompletionItemNew[];
@@ -1744,6 +1790,7 @@ export namespace TextEditorSelectionChangeKind {
}
}
+@es5ClassCompat
export class DocumentLink {
range: Range;
@@ -1764,6 +1811,7 @@ export class DocumentLink {
}
}
+@es5ClassCompat
export class Color {
readonly red: number;
readonly green: number;
@@ -1780,6 +1828,7 @@ export class Color {
export type IColorFormat = string | { opaque: string; transparent: string };
+@es5ClassCompat
export class ColorInformation {
range: Range;
@@ -1797,6 +1846,7 @@ export class ColorInformation {
}
}
+@es5ClassCompat
export class ColorPresentation {
label: string;
textEdit?: TextEdit;
@@ -1879,6 +1929,7 @@ export enum TaskPanelKind {
New = 3
}
+@es5ClassCompat
export class TaskGroup implements vscode.TaskGroup {
isDefault: boolean | undefined;
@@ -1930,6 +1981,7 @@ function computeTaskExecutionId(values: string[]): string {
return id;
}
+@es5ClassCompat
export class ProcessExecution implements vscode.ProcessExecution {
private _process: string;
@@ -2000,6 +2052,7 @@ export class ProcessExecution implements vscode.ProcessExecution {
}
}
+@es5ClassCompat
export class ShellExecution implements vscode.ShellExecution {
private _commandLine: string | undefined;
@@ -2114,6 +2167,7 @@ export class CustomExecution implements vscode.CustomExecution {
}
}
+@es5ClassCompat
export class Task implements vscode.Task {
private static ExtensionCallbackType: string = 'customExecution';
@@ -2370,6 +2424,7 @@ export enum ProgressLocation {
Notification = 15
}
+@es5ClassCompat
export class TreeItem {
label?: string | vscode.TreeItemLabel;
@@ -2446,6 +2501,7 @@ export enum TreeItemCollapsibleState {
Expanded = 2
}
+@es5ClassCompat
export class DataTransferItem {
async asString(): Promise<string> {
@@ -2459,6 +2515,7 @@ export class DataTransferItem {
constructor(public readonly value: any) { }
}
+@es5ClassCompat
export class DataTransfer implements vscode.DataTransfer {
#items = new Map<string, DataTransferItem[]>();
@@ -2500,6 +2557,7 @@ export class DataTransfer implements vscode.DataTransfer {
}
}
+@es5ClassCompat
export class DocumentDropEdit {
insertText: string | SnippetString;
@@ -2510,6 +2568,7 @@ export class DocumentDropEdit {
}
}
+@es5ClassCompat
export class DocumentPasteEdit {
insertText: string | SnippetString;
@@ -2520,6 +2579,7 @@ export class DocumentPasteEdit {
}
}
+@es5ClassCompat
export class ThemeIcon {
static File: ThemeIcon;
@@ -2537,6 +2597,7 @@ ThemeIcon.File = new ThemeIcon('file');
ThemeIcon.Folder = new ThemeIcon('folder');
+@es5ClassCompat
export class ThemeColor {
id: string;
constructor(id: string) {
@@ -2552,6 +2613,7 @@ export enum ConfigurationTarget {
WorkspaceFolder = 3
}
+@es5ClassCompat
export class RelativePattern implements IRelativePattern {
pattern: string;
@@ -2605,6 +2667,7 @@ export class RelativePattern implements IRelativePattern {
}
}
+@es5ClassCompat
export class Breakpoint {
private _id: string | undefined;
@@ -2635,6 +2698,7 @@ export class Breakpoint {
}
}
+@es5ClassCompat
export class SourceBreakpoint extends Breakpoint {
readonly location: Location;
@@ -2647,6 +2711,7 @@ export class SourceBreakpoint extends Breakpoint {
}
}
+@es5ClassCompat
export class FunctionBreakpoint extends Breakpoint {
readonly functionName: string;
@@ -2656,6 +2721,7 @@ export class FunctionBreakpoint extends Breakpoint {
}
}
+@es5ClassCompat
export class DataBreakpoint extends Breakpoint {
readonly label: string;
readonly dataId: string;
@@ -2673,6 +2739,7 @@ export class DataBreakpoint extends Breakpoint {
}
+@es5ClassCompat
export class DebugAdapterExecutable implements vscode.DebugAdapterExecutable {
readonly command: string;
readonly args: string[];
@@ -2685,6 +2752,7 @@ export class DebugAdapterExecutable implements vscode.DebugAdapterExecutable {
}
}
+@es5ClassCompat
export class DebugAdapterServer implements vscode.DebugAdapterServer {
readonly port: number;
readonly host?: string;
@@ -2695,11 +2763,13 @@ export class DebugAdapterServer implements vscode.DebugAdapterServer {
}
}
+@es5ClassCompat
export class DebugAdapterNamedPipeServer implements vscode.DebugAdapterNamedPipeServer {
constructor(public readonly path: string) {
}
}
+@es5ClassCompat
export class DebugAdapterInlineImplementation implements vscode.DebugAdapterInlineImplementation {
readonly implementation: vscode.DebugAdapter;
@@ -2708,6 +2778,7 @@ export class DebugAdapterInlineImplementation implements vscode.DebugAdapterInli
}
}
+@es5ClassCompat
export class EvaluatableExpression implements vscode.EvaluatableExpression {
readonly range: vscode.Range;
readonly expression?: string;
@@ -2728,6 +2799,7 @@ export enum InlineCompletionTriggerKindNew {
Automatic = 1,
}
+@es5ClassCompat
export class InlineValueText implements vscode.InlineValueText {
readonly range: Range;
readonly text: string;
@@ -2738,6 +2810,7 @@ export class InlineValueText implements vscode.InlineValueText {
}
}
+@es5ClassCompat
export class InlineValueVariableLookup implements vscode.InlineValueVariableLookup {
readonly range: Range;
readonly variableName?: string;
@@ -2750,6 +2823,7 @@ export class InlineValueVariableLookup implements vscode.InlineValueVariableLook
}
}
+@es5ClassCompat
export class InlineValueEvaluatableExpression implements vscode.InlineValueEvaluatableExpression {
readonly range: Range;
readonly expression?: string;
@@ -2760,6 +2834,7 @@ export class InlineValueEvaluatableExpression implements vscode.InlineValueEvalu
}
}
+@es5ClassCompat
export class InlineValueContext implements vscode.InlineValueContext {
readonly frameId: number;
@@ -2779,6 +2854,7 @@ export enum FileChangeType {
Deleted = 3,
}
+@es5ClassCompat
export class FileSystemError extends Error {
static FileExists(messageOrUri?: string | URI): FileSystemError {
@@ -2828,6 +2904,7 @@ export class FileSystemError extends Error {
//#region folding api
+@es5ClassCompat
export class FoldingRange {
start: number;
@@ -3117,6 +3194,7 @@ export enum DebugConsoleMode {
//#endregion
+@es5ClassCompat
export class QuickInputButtons {
static readonly Back: vscode.QuickInputButton = { iconPath: new ThemeIcon('arrow-left') };
@@ -3172,6 +3250,7 @@ export class FileDecoration {
//#region Theming
+@es5ClassCompat
export class ColorTheme implements vscode.ColorTheme {
constructor(public readonly kind: ColorThemeKind) {
}
@@ -3466,6 +3545,7 @@ export class NotebookRendererScript {
//#region Timeline
+@es5ClassCompat
export class TimelineItem implements vscode.TimelineItem {
constructor(public label: string, public timestamp: number) { }
}
@@ -3555,6 +3635,7 @@ export enum TestRunProfileKind {
Coverage = 3,
}
+@es5ClassCompat
export class TestRunRequest implements vscode.TestRunRequest {
constructor(
public readonly include: vscode.TestItem[] | undefined = undefined,
@@ -3563,6 +3644,7 @@ export class TestRunRequest implements vscode.TestRunRequest {
) { }
}
+@es5ClassCompat
export class TestMessage implements vscode.TestMessage {
public expectedOutput?: string;
public actualOutput?: string;
@@ -3578,6 +3660,7 @@ export class TestMessage implements vscode.TestMessage {
constructor(public message: string | vscode.MarkdownString) { }
}
+@es5ClassCompat
export class TestTag implements vscode.TestTag {
constructor(public readonly id: string) { }
}
@@ -3585,10 +3668,12 @@ export class TestTag implements vscode.TestTag {
//#endregion
//#region Test Coverage
+@es5ClassCompat
export class CoveredCount implements vscode.CoveredCount {
constructor(public covered: number, public total: number) { }
}
+@es5ClassCompat
export class FileCoverage implements vscode.FileCoverage {
public static fromDetails(uri: vscode.Uri, details: vscode.DetailedCoverage[]): vscode.FileCoverage {
const statements = new CoveredCount(0, 0);
@@ -3632,6 +3717,7 @@ export class FileCoverage implements vscode.FileCoverage {
) { }
}
+@es5ClassCompat
export class StatementCoverage implements vscode.StatementCoverage {
constructor(
public executionCount: number,
@@ -3640,6 +3726,7 @@ export class StatementCoverage implements vscode.StatementCoverage {
) { }
}
+@es5ClassCompat
export class BranchCoverage implements vscode.BranchCoverage {
constructor(
public executionCount: number,
@@ -3647,6 +3734,7 @@ export class BranchCoverage implements vscode.BranchCoverage {
) { }
}
+@es5ClassCompat
export class FunctionCoverage implements vscode.FunctionCoverage {
constructor(
public executionCount: number,
@@ -3709,6 +3797,10 @@ export class TextDiffTabInput {
constructor(readonly original: URI, readonly modified: URI) { }
}
+export class TextMergeTabInput {
+ constructor(readonly base: URI, readonly input1: URI, readonly input2: URI, readonly result: URI) { }
+}
+
export class CustomEditorTabInput {
constructor(readonly uri: URI, readonly viewType: string) { }
}
diff --git a/src/vs/workbench/api/node/extHostCLIServer.ts b/src/vs/workbench/api/node/extHostCLIServer.ts
index 4d6cf235cc7..07e80854178 100644
--- a/src/vs/workbench/api/node/extHostCLIServer.ts
+++ b/src/vs/workbench/api/node/extHostCLIServer.ts
@@ -18,6 +18,7 @@ export interface OpenCommandPipeArgs {
folderURIs?: string[];
forceNewWindow?: boolean;
diffMode?: boolean;
+ mergeMode?: boolean;
addMode?: boolean;
gotoLineMode?: boolean;
forceReuseWindow?: boolean;
@@ -118,7 +119,7 @@ export class CLIServerBase {
}
private async open(data: OpenCommandPipeArgs): Promise<string> {
- const { fileURIs, folderURIs, forceNewWindow, diffMode, addMode, forceReuseWindow, gotoLineMode, waitMarkerFilePath, remoteAuthority } = data;
+ const { fileURIs, folderURIs, forceNewWindow, diffMode, mergeMode, addMode, forceReuseWindow, gotoLineMode, waitMarkerFilePath, remoteAuthority } = data;
const urisToOpen: IWindowOpenable[] = [];
if (Array.isArray(folderURIs)) {
for (const s of folderURIs) {
@@ -144,7 +145,7 @@ export class CLIServerBase {
}
const waitMarkerFileURI = waitMarkerFilePath ? URI.file(waitMarkerFilePath) : undefined;
const preferNewWindow = !forceReuseWindow && !waitMarkerFileURI && !addMode;
- const windowOpenArgs: IOpenWindowOptions = { forceNewWindow, diffMode, addMode, gotoLineMode, forceReuseWindow, preferNewWindow, waitMarkerFileURI, remoteAuthority };
+ const windowOpenArgs: IOpenWindowOptions = { forceNewWindow, diffMode, mergeMode, addMode, gotoLineMode, forceReuseWindow, preferNewWindow, waitMarkerFileURI, remoteAuthority };
this._commands.executeCommand('_remoteCLI.windowOpen', urisToOpen, windowOpenArgs);
return '';
diff --git a/src/vs/workbench/api/test/browser/extHostEditorTabs.test.ts b/src/vs/workbench/api/test/browser/extHostEditorTabs.test.ts
index 4c2501f4692..39bf2306faa 100644
--- a/src/vs/workbench/api/test/browser/extHostEditorTabs.test.ts
+++ b/src/vs/workbench/api/test/browser/extHostEditorTabs.test.ts
@@ -10,7 +10,7 @@ import { mock } from 'vs/base/test/common/mock';
import { IEditorTabDto, IEditorTabGroupDto, MainThreadEditorTabsShape, TabInputKind, TabModelOperationKind, TextInputDto } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostEditorTabs } from 'vs/workbench/api/common/extHostEditorTabs';
import { SingleProxyRPCProtocol } from 'vs/workbench/api/test/common/testRPCProtocol';
-import { TextTabInput } from 'vs/workbench/api/common/extHostTypes';
+import { TextMergeTabInput, TextTabInput } from 'vs/workbench/api/common/extHostTypes';
suite('ExtHostEditorTabs', function () {
@@ -209,6 +209,37 @@ suite('ExtHostEditorTabs', function () {
assert.strictEqual(extHostEditorTabs.tabGroups.activeTabGroup, first);
});
+ test('TextMergeTabInput surfaces in the UI', function () {
+
+ const extHostEditorTabs = new ExtHostEditorTabs(
+ SingleProxyRPCProtocol(new class extends mock<MainThreadEditorTabsShape>() {
+ // override/implement $moveTab or $closeTab
+ })
+ );
+
+ const tab: IEditorTabDto = createTabDto({
+ input: {
+ kind: TabInputKind.TextMergeInput,
+ base: URI.from({ scheme: 'test', path: 'base' }),
+ input1: URI.from({ scheme: 'test', path: 'input1' }),
+ input2: URI.from({ scheme: 'test', path: 'input2' }),
+ result: URI.from({ scheme: 'test', path: 'result' }),
+ }
+ });
+
+ extHostEditorTabs.$acceptEditorTabModel([{
+ isActive: true,
+ viewColumn: 0,
+ groupId: 12,
+ tabs: [tab]
+ }]);
+ assert.strictEqual(extHostEditorTabs.tabGroups.all.length, 1);
+ const [first] = extHostEditorTabs.tabGroups.all;
+ assert.ok(first.activeTab);
+ assert.strictEqual(first.tabs.indexOf(first.activeTab), 0);
+ assert.ok(first.activeTab.input instanceof TextMergeTabInput);
+ });
+
test('Ensure reference stability', function () {
const extHostEditorTabs = new ExtHostEditorTabs(
diff --git a/src/vs/workbench/api/test/browser/mainThreadEditors.test.ts b/src/vs/workbench/api/test/browser/mainThreadEditors.test.ts
index 859c9c28ee8..6ee05e73b5a 100644
--- a/src/vs/workbench/api/test/browser/mainThreadEditors.test.ts
+++ b/src/vs/workbench/api/test/browser/mainThreadEditors.test.ts
@@ -17,7 +17,7 @@ import { Range } from 'vs/editor/common/core/range';
import { Position } from 'vs/editor/common/core/position';
import { IModelService } from 'vs/editor/common/services/model';
import { EditOperation } from 'vs/editor/common/core/editOperation';
-import { TestFileService, TestEditorService, TestEditorGroupsService, TestEnvironmentService, TestLifecycleService } from 'vs/workbench/test/browser/workbenchTestServices';
+import { TestFileService, TestEditorService, TestEditorGroupsService, TestEnvironmentService, TestLifecycleService, TestWorkingCopyService } from 'vs/workbench/test/browser/workbenchTestServices';
import { BulkEditService } from 'vs/workbench/contrib/bulkEdit/browser/bulkEditService';
import { NullLogService, ILogService } from 'vs/platform/log/common/log';
import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService';
@@ -56,6 +56,7 @@ import { LanguageService } from 'vs/editor/common/services/languageService';
import { LanguageFeatureDebounceService } from 'vs/editor/common/services/languageFeatureDebounce';
import { LanguageFeaturesService } from 'vs/editor/common/services/languageFeaturesService';
import { MainThreadBulkEdits } from 'vs/workbench/api/browser/mainThreadBulkEdits';
+import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
suite('MainThreadEditors', () => {
@@ -114,6 +115,7 @@ suite('MainThreadEditors', () => {
services.set(IFileService, new TestFileService());
services.set(IEditorService, new TestEditorService());
services.set(ILifecycleService, new TestLifecycleService());
+ services.set(IWorkingCopyService, new TestWorkingCopyService());
services.set(IEditorGroupsService, new TestEditorGroupsService());
services.set(ITextFileService, new class extends mock<ITextFileService>() {
override isDirty() { return false; }
diff --git a/src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts b/src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts
index 1386a01b234..2c7906406f5 100644
--- a/src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts
+++ b/src/vs/workbench/api/test/browser/mainThreadTreeViews.test.ts
@@ -57,7 +57,7 @@ suite('MainThreadHostTreeView', function () {
id: testTreeViewId,
ctorDescriptor: null!,
name: 'Test View 1',
- treeView: instantiationService.createInstance(CustomTreeView, 'testTree', 'Test Title'),
+ treeView: instantiationService.createInstance(CustomTreeView, 'testTree', 'Test Title', 'extension.id'),
};
ViewsRegistry.registerViews([viewDescriptor], container);
diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts
index 9bf977172a8..3890ccfb5ba 100644
--- a/src/vs/workbench/browser/actions/layoutActions.ts
+++ b/src/vs/workbench/browser/actions/layoutActions.ts
@@ -583,6 +583,9 @@ if (isWindows || isLinux || isWeb) {
id: MenuId.MenubarAppearanceMenu,
group: '2_workbench_layout',
order: 0
+ }, {
+ id: MenuId.TitleBarContext,
+ order: 0
}]
});
}
diff --git a/src/vs/workbench/browser/actions/listCommands.ts b/src/vs/workbench/browser/actions/listCommands.ts
index f6f0db4d328..ee4848ce1ae 100644
--- a/src/vs/workbench/browser/actions/listCommands.ts
+++ b/src/vs/workbench/browser/actions/listCommands.ts
@@ -7,7 +7,7 @@ import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { List } from 'vs/base/browser/ui/list/listWidget';
-import { WorkbenchListFocusContextKey, IListService, WorkbenchListSupportsMultiSelectContextKey, ListWidget, WorkbenchListHasSelectionOrFocus, getSelectionKeyboardEvent, WorkbenchListWidget, WorkbenchListSelectionNavigation, WorkbenchTreeElementCanCollapse, WorkbenchTreeElementHasParent, WorkbenchTreeElementHasChild, WorkbenchTreeElementCanExpand } from 'vs/platform/list/browser/listService';
+import { WorkbenchListFocusContextKey, IListService, WorkbenchListSupportsMultiSelectContextKey, ListWidget, WorkbenchListHasSelectionOrFocus, getSelectionKeyboardEvent, WorkbenchListWidget, WorkbenchListSelectionNavigation, WorkbenchTreeElementCanCollapse, WorkbenchTreeElementHasParent, WorkbenchTreeElementHasChild, WorkbenchTreeElementCanExpand, RawWorkbenchListFocusContextKey, WorkbenchTreeFindOpen } from 'vs/platform/list/browser/listService';
import { PagedList } from 'vs/base/browser/ui/list/listPaging';
import { equals, range } from 'vs/base/common/arrays';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
@@ -17,6 +17,7 @@ import { DataTree } from 'vs/base/browser/ui/tree/dataTree';
import { ITreeNode } from 'vs/base/browser/ui/tree/tree';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { Table } from 'vs/base/browser/ui/table/tableWidget';
+import { AbstractTree, TreeFindMode } from 'vs/base/browser/ui/tree/abstractTree';
function ensureDOMFocus(widget: ListWidget | undefined): void {
// it can happen that one of the commands is executed while
@@ -607,27 +608,62 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
});
CommandsRegistry.registerCommand({
- id: 'list.toggleKeyboardNavigation',
+ id: 'list.triggerTypeNavigation',
handler: (accessor) => {
const widget = accessor.get(IListService).lastFocusedList;
- widget?.toggleKeyboardNavigation();
+ widget?.triggerTypeNavigation();
}
});
CommandsRegistry.registerCommand({
- id: 'list.toggleFilterOnType',
+ id: 'list.toggleFindMode',
handler: (accessor) => {
- const focused = accessor.get(IListService).lastFocusedList;
+ const widget = accessor.get(IListService).lastFocusedList;
+
+ if (widget instanceof AbstractTree || widget instanceof AsyncDataTree) {
+ const tree = widget;
+ tree.findMode = tree.findMode === TreeFindMode.Filter ? TreeFindMode.Highlight : TreeFindMode.Filter;
+ }
+ }
+});
+
+// Deprecated commands
+CommandsRegistry.registerCommandAlias('list.toggleKeyboardNavigation', 'list.triggerTypeNavigation');
+CommandsRegistry.registerCommandAlias('list.toggleFilterOnType', 'list.toggleFindMode');
+
+KeybindingsRegistry.registerCommandAndKeybindingRule({
+ id: 'list.find',
+ weight: KeybindingWeight.WorkbenchContrib,
+ when: RawWorkbenchListFocusContextKey,
+ primary: KeyMod.CtrlCmd | KeyCode.KeyF,
+ secondary: [KeyCode.F3],
+ handler: (accessor) => {
+ const widget = accessor.get(IListService).lastFocusedList;
// List
- if (focused instanceof List || focused instanceof PagedList || focused instanceof Table) {
+ if (widget instanceof List || widget instanceof PagedList || widget instanceof Table) {
// TODO@joao
}
// Tree
- else if (focused instanceof ObjectTree || focused instanceof DataTree || focused instanceof AsyncDataTree) {
- const tree = focused;
- tree.updateOptions({ filterOnType: !tree.filterOnType });
+ else if (widget instanceof AbstractTree || widget instanceof AsyncDataTree) {
+ const tree = widget;
+ tree.openFind();
+ }
+ }
+});
+
+KeybindingsRegistry.registerCommandAndKeybindingRule({
+ id: 'list.closeFind',
+ weight: KeybindingWeight.WorkbenchContrib,
+ when: ContextKeyExpr.and(RawWorkbenchListFocusContextKey, WorkbenchTreeFindOpen),
+ primary: KeyCode.Escape,
+ handler: (accessor) => {
+ const widget = accessor.get(IListService).lastFocusedList;
+
+ if (widget instanceof AbstractTree || widget instanceof AsyncDataTree) {
+ const tree = widget;
+ tree.closeFind();
}
}
});
diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts
index 3e47d3df06a..34679828f66 100644
--- a/src/vs/workbench/browser/labels.ts
+++ b/src/vs/workbench/browser/labels.ts
@@ -114,6 +114,7 @@ export class ResourceLabels extends Disposable {
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IModelService private readonly modelService: IModelService,
+ @IWorkspaceContextService private readonly workspaceService: IWorkspaceContextService,
@ILanguageService private readonly languageService: ILanguageService,
@IDecorationsService private readonly decorationsService: IDecorationsService,
@IThemeService private readonly themeService: IThemeService,
@@ -153,6 +154,11 @@ export class ResourceLabels extends Disposable {
this.widgets.forEach(widget => widget.notifyModelAdded(model));
}));
+ // notify when workspace folders changes
+ this._register(this.workspaceService.onDidChangeWorkspaceFolders(() => {
+ this.widgets.forEach(widget => widget.notifyWorkspaceFoldersChange());
+ }));
+
// notify when file decoration changes
this._register(this.decorationsService.onDidChangeDecorations(e => {
let notifyDidChangeDecorations = false;
@@ -250,13 +256,14 @@ export class ResourceLabel extends ResourceLabels {
@IInstantiationService instantiationService: IInstantiationService,
@IConfigurationService configurationService: IConfigurationService,
@IModelService modelService: IModelService,
+ @IWorkspaceContextService workspaceService: IWorkspaceContextService,
@ILanguageService languageService: ILanguageService,
@IDecorationsService decorationsService: IDecorationsService,
@IThemeService themeService: IThemeService,
@ILabelService labelService: ILabelService,
@ITextFileService textFileService: ITextFileService
) {
- super(DEFAULT_LABELS_CONTAINER, instantiationService, configurationService, modelService, languageService, decorationsService, themeService, labelService, textFileService);
+ super(DEFAULT_LABELS_CONTAINER, instantiationService, configurationService, modelService, workspaceService, languageService, decorationsService, themeService, labelService, textFileService);
this.label = this._register(this.create(container, options));
}
@@ -279,6 +286,7 @@ class ResourceLabelWidget extends IconLabel {
private computedIconClasses: string[] | undefined = undefined;
private computedLanguageId: string | undefined = undefined;
private computedPathLabel: string | undefined = undefined;
+ private computedWorkspaceFolderLabel: string | undefined = undefined;
private needsRedraw: Redraw | undefined = undefined;
private isHidden: boolean = false;
@@ -374,6 +382,15 @@ class ResourceLabelWidget extends IconLabel {
}
}
+ notifyWorkspaceFoldersChange(): void {
+ if (typeof this.computedWorkspaceFolderLabel === 'string') {
+ const resource = toResource(this.label);
+ if (URI.isUri(resource) && this.label?.name === this.computedWorkspaceFolderLabel) {
+ this.setFile(resource, this.options);
+ }
+ }
+ }
+
setFile(resource: URI, options?: IFileLabelOptions): void {
const hideLabel = options?.hideLabel;
let name: string | undefined;
@@ -382,6 +399,7 @@ class ResourceLabelWidget extends IconLabel {
const workspaceFolder = this.contextService.getWorkspaceFolder(resource);
if (workspaceFolder) {
name = workspaceFolder.name;
+ this.computedWorkspaceFolderLabel = name;
}
}
@@ -602,5 +620,6 @@ class ResourceLabelWidget extends IconLabel {
this.computedLanguageId = undefined;
this.computedIconClasses = undefined;
this.computedPathLabel = undefined;
+ this.computedWorkspaceFolderLabel = undefined;
}
}
diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts
index 240b52cc2b9..2e997f59f0b 100644
--- a/src/vs/workbench/browser/layout.ts
+++ b/src/vs/workbench/browser/layout.ts
@@ -9,8 +9,7 @@ import { EventType, addDisposableListener, getClientArea, Dimension, position, s
import { onDidChangeFullscreen, isFullscreen } from 'vs/base/browser/browser';
import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup';
import { isWindows, isLinux, isMacintosh, isWeb, isNative, isIOS } from 'vs/base/common/platform';
-import { IUntypedEditorInput, pathsToEditors } from 'vs/workbench/common/editor';
-import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput';
+import { EditorInputCapabilities, isResourceEditorInput, IUntypedEditorInput, pathsToEditors } from 'vs/workbench/common/editor';
import { SidebarPart } from 'vs/workbench/browser/parts/sidebar/sidebarPart';
import { PanelPart } from 'vs/workbench/browser/parts/panel/panelPart';
import { Position, Parts, PanelOpensMaximizedOptions, IWorkbenchLayoutService, positionFromString, positionToString, panelOpensMaximizedFromString, PanelAlignment } from 'vs/workbench/services/layout/browser/layoutService';
@@ -75,7 +74,7 @@ interface IWorkbenchLayoutWindowInitializationState {
};
editor: {
restoreEditors: boolean;
- editorsToOpen: Promise<IUntypedEditorInput[]> | IUntypedEditorInput[];
+ editorsToOpen: Promise<IUntypedEditorInput[]>;
};
}
@@ -98,6 +97,7 @@ enum WorkbenchLayoutClasses {
interface IInitialFilesToOpen {
filesToOpenOrCreate?: IPath[];
filesToDiff?: IPath[];
+ filesToMerge?: IPath[];
}
export abstract class Layout extends Disposable implements IWorkbenchLayoutService {
@@ -278,7 +278,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
this._register(this.hostService.onDidChangeFocus(e => this.onWindowFocusChanged(e)));
}
- private onMenubarToggled(visible: boolean) {
+ private onMenubarToggled(visible: boolean): void {
if (visible !== this.windowState.runtime.menuBar.toggled) {
this.windowState.runtime.menuBar.toggled = visible;
@@ -565,26 +565,33 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
return this.windowState.initialization.editor.restoreEditors;
}
- private resolveEditorsToOpen(fileService: IFileService, initialFilesToOpen: IInitialFilesToOpen | undefined): Promise<IUntypedEditorInput[]> | IUntypedEditorInput[] {
-
- // Files to open, diff or create
+ private async resolveEditorsToOpen(fileService: IFileService, initialFilesToOpen: IInitialFilesToOpen | undefined): Promise<IUntypedEditorInput[]> {
if (initialFilesToOpen) {
- // Files to diff is exclusive
- return pathsToEditors(initialFilesToOpen.filesToDiff, fileService).then(filesToDiff => {
- if (filesToDiff.length === 2) {
- const diffEditorInput: IUntypedEditorInput[] = [{
- original: { resource: filesToDiff[0].resource },
- modified: { resource: filesToDiff[1].resource },
- options: { pinned: true }
- }];
+ // Merge editor
+ const filesToMerge = await pathsToEditors(initialFilesToOpen.filesToMerge, fileService);
+ if (filesToMerge.length === 4 && isResourceEditorInput(filesToMerge[0]) && isResourceEditorInput(filesToMerge[1]) && isResourceEditorInput(filesToMerge[2]) && isResourceEditorInput(filesToMerge[3])) {
+ return [{
+ input1: { resource: filesToMerge[0].resource },
+ input2: { resource: filesToMerge[1].resource },
+ base: { resource: filesToMerge[2].resource },
+ result: { resource: filesToMerge[3].resource },
+ options: { pinned: true, override: 'mergeEditor.Input' } // TODO@bpasero remove the override once the resolver is ready
+ }];
+ }
- return diffEditorInput;
- }
+ // Diff editor
+ const filesToDiff = await pathsToEditors(initialFilesToOpen.filesToDiff, fileService);
+ if (filesToDiff.length === 2) {
+ return [{
+ original: { resource: filesToDiff[0].resource },
+ modified: { resource: filesToDiff[1].resource },
+ options: { pinned: true }
+ }];
+ }
- // Otherwise: Open/Create files
- return pathsToEditors(initialFilesToOpen.filesToOpenOrCreate, fileService);
- });
+ // Normal editor
+ return pathsToEditors(initialFilesToOpen.filesToOpenOrCreate, fileService);
}
// Empty workbench configured to open untitled file if empty
@@ -593,13 +600,12 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
return []; // do not open any empty untitled file if we restored groups/editors from previous session
}
- return this.workingCopyBackupService.hasBackups().then(hasBackups => {
- if (hasBackups) {
- return []; // do not open any empty untitled file if we have backups to restore
- }
+ const hasBackups = await this.workingCopyBackupService.hasBackups();
+ if (hasBackups) {
+ return []; // do not open any empty untitled file if we have backups to restore
+ }
- return [{ resource: undefined }]; // open empty untitled file
- });
+ return [{ resource: undefined }]; // open empty untitled file
}
return [];
@@ -638,10 +644,10 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
};
}
- // Then check for files to open, create or diff from main side
- const { filesToOpenOrCreate, filesToDiff } = this.environmentService;
- if (filesToOpenOrCreate || filesToDiff) {
- return { filesToOpenOrCreate, filesToDiff };
+ // Then check for files to open, create or diff/merge from main side
+ const { filesToOpenOrCreate, filesToDiff, filesToMerge } = this.environmentService;
+ if (filesToOpenOrCreate || filesToDiff || filesToMerge) {
+ return { filesToOpenOrCreate, filesToDiff, filesToMerge };
}
return undefined;
@@ -681,12 +687,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
// signaling that layout is restored, but we do
// not need to await the editors from having
// fully loaded.
- let editors: IUntypedEditorInput[];
- if (Array.isArray(this.windowState.initialization.editor.editorsToOpen)) {
- editors = this.windowState.initialization.editor.editorsToOpen;
- } else {
- editors = await this.windowState.initialization.editor.editorsToOpen;
- }
+ const editors = await this.windowState.initialization.editor.editorsToOpen;
let openEditorsPromise: Promise<unknown> | undefined = undefined;
if (editors.length) {
@@ -1307,27 +1308,25 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
centerEditorLayout(active: boolean, skipLayout?: boolean): void {
this.stateModel.setRuntimeValue(LayoutStateKeys.EDITOR_CENTERED, active);
- let smartActive = active;
const activeEditor = this.editorService.activeEditor;
- let isEditorSplit = false;
+ let isEditorComplex = false;
if (activeEditor instanceof DiffEditorInput) {
- isEditorSplit = this.configurationService.getValue('diffEditor.renderSideBySide');
- } else if (activeEditor instanceof SideBySideEditorInput) {
- isEditorSplit = true;
+ isEditorComplex = this.configurationService.getValue('diffEditor.renderSideBySide');
+ } else if (activeEditor?.hasCapability(EditorInputCapabilities.MultipleEditors)) {
+ isEditorComplex = true;
}
const isCenteredLayoutAutoResizing = this.configurationService.getValue('workbench.editor.centeredLayoutAutoResize');
if (
isCenteredLayoutAutoResizing &&
- (this.editorGroupService.groups.length > 1 || isEditorSplit)
+ (this.editorGroupService.groups.length > 1 || isEditorComplex)
) {
- smartActive = false;
+ active = false; // disable centered layout for complex editors or when there is more than one group
}
- // Enter Centered Editor Layout
- if (this.editorGroupService.isLayoutCentered() !== smartActive) {
- this.editorGroupService.centerLayout(smartActive);
+ if (this.editorGroupService.isLayoutCentered() !== active) {
+ this.editorGroupService.centerLayout(active);
if (!skipLayout) {
this.layout();
diff --git a/src/vs/workbench/browser/part.ts b/src/vs/workbench/browser/part.ts
index 1830fb9ee87..345cccd504a 100644
--- a/src/vs/workbench/browser/part.ts
+++ b/src/vs/workbench/browser/part.ts
@@ -126,7 +126,7 @@ export abstract class Part extends Component implements ISerializableView {
//#region ISerializableView
- private _onDidChange = this._register(new Emitter<IViewSize | undefined>());
+ protected _onDidChange = this._register(new Emitter<IViewSize | undefined>());
get onDidChange(): Event<IViewSize | undefined> { return this._onDidChange.event; }
element!: HTMLElement;
diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts
index ed7324042f0..d49e3b8f875 100644
--- a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts
+++ b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts
@@ -103,8 +103,9 @@ export class ViewContainerActivityAction extends ActivityAction {
private logAction(action: string) {
type ActivityBarActionClassification = {
owner: 'sbatten';
- viewletId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
- action: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
+ comment: 'Event logged when an activity bar action is triggered.';
+ viewletId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The view in the activity bar for which the action was performed.' };
+ action: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The action that was performed. e.g. "hide", "show", or "refocus"' };
};
this.telemetryService.publicLog2<{ viewletId: String; action: String }, ActivityBarActionClassification>('activityBarAction', { viewletId: this.activity.id, action });
}
diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts
index 57aa2f38d91..15973ea64e2 100644
--- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts
+++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts
@@ -280,10 +280,10 @@ export class BreadcrumbsControl {
this._editorGroup.activeEditorPane
);
- this.domNode.classList.toggle('relative-path', model.isRelative());
this.domNode.classList.toggle('backslash-path', this._labelService.getSeparator(uri.scheme, uri.authority) === '\\');
const updateBreadcrumbs = () => {
+ this.domNode.classList.toggle('relative-path', model.isRelative());
const showIcons = this._cfShowIcons.getValue();
const options: IBreadcrumbsControlOptions = {
...this._options,
diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts
index d3d8b188c83..e542f84cff4 100644
--- a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts
+++ b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts
@@ -37,7 +37,7 @@ export class OutlineElement2 {
export class BreadcrumbsModel {
private readonly _disposables = new DisposableStore();
- private readonly _fileInfo: FileInfo;
+ private _fileInfo: FileInfo;
private readonly _cfgFilePath: BreadcrumbsConfig<'on' | 'off' | 'last'>;
private readonly _cfgSymbolPath: BreadcrumbsConfig<'on' | 'off' | 'last'>;
@@ -60,6 +60,7 @@ export class BreadcrumbsModel {
this._disposables.add(this._cfgFilePath.onDidChange(_ => this._onDidUpdate.fire(this)));
this._disposables.add(this._cfgSymbolPath.onDidChange(_ => this._onDidUpdate.fire(this)));
+ this._workspaceService.onDidChangeWorkspaceFolders(this._onDidChangeWorkspaceFolders, this, this._disposables);
this._fileInfo = this._initFilePathInfo(resource);
if (editor) {
@@ -146,6 +147,11 @@ export class BreadcrumbsModel {
return info;
}
+ private _onDidChangeWorkspaceFolders() {
+ this._fileInfo = this._initFilePathInfo(this.resource);
+ this._onDidUpdate.fire(this);
+ }
+
private _bindToEditor(editor: IEditorPane): void {
const newCts = new CancellationTokenSource();
this._currentOutline.clear();
diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts
index 6abeeef5019..ab5ebcce987 100644
--- a/src/vs/workbench/browser/parts/editor/editorCommands.ts
+++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts
@@ -970,10 +970,10 @@ function registerCloseEditorCommands() {
type WorkbenchEditorReopenClassification = {
owner: 'rebornix';
comment: 'Identify how a document is reopened';
- scheme: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
- ext: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
- from: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
- to: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
+ scheme: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'File system provider scheme for the resource' };
+ ext: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'File extension for the resource' };
+ from: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The editor view type the resource is switched from' };
+ to: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The editor view type the resource is switched to' };
};
type WorkbenchEditorReopenEvent = {
diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts
index 4d29cc8f143..e9686439360 100644
--- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts
+++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts
@@ -32,7 +32,7 @@ interface IDropOperation {
}
function isDropIntoEditorEnabledGlobally(configurationService: IConfigurationService) {
- return configurationService.getValue<boolean>('workbench.experimental.editor.dropIntoEditor.enabled');
+ return configurationService.getValue<boolean>('workbench.editor.dropIntoEditor.enabled');
}
function isDragIntoEditorEvent(e: DragEvent): boolean {
diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts
index d2df00e4d5e..29bfb9d6706 100644
--- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts
+++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts
@@ -1555,7 +1555,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView {
// Let editor handle confirmation if implemented
if (typeof editor.closeHandler?.confirm === 'function') {
- confirmation = await editor.closeHandler.confirm();
+ confirmation = await editor.closeHandler.confirm([{ editor, groupId: this.id }]);
}
// Show a file specific confirmation
diff --git a/src/vs/workbench/browser/parts/editor/editorWithViewState.ts b/src/vs/workbench/browser/parts/editor/editorWithViewState.ts
index b24848b51f9..f01bedd8f59 100644
--- a/src/vs/workbench/browser/parts/editor/editorWithViewState.ts
+++ b/src/vs/workbench/browser/parts/editor/editorWithViewState.ts
@@ -208,7 +208,9 @@ export abstract class AbstractEditorWithViewState<T extends object> extends Edit
*
* @param resource the expected `URI` for the view state. This
* should be used as a way to ensure the view state in the
- * editor control is matching the resource expected.
+ * editor control is matching the resource expected, for example
+ * by comparing with the underlying model (this was a fix for
+ * https://github.com/microsoft/vscode/issues/40114).
*/
protected abstract computeEditorViewState(resource: URI): T | undefined;
diff --git a/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts b/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts
index 2b383111c09..25e9213343e 100644
--- a/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts
+++ b/src/vs/workbench/browser/parts/titlebar/commandCenterControl.ts
@@ -7,7 +7,7 @@ import { reset } from 'vs/base/browser/dom';
import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate';
import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels';
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
-import { IAction } from 'vs/base/common/actions';
+import { IAction, WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from 'vs/base/common/actions';
import { Codicon } from 'vs/base/common/codicons';
import { Emitter, Event } from 'vs/base/common/event';
import { DisposableStore } from 'vs/base/common/lifecycle';
@@ -20,6 +20,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
+import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import * as colors from 'vs/platform/theme/common/colorRegistry';
import { WindowTitle } from 'vs/workbench/browser/parts/titlebar/windowTitle';
import { MENUBAR_SELECTION_BACKGROUND, MENUBAR_SELECTION_FOREGROUND, PANEL_BORDER, TITLE_BAR_ACTIVE_FOREGROUND } from 'vs/workbench/common/theme';
@@ -42,6 +43,7 @@ export class CommandCenterControl {
@IMenuService menuService: IMenuService,
@IQuickInputService quickInputService: IQuickInputService,
@IKeybindingService keybindingService: IKeybindingService,
+ @ITelemetryService telemetryService: ITelemetryService,
) {
this.element.classList.add('command-center');
@@ -65,15 +67,14 @@ export class CommandCenterControl {
searchIcon.classList.add('search-icon');
this.workspaceTitle.classList.add('search-label');
- this._updateFromWindowTitle();
+ this.updateTooltip();
reset(this.label, searchIcon, this.workspaceTitle);
// this._renderAllQuickPickItem(container);
- this._store.add(windowTitle.onDidChange(this._updateFromWindowTitle, this));
+ this._store.add(windowTitle.onDidChange(this.updateTooltip, this));
}
- private _updateFromWindowTitle() {
-
+ override getTooltip() {
// label: just workspace name and optional decorations
const { prefix, suffix } = windowTitle.getTitleDecorations();
let label = windowTitle.workspaceName;
@@ -93,7 +94,8 @@ export class CommandCenterControl {
const title = kb
? localize('title', "Search {0} ({1}) \u2014 {2}", windowTitle.workspaceName, kb, windowTitle.value)
: localize('title2', "Search {0} \u2014 {1}", windowTitle.workspaceName, windowTitle.value);
- this._applyUpdateTooltip(title);
+
+ return title;
}
}
return instantiationService.createInstance(InputLikeViewItem, action, { hoverDelegate });
@@ -129,6 +131,10 @@ export class CommandCenterControl {
}));
this._disposables.add(quickInputService.onShow(this._setVisibility.bind(this, false)));
this._disposables.add(quickInputService.onHide(this._setVisibility.bind(this, true)));
+
+ titleToolbar.actionRunner.onDidRun(e => {
+ telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', { id: e.action.id, from: 'commandCenter' });
+ });
}
private _setVisibility(show: boolean): void {
diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css
index 29f1142364e..5f3f76d376d 100644
--- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css
+++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css
@@ -133,6 +133,7 @@
/* width */
width: 16px;
+ flex-shrink: 0;
}
.monaco-workbench .part.titlebar>.titlebar-container>.window-title>.command-center .action-item.quickopen>.action-label {
@@ -190,7 +191,7 @@
width: 35px;
height: 100%;
position: relative;
- z-index: 3000;
+ z-index: 2500;
flex-shrink: 0;
}
diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts
index 6c0a24014c8..b431d4df48e 100644
--- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts
+++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts
@@ -581,17 +581,6 @@ export class CustomMenubarControl extends MenubarControl {
return getMenuBarVisibility(this.configurationService);
}
- private get currentCommandCenterEnabled(): boolean {
- const settingValue = this.configurationService.getValue<boolean>('window.commandCenter');
-
- let enableCommandCenter = false;
- if (typeof settingValue === 'boolean') {
- enableCommandCenter = !!settingValue;
- }
-
- return enableCommandCenter;
- }
-
private get currentDisableMenuBarAltFocus(): boolean {
const settingValue = this.configurationService.getValue<boolean>('window.customMenuBarAltFocus');
@@ -637,11 +626,6 @@ export class CustomMenubarControl extends MenubarControl {
private get currentCompactMenuMode(): Direction | undefined {
if (this.currentMenubarVisibility !== 'compact') {
- // With the command center enabled, use compact menu in title bar and flow to the right
- if (this.currentCommandCenterEnabled) {
- return Direction.Down;
- }
-
return undefined;
}
diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts
index 2c7855bd9cc..7c4cef14f90 100644
--- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts
+++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts
@@ -11,23 +11,23 @@ import { getZoomFactor } from 'vs/base/browser/browser';
import { MenuBarVisibility, getTitleBarStyle, getMenuBarVisibility } from 'vs/platform/window/common/window';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
-import { IAction, toAction } from 'vs/base/common/actions';
+import { IAction } from 'vs/base/common/actions';
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
import { DisposableStore, dispose } from 'vs/base/common/lifecycle';
import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/environment/browser/environmentService';
import { IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { TITLE_BAR_ACTIVE_BACKGROUND, TITLE_BAR_ACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_BACKGROUND, TITLE_BAR_BORDER, WORKBENCH_BACKGROUND } from 'vs/workbench/common/theme';
-import { isMacintosh, isWindows, isLinux, isWeb } from 'vs/base/common/platform';
+import { isMacintosh, isWindows, isLinux, isWeb, isNative } from 'vs/base/common/platform';
import { Color } from 'vs/base/common/color';
import { EventType, EventHelper, Dimension, isAncestor, append, $, addDisposableListener, runAtThisOrScheduleAtNextAnimationFrame, prepend, reset } from 'vs/base/browser/dom';
import { CustomMenubarControl } from 'vs/workbench/browser/parts/titlebar/menubarControl';
-import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
+import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { Emitter, Event } from 'vs/base/common/event';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { createActionViewItem, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
-import { IMenuService, IMenu, MenuId } from 'vs/platform/actions/common/actions';
-import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
+import { Action2, IMenuService, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
+import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { Codicon } from 'vs/base/common/codicons';
import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry';
@@ -36,6 +36,7 @@ import { WindowTitle } from 'vs/workbench/browser/parts/titlebar/windowTitle';
import { CommandCenterControl } from 'vs/workbench/browser/parts/titlebar/commandCenterControl';
import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate';
import { IHoverService } from 'vs/workbench/services/hover/browser/hover';
+import { CATEGORIES } from 'vs/workbench/common/actions';
export class TitlebarPart extends Part implements ITitleService {
@@ -49,8 +50,9 @@ export class TitlebarPart extends Part implements ITitleService {
readonly maximumWidth: number = Number.POSITIVE_INFINITY;
get minimumHeight(): number {
const value = this.isCommandCenterVisible ? 35 : 30;
- return value / (this.currentMenubarVisibility === 'hidden' || getZoomFactor() < 1 ? getZoomFactor() : 1);
+ return value / (this.useCounterZoom ? getZoomFactor() : 1);
}
+
get maximumHeight(): number { return this.minimumHeight; }
//#endregion
@@ -82,8 +84,6 @@ export class TitlebarPart extends Part implements ITitleService {
private readonly windowTitle: WindowTitle;
- private readonly contextMenu: IMenu;
-
constructor(
@IContextMenuService private readonly contextMenuService: IContextMenuService,
@IConfigurationService protected readonly configurationService: IConfigurationService,
@@ -99,7 +99,6 @@ export class TitlebarPart extends Part implements ITitleService {
) {
super(Parts.TITLEBAR_PART, { hasTitle: false }, themeService, storageService, layoutService);
this.windowTitle = this._register(instantiationService.createInstance(WindowTitle));
- this.contextMenu = this._register(menuService.createMenu(MenuId.TitleBarContext, contextKeyService));
this.titleBarStyle = getTitleBarStyle(this.configurationService);
@@ -157,18 +156,11 @@ export class TitlebarPart extends Part implements ITitleService {
this.installMenubar();
}
}
-
- // Trigger a re-install of the menubar with command center change
- if (event.affectsConfiguration('window.commandCenter')) {
- if (this.currentMenubarVisibility !== 'compact') {
- this.uninstallMenubar();
- this.installMenubar();
- }
- }
}
if (this.titleBarStyle !== 'native' && this.layoutControls && event.affectsConfiguration('workbench.layoutControl.enabled')) {
this.layoutControls.classList.toggle('show-layout-control', this.layoutControlEnabled);
+ this._onDidChange.fire(undefined);
}
if (event.affectsConfiguration(TitlebarPart.configCommandCenter)) {
@@ -284,13 +276,6 @@ export class TitlebarPart extends Part implements ITitleService {
allowContextMenu: true
});
- this._register(addDisposableListener(this.layoutControls, EventType.CONTEXT_MENU, e => {
- EventHelper.stop(e);
-
- this.onLayoutControlContextMenu(e, this.layoutControls!);
- }));
-
-
const menu = this._register(this.menuService.createMenu(MenuId.LayoutControlMenu, this.contextKeyService));
const updateLayoutMenu = () => {
if (!this.layoutToolbar) {
@@ -313,11 +298,10 @@ export class TitlebarPart extends Part implements ITitleService {
// Context menu on title
[EventType.CONTEXT_MENU, EventType.MOUSE_DOWN].forEach(event => {
- this._register(addDisposableListener(this.title, event, e => {
+ this._register(addDisposableListener(this.rootContainer, event, e => {
if (e.type === EventType.CONTEXT_MENU || e.metaKey) {
EventHelper.stop(e);
-
- this.onContextMenu(e);
+ this.onContextMenu(e, e.target === this.title ? MenuId.TitleBarTitleContext : MenuId.TitleBarContext);
}
}));
});
@@ -347,6 +331,27 @@ export class TitlebarPart extends Part implements ITitleService {
this.updateStyles();
+ const that = this;
+ registerAction2(class FocusTitleBar extends Action2 {
+
+ constructor() {
+ super({
+ id: `workbench.action.focusTitleBar`,
+ title: { value: localize('focusTitleBar', "Focus Title Bar"), original: 'Focus Title Bar' },
+ category: CATEGORIES.View,
+ f1: true,
+ });
+ }
+
+ run(accessor: ServicesAccessor, ...args: any[]): void {
+ if (that.customMenubar) {
+ that.customMenubar.toggleFocus();
+ } else {
+ (that.element.querySelector('[tabindex]:not([tabindex="-1"])') as HTMLElement).focus();
+ }
+ }
+ });
+
return this.element;
}
@@ -388,42 +393,23 @@ export class TitlebarPart extends Part implements ITitleService {
}
}
- private onContextMenu(e: MouseEvent): void {
+ protected onContextMenu(e: MouseEvent, menuId: MenuId): void {
// Find target anchor
const event = new StandardMouseEvent(e);
const anchor = { x: event.posx, y: event.posy };
// Fill in contributed actions
+ const menu = this.menuService.createMenu(menuId, this.contextKeyService);
const actions: IAction[] = [];
- const actionsDisposable = createAndFillInContextMenuActions(this.contextMenu, undefined, actions);
-
- // Show it
- this.contextMenuService.showContextMenu({
- getAnchor: () => anchor,
- getActions: () => actions,
- onHide: () => dispose(actionsDisposable)
- });
- }
-
- private onLayoutControlContextMenu(e: MouseEvent, el: HTMLElement): void {
- // Find target anchor
- const event = new StandardMouseEvent(e);
- const anchor = { x: event.posx, y: event.posy };
-
- const actions: IAction[] = [];
- actions.push(toAction({
- id: 'layoutControl.hide',
- label: localize('layoutControl.hide', "Hide Layout Control"),
- run: () => {
- this.configurationService.updateValue('workbench.layoutControl.enabled', false);
- }
- }));
+ const actionsDisposable = createAndFillInContextMenuActions(menu, undefined, actions);
+ menu.dispose();
// Show it
this.contextMenuService.showContextMenu({
getAnchor: () => anchor,
getActions: () => actions,
- domForShadowRoot: el
+ onHide: () => dispose(actionsDisposable),
+ domForShadowRoot: isMacintosh && isNative ? event.target : undefined
});
}
@@ -454,17 +440,26 @@ export class TitlebarPart extends Part implements ITitleService {
return this.configurationService.getValue<boolean>('workbench.layoutControl.enabled');
}
+ protected get useCounterZoom(): boolean {
+ // Prevent zooming behavior if any of the following conditions are met:
+ // 1. Shrinking below the window control size (zoom < 1)
+ // 2. No custom items are present in the title bar
+ const zoomFactor = getZoomFactor();
+
+ const noMenubar = this.currentMenubarVisibility === 'hidden' || (!isWeb && isMacintosh);
+ const noCommandCenter = !this.isCommandCenterVisible;
+ const noLayoutControls = !this.layoutControlEnabled;
+ return zoomFactor < 1 || (noMenubar && noCommandCenter && noLayoutControls);
+ }
+
updateLayout(dimension: Dimension): void {
this.lastLayoutDimensions = dimension;
if (getTitleBarStyle(this.configurationService) === 'custom') {
- // Prevent zooming behavior if any of the following conditions are met:
- // 1. Native macOS
- // 2. Menubar is hidden
- // 3. Shrinking below the window control size (zoom < 1)
const zoomFactor = getZoomFactor();
+
this.element.style.setProperty('--zoom-factor', zoomFactor.toString());
- this.rootContainer.classList.toggle('counter-zoom', zoomFactor < 1 || (!isWeb && isMacintosh) || this.currentMenubarVisibility === 'hidden');
+ this.rootContainer.classList.toggle('counter-zoom', this.useCounterZoom);
runAtThisOrScheduleAtNextAnimationFrame(() => this.adjustTitleMarginToCenter());
@@ -507,3 +502,34 @@ registerThemingParticipant((theme, collector) => {
`);
}
});
+
+
+class ToogleConfigAction extends Action2 {
+
+ constructor(private readonly section: string, title: string, order: number) {
+ super({
+ id: `toggle.${section}`,
+ title,
+ toggled: ContextKeyExpr.equals(`config.${section}`, true),
+ menu: { id: MenuId.TitleBarContext, order }
+ });
+ }
+
+ run(accessor: ServicesAccessor, ...args: any[]): void {
+ const configService = accessor.get(IConfigurationService);
+ const value = configService.getValue(this.section);
+ configService.updateValue(this.section, !value);
+ }
+}
+
+registerAction2(class ToogleCommandCenter extends ToogleConfigAction {
+ constructor() {
+ super('window.commandCenter', localize('toggle.commandCenter', 'Show Command Center'), 1);
+ }
+});
+
+registerAction2(class ToogleLayoutControl extends ToogleConfigAction {
+ constructor() {
+ super('workbench.layoutControl.enabled', localize('toggle.layout', 'Show Layout Controls'), 2);
+ }
+});
diff --git a/src/vs/workbench/browser/parts/views/viewPane.ts b/src/vs/workbench/browser/parts/views/viewPane.ts
index b8b9f5046ce..22a0701865b 100644
--- a/src/vs/workbench/browser/parts/views/viewPane.ts
+++ b/src/vs/workbench/browser/parts/views/viewPane.ts
@@ -52,8 +52,9 @@ export interface IViewPaneOptions extends IPaneOptions {
type WelcomeActionClassification = {
owner: 'joaomoreno';
- viewId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
- uri: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
+ viewId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The view ID in which the welcome view button was clicked.' };
+ uri: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The URI of the command ran by the result of clicking the button.' };
+ comment: 'This is used to know when users click on the welcome view buttons.';
};
const viewPaneContainerExpandedIcon = registerIcon('view-pane-container-expanded', Codicon.chevronDown, nls.localize('viewPaneContainerExpandedIcon', 'Icon for an expanded view pane container.'));
diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts
index 5d3a8a9d037..b042c202d98 100644
--- a/src/vs/workbench/browser/workbench.contribution.ts
+++ b/src/vs/workbench/browser/workbench.contribution.ts
@@ -466,6 +466,11 @@ const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Con
'default': 'both',
'description': localize('layoutControlType', "Controls whether the layout control in the custom title bar is displayed as a single menu button or with multiple UI toggles."),
},
+ 'workbench.editor.dropIntoEditor.enabled': {
+ 'type': 'boolean',
+ 'default': true,
+ 'markdownDescription': localize('dropIntoEditor', "Controls whether you can drag and drop a file into a text editor by holding down `shift` (instead of opening the file in an editor)."),
+ },
'workbench.experimental.layoutControl.enabled': {
'type': 'boolean',
'tags': ['experimental'],
@@ -486,12 +491,6 @@ const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Con
'description': localize('layoutControlType', "Controls whether the layout control in the custom title bar is displayed as a single menu button or with multiple UI toggles."),
'markdownDeprecationMessage': localize({ key: 'layoutControlTypeDeprecation', comment: ['{0} is a placeholder for a setting identifier.'] }, "This setting has been deprecated in favor of {0}", '`#workbench.layoutControl.type#`')
},
- 'workbench.experimental.editor.dropIntoEditor.enabled': {
- 'type': 'boolean',
- 'default': true,
- 'tags': ['experimental'],
- 'markdownDescription': localize('dropIntoEditor', "Controls whether you can drag and drop a file into a text editor by holding down `shift` (instead of opening the file in an editor)."),
- }
}
});
diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts
index 03fd014d39b..6f6ab509248 100644
--- a/src/vs/workbench/common/editor.ts
+++ b/src/vs/workbench/common/editor.ts
@@ -484,6 +484,36 @@ export interface IResourceDiffEditorInput extends IBaseUntypedEditorInput {
readonly modified: IResourceEditorInput | ITextResourceEditorInput | IUntitledTextResourceEditorInput;
}
+/**
+ * A resource merge editor input compares multiple editors
+ * highlighting the differences for merging.
+ *
+ * Note: all sides must be resolvable to the same editor, or
+ * a text based presentation will be used as fallback.
+ */
+export interface IResourceMergeEditorInput extends IBaseUntypedEditorInput {
+
+ /**
+ * The one changed version of the file.
+ */
+ readonly input1: IResourceEditorInput | ITextResourceEditorInput;
+
+ /**
+ * The second changed version of the file.
+ */
+ readonly input2: IResourceEditorInput | ITextResourceEditorInput;
+
+ /**
+ * The base common ancestor of the file to merge.
+ */
+ readonly base: IResourceEditorInput | ITextResourceEditorInput;
+
+ /**
+ * The resulting output of the merge.
+ */
+ readonly result: IResourceEditorInput | ITextResourceEditorInput;
+}
+
export function isResourceEditorInput(editor: unknown): editor is IResourceEditorInput {
if (isEditorInput(editor)) {
return false; // make sure to not accidentally match on typed editor inputs
@@ -531,6 +561,16 @@ export function isUntitledResourceEditorInput(editor: unknown): editor is IUntit
return candidate.resource === undefined || candidate.resource.scheme === Schemas.untitled || candidate.forceUntitled === true;
}
+export function isResourceMergeEditorInput(editor: unknown): editor is IResourceMergeEditorInput {
+ if (isEditorInput(editor)) {
+ return false; // make sure to not accidentally match on typed editor inputs
+ }
+
+ const candidate = editor as IResourceMergeEditorInput | undefined;
+
+ return URI.isUri(candidate?.base?.resource) && URI.isUri(candidate?.input1?.resource) && URI.isUri(candidate?.input2?.resource) && URI.isUri(candidate?.result?.resource);
+}
+
export const enum Verbosity {
SHORT,
MEDIUM,
@@ -693,9 +733,15 @@ export const enum EditorInputCapabilities {
* editor by holding shift.
*/
CanDropIntoEditor = 1 << 7,
+
+ /**
+ * Signals that the editor is composed of multiple editors
+ * within.
+ */
+ MultipleEditors = 1 << 8
}
-export type IUntypedEditorInput = IResourceEditorInput | ITextResourceEditorInput | IUntitledTextResourceEditorInput | IResourceDiffEditorInput | IResourceSideBySideEditorInput;
+export type IUntypedEditorInput = IResourceEditorInput | ITextResourceEditorInput | IUntitledTextResourceEditorInput | IResourceDiffEditorInput | IResourceSideBySideEditorInput | IResourceMergeEditorInput;
export abstract class AbstractEditorInput extends Disposable {
// Marker class for implementing `isEditorInput`
@@ -1114,11 +1160,17 @@ class EditorResourceAccessorImpl {
getOriginalUri(editor: EditorInput | IUntypedEditorInput | undefined | null): URI | undefined;
getOriginalUri(editor: EditorInput | IUntypedEditorInput | undefined | null, options: IEditorResourceAccessorOptions & { supportSideBySide?: SideBySideEditor.PRIMARY | SideBySideEditor.SECONDARY | SideBySideEditor.ANY }): URI | undefined;
getOriginalUri(editor: EditorInput | IUntypedEditorInput | undefined | null, options: IEditorResourceAccessorOptions & { supportSideBySide: SideBySideEditor.BOTH }): URI | { primary?: URI; secondary?: URI } | undefined;
+ getOriginalUri(editor: EditorInput | IUntypedEditorInput | undefined | null, options?: IEditorResourceAccessorOptions): URI | { primary?: URI; secondary?: URI } | undefined;
getOriginalUri(editor: EditorInput | IUntypedEditorInput | undefined | null, options?: IEditorResourceAccessorOptions): URI | { primary?: URI; secondary?: URI } | undefined {
if (!editor) {
return undefined;
}
+ // Merge editors are handled with `merged` result editor
+ if (isResourceMergeEditorInput(editor)) {
+ return EditorResourceAccessor.getOriginalUri(editor.result, options);
+ }
+
// Optionally support side-by-side editors
if (options?.supportSideBySide) {
const { primary, secondary } = this.getSideEditors(editor);
@@ -1136,8 +1188,8 @@ class EditorResourceAccessorImpl {
}
}
- if (isResourceDiffEditorInput(editor) || isResourceSideBySideEditorInput(editor)) {
- return;
+ if (isResourceDiffEditorInput(editor) || isResourceSideBySideEditorInput(editor) || isResourceMergeEditorInput(editor)) {
+ return undefined;
}
// Original URI is the `preferredResource` of an editor if any
@@ -1177,11 +1229,17 @@ class EditorResourceAccessorImpl {
getCanonicalUri(editor: EditorInput | IUntypedEditorInput | undefined | null): URI | undefined;
getCanonicalUri(editor: EditorInput | IUntypedEditorInput | undefined | null, options: IEditorResourceAccessorOptions & { supportSideBySide?: SideBySideEditor.PRIMARY | SideBySideEditor.SECONDARY | SideBySideEditor.ANY }): URI | undefined;
getCanonicalUri(editor: EditorInput | IUntypedEditorInput | undefined | null, options: IEditorResourceAccessorOptions & { supportSideBySide: SideBySideEditor.BOTH }): URI | { primary?: URI; secondary?: URI } | undefined;
+ getCanonicalUri(editor: EditorInput | IUntypedEditorInput | undefined | null, options?: IEditorResourceAccessorOptions): URI | { primary?: URI; secondary?: URI } | undefined;
getCanonicalUri(editor: EditorInput | IUntypedEditorInput | undefined | null, options?: IEditorResourceAccessorOptions): URI | { primary?: URI; secondary?: URI } | undefined {
if (!editor) {
return undefined;
}
+ // Merge editors are handled with `merged` result editor
+ if (isResourceMergeEditorInput(editor)) {
+ return EditorResourceAccessor.getCanonicalUri(editor.result, options);
+ }
+
// Optionally support side-by-side editors
if (options?.supportSideBySide) {
const { primary, secondary } = this.getSideEditors(editor);
@@ -1199,8 +1257,8 @@ class EditorResourceAccessorImpl {
}
}
- if (isResourceDiffEditorInput(editor) || isResourceSideBySideEditorInput(editor)) {
- return;
+ if (isResourceDiffEditorInput(editor) || isResourceSideBySideEditorInput(editor) || isResourceMergeEditorInput(editor)) {
+ return undefined;
}
// Canonical URI is the `resource` of an editor
diff --git a/src/vs/workbench/common/editor/editorInput.ts b/src/vs/workbench/common/editor/editorInput.ts
index 86b53423c60..33fb8d83824 100644
--- a/src/vs/workbench/common/editor/editorInput.ts
+++ b/src/vs/workbench/common/editor/editorInput.ts
@@ -30,10 +30,10 @@ export interface IEditorCloseHandler {
* should be used besides dirty state, this method should be
* implemented to show a different dialog.
*
- * @param editors if more than one editor is closed, will pass in
- * each editor of the same kind to be able to show a combined dialog.
+ * @param editors All editors of the same kind that are being closed. Should be used
+ * to show a combined dialog.
*/
- confirm(editors?: ReadonlyArray<IEditorIdentifier>): Promise<ConfirmResult>;
+ confirm(editors: ReadonlyArray<IEditorIdentifier>): Promise<ConfirmResult>;
}
/**
diff --git a/src/vs/workbench/common/editor/sideBySideEditorInput.ts b/src/vs/workbench/common/editor/sideBySideEditorInput.ts
index cf8aaffcc04..d36c91348a4 100644
--- a/src/vs/workbench/common/editor/sideBySideEditorInput.ts
+++ b/src/vs/workbench/common/editor/sideBySideEditorInput.ts
@@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Registry } from 'vs/platform/registry/common/platform';
-import { EditorInputCapabilities, GroupIdentifier, ISaveOptions, IRevertOptions, EditorExtensions, IEditorFactoryRegistry, IEditorSerializer, ISideBySideEditorInput, IUntypedEditorInput, isResourceSideBySideEditorInput, isDiffEditorInput, isResourceDiffEditorInput, IResourceSideBySideEditorInput, findViewStateForEditor, IMoveResult, isEditorInput, isResourceEditorInput, Verbosity } from 'vs/workbench/common/editor';
+import { EditorInputCapabilities, GroupIdentifier, ISaveOptions, IRevertOptions, EditorExtensions, IEditorFactoryRegistry, IEditorSerializer, ISideBySideEditorInput, IUntypedEditorInput, isResourceSideBySideEditorInput, isDiffEditorInput, isResourceDiffEditorInput, IResourceSideBySideEditorInput, findViewStateForEditor, IMoveResult, isEditorInput, isResourceEditorInput, Verbosity, isResourceMergeEditorInput } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
@@ -42,6 +42,9 @@ export class SideBySideEditorInput extends EditorInput implements ISideBySideEdi
capabilities |= EditorInputCapabilities.Singleton;
}
+ // Indicate we show more than one editor
+ capabilities |= EditorInputCapabilities.MultipleEditors;
+
return capabilities;
}
@@ -186,7 +189,7 @@ export class SideBySideEditorInput extends EditorInput implements ISideBySideEdi
return new SideBySideEditorInput(this.preferredName, this.preferredDescription, primarySaveResult, primarySaveResult, this.editorService);
}
- if (!isResourceDiffEditorInput(primarySaveResult) && !isResourceSideBySideEditorInput(primarySaveResult)) {
+ if (!isResourceDiffEditorInput(primarySaveResult) && !isResourceSideBySideEditorInput(primarySaveResult) && !isResourceMergeEditorInput(primarySaveResult)) {
return {
primary: primarySaveResult,
secondary: primarySaveResult,
@@ -251,7 +254,8 @@ export class SideBySideEditorInput extends EditorInput implements ISideBySideEdi
if (
primaryResourceEditorInput && secondaryResourceEditorInput &&
!isResourceDiffEditorInput(primaryResourceEditorInput) && !isResourceDiffEditorInput(secondaryResourceEditorInput) &&
- !isResourceSideBySideEditorInput(primaryResourceEditorInput) && !isResourceSideBySideEditorInput(secondaryResourceEditorInput)
+ !isResourceSideBySideEditorInput(primaryResourceEditorInput) && !isResourceSideBySideEditorInput(secondaryResourceEditorInput) &&
+ !isResourceMergeEditorInput(primaryResourceEditorInput) && !isResourceMergeEditorInput(secondaryResourceEditorInput)
) {
const untypedInput: IResourceSideBySideEditorInput = {
label: this.preferredName,
diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts
index bad36b7ccad..57a34f2c7d0 100644
--- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts
+++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts
@@ -122,11 +122,14 @@ class BulkEditPreviewContribution {
const choice = await this._dialogService.show(
Severity.Info,
localize('overlap', "Another refactoring is being previewed."),
- [localize('cancel', "Cancel"), localize('continue', "Continue")],
- { detail: localize('detail', "Press 'Continue' to discard the previous refactoring and continue with the current refactoring.") }
+ [localize('continue', "Continue"), localize('cancel', "Cancel")],
+ {
+ detail: localize('detail', "Press 'Continue' to discard the previous refactoring and continue with the current refactoring."),
+ cancelId: 1
+ }
);
- if (choice.choice === 0) {
+ if (choice.choice === 1) {
// this refactoring is being cancelled
return [];
}
diff --git a/src/vs/workbench/contrib/codeEditor/browser/untitledTextEditorHint.ts b/src/vs/workbench/contrib/codeEditor/browser/untitledTextEditorHint.ts
index 14c97806e09..bac3e753519 100644
--- a/src/vs/workbench/contrib/codeEditor/browser/untitledTextEditorHint.ts
+++ b/src/vs/workbench/contrib/codeEditor/browser/untitledTextEditorHint.ts
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as dom from 'vs/base/browser/dom';
-import { dispose, IDisposable } from 'vs/base/common/lifecycle';
+import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle';
import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser';
import { localize } from 'vs/nls';
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
@@ -17,9 +17,10 @@ import { Schemas } from 'vs/base/common/network';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions';
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
-import { EventType as GestureEventType, Gesture } from 'vs/base/browser/touch';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
+import { IContentActionHandler, renderFormattedText } from 'vs/base/browser/formattedTextRenderer';
+import { SelectSnippetForEmptyFile } from 'vs/workbench/contrib/snippets/browser/commands/emptyFileSnippets';
const $ = dom.$;
@@ -70,7 +71,7 @@ class UntitledTextEditorHintContentWidget implements IContentWidget {
private static readonly ID = 'editor.widget.untitledHint';
private domNode: HTMLElement | undefined;
- private toDispose: IDisposable[];
+ private toDispose: DisposableStore;
constructor(
private readonly editor: ICodeEditor,
@@ -79,9 +80,9 @@ class UntitledTextEditorHintContentWidget implements IContentWidget {
private readonly configurationService: IConfigurationService,
private readonly keybindingService: IKeybindingService,
) {
- this.toDispose = [];
- this.toDispose.push(editor.onDidChangeModelContent(() => this.onDidChangeModelContent()));
- this.toDispose.push(this.editor.onDidChangeConfiguration((e: ConfigurationChangedEvent) => {
+ this.toDispose = new DisposableStore();
+ this.toDispose.add(editor.onDidChangeModelContent(() => this.onDidChangeModelContent()));
+ this.toDispose.add(this.editor.onDidChangeConfiguration((e: ConfigurationChangedEvent) => {
if (this.domNode && e.hasChanged(EditorOption.fontInfo)) {
this.editor.applyFontInfo(this.domNode);
}
@@ -107,49 +108,43 @@ class UntitledTextEditorHintContentWidget implements IContentWidget {
this.domNode = $('.untitled-hint');
this.domNode.style.width = 'max-content';
- const language = $('a.language-mode');
- language.style.cursor = 'pointer';
- language.innerText = localize('selectAlanguage2', "Select a language");
- const languageKeyBinding = this.keybindingService.lookupKeybinding(ChangeLanguageAction.ID);
- const languageKeybindingLabel = languageKeyBinding?.getLabel();
- if (languageKeybindingLabel) {
- language.title = localize('keyboardBindingTooltip', "{0}", languageKeybindingLabel);
- }
- this.domNode.appendChild(language);
-
- const or = $('span');
- or.innerText = localize('or', " or ",);
- this.domNode.appendChild(or);
-
- const editorType = $('a.editor-type');
- editorType.style.cursor = 'pointer';
- editorType.innerText = localize('openADifferentEditor', "open a different editor");
- const selectEditorTypeKeyBinding = this.keybindingService.lookupKeybinding('welcome.showNewFileEntries');
- const selectEditorTypeKeybindingLabel = selectEditorTypeKeyBinding?.getLabel();
- if (selectEditorTypeKeybindingLabel) {
- editorType.title = localize('keyboardBindingTooltip', "{0}", selectEditorTypeKeybindingLabel);
- }
- this.domNode.appendChild(editorType);
-
- const toGetStarted = $('span');
- toGetStarted.innerText = localize('toGetStarted', " to get started.");
- this.domNode.appendChild(toGetStarted);
-
- this.domNode.appendChild($('br'));
-
- const startTyping = $('span');
- startTyping.innerText = localize('startTyping', "Start typing to dismiss or ");
- this.domNode.appendChild(startTyping);
+ const hintMsg = localize({ key: 'message', comment: ['Presereve double-square brackets and their order'] }, '[[Select a language]], [[start with a snippet]], or [[open a different editor]] to get started.\nStart typing to dismiss or [[don\'t show]] this again.');
+ const hintHandler: IContentActionHandler = {
+ disposables: this.toDispose,
+ callback: (index, event) => {
+ switch (index) {
+ case '0':
+ languageOnClickOrTap(event.browserEvent);
+ break;
+ case '1':
+ snippetOnClickOrTab(event.browserEvent);
+ break;
+ case '2':
+ chooseEditorOnClickOrTap(event.browserEvent);
+ break;
+ case '3':
+ dontShowOnClickOrTap();
+ break;
+ }
+ }
+ };
- const dontShow = $('a');
- dontShow.style.cursor = 'pointer';
- dontShow.innerText = localize('dontshow', "don't show");
- this.domNode.appendChild(dontShow);
+ const hintElement = renderFormattedText(hintMsg, {
+ actionHandler: hintHandler,
+ renderCodeSegments: false,
+ });
+ this.domNode.append(hintElement);
+
+ // ugly way to associate keybindings...
+ const keybindingsLookup = [ChangeLanguageAction.ID, SelectSnippetForEmptyFile.Id, 'welcome.showNewFileEntries'];
+ for (const anchor of hintElement.querySelectorAll('A')) {
+ (<HTMLAnchorElement>anchor).style.cursor = 'pointer';
+ const id = keybindingsLookup.shift();
+ const title = id && this.keybindingService.lookupKeybinding(id)?.getLabel();
+ (<HTMLAnchorElement>anchor).title = title ?? '';
+ }
- const thisAgain = $('span');
- thisAgain.innerText = localize('thisAgain', " this again.");
- this.domNode.appendChild(thisAgain);
- this.toDispose.push(Gesture.addTarget(this.domNode));
+ // the actual command handlers...
const languageOnClickOrTap = async (e: MouseEvent) => {
e.stopPropagation();
// Need to focus editor before so current editor becomes active and the command is properly executed
@@ -157,9 +152,12 @@ class UntitledTextEditorHintContentWidget implements IContentWidget {
await this.commandService.executeCommand(ChangeLanguageAction.ID, { from: 'hint' });
this.editor.focus();
};
- this.toDispose.push(dom.addDisposableListener(language, 'click', languageOnClickOrTap));
- this.toDispose.push(dom.addDisposableListener(language, GestureEventType.Tap, languageOnClickOrTap));
- this.toDispose.push(Gesture.addTarget(language));
+
+ const snippetOnClickOrTab = async (e: MouseEvent) => {
+ e.stopPropagation();
+ this.editor.focus();
+ this.commandService.executeCommand(SelectSnippetForEmptyFile.Id, { from: 'hint' });
+ };
const chooseEditorOnClickOrTap = async (e: MouseEvent) => {
e.stopPropagation();
@@ -172,20 +170,14 @@ class UntitledTextEditorHintContentWidget implements IContentWidget {
this.editorGroupsService.activeGroup.closeEditor(activeEditorInput, { preserveFocus: true });
}
};
- this.toDispose.push(dom.addDisposableListener(editorType, 'click', chooseEditorOnClickOrTap));
- this.toDispose.push(dom.addDisposableListener(editorType, GestureEventType.Tap, chooseEditorOnClickOrTap));
- this.toDispose.push(Gesture.addTarget(editorType));
const dontShowOnClickOrTap = () => {
this.configurationService.updateValue(untitledTextEditorHintSetting, 'hidden');
this.dispose();
this.editor.focus();
};
- this.toDispose.push(dom.addDisposableListener(dontShow, 'click', dontShowOnClickOrTap));
- this.toDispose.push(dom.addDisposableListener(dontShow, GestureEventType.Tap, dontShowOnClickOrTap));
- this.toDispose.push(Gesture.addTarget(dontShow));
- this.toDispose.push(dom.addDisposableListener(this.domNode, 'click', () => {
+ this.toDispose.add(dom.addDisposableListener(this.domNode, 'click', () => {
this.editor.focus();
}));
diff --git a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts
index 020ef7eb4d1..42ebc26458d 100644
--- a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts
+++ b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts
@@ -13,8 +13,6 @@ import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels';
import { CommentNode, CommentsModel, ResourceWithCommentThreads } from 'vs/workbench/contrib/comments/common/commentModel';
import { IAsyncDataSource, ITreeNode } from 'vs/base/browser/ui/tree/tree';
import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list';
-import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
-import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { WorkbenchAsyncDataTree, IListService, IWorkbenchAsyncDataTreeOptions } from 'vs/platform/list/browser/listService';
@@ -266,8 +264,6 @@ export class CommentsList extends WorkbenchAsyncDataTree<any, any> {
@IThemeService themeService: IThemeService,
@IInstantiationService instantiationService: IInstantiationService,
@IConfigurationService configurationService: IConfigurationService,
- @IKeybindingService keybindingService: IKeybindingService,
- @IAccessibilityService accessibilityService: IAccessibilityService
) {
const delegate = new CommentsModelVirualDelegate();
const dataSource = new CommentsAsyncDataSource();
@@ -311,12 +307,11 @@ export class CommentsList extends WorkbenchAsyncDataTree<any, any> {
},
overrideStyles: options.overrideStyles
},
+ instantiationService,
contextKeyService,
listService,
themeService,
- configurationService,
- keybindingService,
- accessibilityService
+ configurationService
);
}
}
diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts
index 596bbd9c32d..92b3b970f07 100644
--- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts
+++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts
@@ -304,27 +304,27 @@ CommandsRegistry.registerCommand({
CommandsRegistry.registerCommand({
id: REVERSE_CONTINUE_ID,
- handler: (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
- getThreadAndRun(accessor, context, thread => thread.reverseContinue());
+ handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
+ await getThreadAndRun(accessor, context, thread => thread.reverseContinue());
}
});
CommandsRegistry.registerCommand({
id: STEP_BACK_ID,
- handler: (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
+ handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
const contextKeyService = accessor.get(IContextKeyService);
if (CONTEXT_DISASSEMBLY_VIEW_FOCUS.getValue(contextKeyService)) {
- getThreadAndRun(accessor, context, (thread: IThread) => thread.stepBack('instruction'));
+ await getThreadAndRun(accessor, context, (thread: IThread) => thread.stepBack('instruction'));
} else {
- getThreadAndRun(accessor, context, (thread: IThread) => thread.stepBack());
+ await getThreadAndRun(accessor, context, (thread: IThread) => thread.stepBack());
}
}
});
CommandsRegistry.registerCommand({
id: TERMINATE_THREAD_ID,
- handler: (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
- getThreadAndRun(accessor, context, thread => thread.terminate());
+ handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
+ await getThreadAndRun(accessor, context, thread => thread.terminate());
}
});
@@ -467,12 +467,12 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
weight: KeybindingWeight.WorkbenchContrib,
primary: isWeb ? (KeyMod.Alt | KeyCode.F10) : KeyCode.F10, // Browsers do not allow F10 to be binded so we have to bind an alternative
when: CONTEXT_DEBUG_STATE.isEqualTo('stopped'),
- handler: (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
+ handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
const contextKeyService = accessor.get(IContextKeyService);
if (CONTEXT_DISASSEMBLY_VIEW_FOCUS.getValue(contextKeyService)) {
- getThreadAndRun(accessor, context, (thread: IThread) => thread.next('instruction'));
+ await getThreadAndRun(accessor, context, (thread: IThread) => thread.next('instruction'));
} else {
- getThreadAndRun(accessor, context, (thread: IThread) => thread.next());
+ await getThreadAndRun(accessor, context, (thread: IThread) => thread.next());
}
}
});
@@ -486,12 +486,12 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
primary: STEP_INTO_KEYBINDING,
// Use a more flexible when clause to not allow full screen command to take over when F11 pressed a lot of times
when: CONTEXT_DEBUG_STATE.notEqualsTo('inactive'),
- handler: (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
+ handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
const contextKeyService = accessor.get(IContextKeyService);
if (CONTEXT_DISASSEMBLY_VIEW_FOCUS.getValue(contextKeyService)) {
- getThreadAndRun(accessor, context, (thread: IThread) => thread.stepIn('instruction'));
+ await getThreadAndRun(accessor, context, (thread: IThread) => thread.stepIn('instruction'));
} else {
- getThreadAndRun(accessor, context, (thread: IThread) => thread.stepIn());
+ await getThreadAndRun(accessor, context, (thread: IThread) => thread.stepIn());
}
}
});
@@ -501,12 +501,12 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyMod.Shift | KeyCode.F11,
when: CONTEXT_DEBUG_STATE.isEqualTo('stopped'),
- handler: (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
+ handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
const contextKeyService = accessor.get(IContextKeyService);
if (CONTEXT_DISASSEMBLY_VIEW_FOCUS.getValue(contextKeyService)) {
- getThreadAndRun(accessor, context, (thread: IThread) => thread.stepOut('instruction'));
+ await getThreadAndRun(accessor, context, (thread: IThread) => thread.stepOut('instruction'));
} else {
- getThreadAndRun(accessor, context, (thread: IThread) => thread.stepOut());
+ await getThreadAndRun(accessor, context, (thread: IThread) => thread.stepOut());
}
}
});
@@ -516,8 +516,8 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
weight: KeybindingWeight.WorkbenchContrib + 2, // take priority over focus next part while we are debugging
primary: KeyCode.F6,
when: CONTEXT_DEBUG_STATE.isEqualTo('running'),
- handler: (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
- getThreadAndRun(accessor, context, thread => thread.pause());
+ handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
+ await getThreadAndRun(accessor, context, thread => thread.pause());
}
});
@@ -649,17 +649,15 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
weight: KeybindingWeight.WorkbenchContrib + 10, // Use a stronger weight to get priority over start debugging F5 shortcut
primary: KeyCode.F5,
when: CONTEXT_DEBUG_STATE.isEqualTo('stopped'),
- handler: (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
- getThreadAndRun(accessor, context, thread => thread.continue());
+ handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
+ await getThreadAndRun(accessor, context, thread => thread.continue());
}
});
CommandsRegistry.registerCommand({
id: SHOW_LOADED_SCRIPTS_ID,
handler: async (accessor) => {
-
await showLoadedScriptMenu(accessor);
-
}
});
@@ -917,7 +915,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
const launch = manager.getLaunches().find(l => l.uri.toString() === launchUri) || manager.selectedConfiguration.launch;
if (launch) {
- const { editor, created } = await launch.openConfigFile(false);
+ const { editor, created } = await launch.openConfigFile({ preserveFocus: false });
if (editor && !created) {
const codeEditor = <ICodeEditor>editor.getControl();
if (codeEditor) {
diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts
index 0245dbd5a2d..a00d75ef26a 100644
--- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts
+++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts
@@ -215,7 +215,7 @@ export class ConfigurationManager implements IConfigurationManager {
disposables.add(input.onDidTriggerItemButton(async (context) => {
resolve(undefined);
const { launch, config } = context.item;
- await launch.openConfigFile(false, config.type);
+ await launch.openConfigFile({ preserveFocus: false, type: config.type });
// Only Launch have a pin trigger button
await (launch as Launch).writeConfiguration(config);
await this.selectConfiguration(launch, config.name);
@@ -521,11 +521,13 @@ abstract class AbstractLaunch {
return configuration;
}
- async getInitialConfigurationContent(folderUri?: uri, type?: string, token?: CancellationToken): Promise<string> {
+ async getInitialConfigurationContent(folderUri?: uri, type?: string, useInitialConfigs?: boolean, token?: CancellationToken): Promise<string> {
let content = '';
const adapter = type ? this.adapterManager.getEnabledDebugger(type) : await this.adapterManager.guessDebugger(true);
if (adapter) {
- const initialConfigs = await this.configurationManager.provideDebugConfigurations(folderUri, adapter.type, token || CancellationToken.None);
+ const initialConfigs = useInitialConfigs ?
+ await this.configurationManager.provideDebugConfigurations(folderUri, adapter.type, token || CancellationToken.None) :
+ [];
content = await adapter.getInitialConfigurationContent(initialConfigs);
}
return content;
@@ -562,7 +564,7 @@ class Launch extends AbstractLaunch implements ILaunch {
return this.configurationService.inspect<IGlobalConfig>('launch', { resource: this.workspace.uri }).workspaceFolderValue;
}
- async openConfigFile(preserveFocus: boolean, type?: string, token?: CancellationToken): Promise<{ editor: IEditorPane | null; created: boolean }> {
+ async openConfigFile({ preserveFocus, type, useInitialConfigs }: { preserveFocus: boolean; type?: string; useInitialConfigs?: boolean }, token?: CancellationToken): Promise<{ editor: IEditorPane | null; created: boolean }> {
const resource = this.uri;
let created = false;
let content = '';
@@ -571,7 +573,7 @@ class Launch extends AbstractLaunch implements ILaunch {
content = fileContent.value.toString();
} catch {
// launch.json not found: create one by collecting launch configs from debugConfigProviders
- content = await this.getInitialConfigurationContent(this.workspace.uri, type, token);
+ content = await this.getInitialConfigurationContent(this.workspace.uri, type, useInitialConfigs, token);
if (!content) {
// Cancelled
return { editor: null, created: false };
@@ -647,11 +649,11 @@ class WorkspaceLaunch extends AbstractLaunch implements ILaunch {
return this.configurationService.inspect<IGlobalConfig>('launch').workspaceValue;
}
- async openConfigFile(preserveFocus: boolean, type?: string, token?: CancellationToken): Promise<{ editor: IEditorPane | null; created: boolean }> {
+ async openConfigFile({ preserveFocus, type, useInitialConfigs }: { preserveFocus: boolean; type?: string; useInitialConfigs?: boolean }, token?: CancellationToken): Promise<{ editor: IEditorPane | null; created: boolean }> {
const launchExistInFile = !!this.getConfig();
if (!launchExistInFile) {
// Launch property in workspace config not found: create one by collecting launch configs from debugConfigProviders
- const content = await this.getInitialConfigurationContent(undefined, type, token);
+ const content = await this.getInitialConfigurationContent(undefined, type, useInitialConfigs, token);
if (content) {
await this.configurationService.updateValue('launch', json.parse(content), ConfigurationTarget.WORKSPACE);
} else {
@@ -702,7 +704,7 @@ class UserLaunch extends AbstractLaunch implements ILaunch {
return this.configurationService.inspect<IGlobalConfig>('launch').userValue;
}
- async openConfigFile(preserveFocus: boolean): Promise<{ editor: IEditorPane | null; created: boolean }> {
+ async openConfigFile({ preserveFocus, type, useInitialContent }: { preserveFocus: boolean; type?: string; useInitialContent?: boolean }): Promise<{ editor: IEditorPane | null; created: boolean }> {
const editor = await this.preferencesService.openUserSettings({ jsonEditor: true, preserveFocus, revealSetting: { key: 'launch' } });
return ({
editor: withUndefinedAsNull(editor),
diff --git a/src/vs/workbench/contrib/debug/browser/debugHover.ts b/src/vs/workbench/contrib/debug/browser/debugHover.ts
index 21d65a90cb3..6308c2b6c5b 100644
--- a/src/vs/workbench/contrib/debug/browser/debugHover.ts
+++ b/src/vs/workbench/contrib/debug/browser/debugHover.ts
@@ -116,8 +116,6 @@ export class DebugHoverWidget implements IContentWidget {
horizontalScrolling: true,
useShadows: false,
keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (e: IExpression) => e.name },
- filterOnType: false,
- simpleKeyboardNavigation: true,
overrideStyles: {
listBackground: editorHoverBackground
}
diff --git a/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts b/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts
index df5f79031ea..6cd33d34f5c 100644
--- a/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts
+++ b/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts
@@ -63,7 +63,7 @@ export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider<IPi
tooltip: localize('customizeLaunchConfig', "Configure Launch Configuration")
}],
trigger: () => {
- config.launch.openConfigFile(false);
+ config.launch.openConfigFile({ preserveFocus: false, useInitialConfigs: false });
return TriggerAction.CLOSE_PICKER;
},
diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts
index 2474922a5ad..f6db1acdb80 100644
--- a/src/vs/workbench/contrib/debug/browser/debugService.ts
+++ b/src/vs/workbench/contrib/debug/browser/debugService.ts
@@ -474,7 +474,7 @@ export class DebugService implements IDebugService {
const cfg = await this.configurationManager.resolveDebugConfigurationWithSubstitutedVariables(launch && launch.workspace ? launch.workspace.uri : undefined, type, resolvedConfig, initCancellationToken.token);
if (!cfg) {
if (launch && type && cfg === null && !initCancellationToken.token.isCancellationRequested) { // show launch.json only for "config" being "null".
- await launch.openConfigFile(true, type, initCancellationToken.token);
+ await launch.openConfigFile({ preserveFocus: true, type }, initCancellationToken.token);
}
return false;
}
@@ -526,7 +526,7 @@ export class DebugService implements IDebugService {
await this.showError(nls.localize('noFolderWorkspaceDebugError', "The active file can not be debugged. Make sure it is saved and that you have a debug extension installed for that file type."));
}
if (launch && !initCancellationToken.token.isCancellationRequested) {
- await launch.openConfigFile(true, undefined, initCancellationToken.token);
+ await launch.openConfigFile({ preserveFocus: true }, initCancellationToken.token);
}
return false;
@@ -534,7 +534,7 @@ export class DebugService implements IDebugService {
}
if (launch && type && configByProviders === null && !initCancellationToken.token.isCancellationRequested) { // show launch.json only for "config" being "null".
- await launch.openConfigFile(true, type, initCancellationToken.token);
+ await launch.openConfigFile({ preserveFocus: true, type }, initCancellationToken.token);
}
return false;
diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts
index ae38d059d88..ca8b89ac152 100644
--- a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts
+++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts
@@ -231,7 +231,7 @@ registerAction2(class extends Action2 {
}
if (launch) {
- await launch.openConfigFile(false);
+ await launch.openConfigFile({ preserveFocus: false });
}
}
});
diff --git a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts
index 1dc13b23fb7..5f9b1975001 100644
--- a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts
+++ b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts
@@ -39,6 +39,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IPathService } from 'vs/workbench/services/path/common/pathService';
+import { TreeFindMode } from 'vs/base/browser/ui/tree/abstractTree';
const NEW_STYLE_COMPRESS = true;
@@ -585,8 +586,8 @@ export class LoadedScriptsView extends ViewPane {
// feature: expand all nodes when filtering (not when finding)
let viewState: IViewState | undefined;
- this._register(this.tree.onDidChangeTypeFilterPattern(pattern => {
- if (!this.tree.options.filterOnType) {
+ this._register(this.tree.onDidChangeFindPattern(pattern => {
+ if (this.tree.findMode === TreeFindMode.Highlight) {
return;
}
diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts
index 21dc92d475c..ca1c5e318d2 100644
--- a/src/vs/workbench/contrib/debug/common/debug.ts
+++ b/src/vs/workbench/contrib/debug/common/debug.ts
@@ -927,7 +927,7 @@ export interface ILaunch {
/**
* Opens the launch.json file. Creates if it does not exist.
*/
- openConfigFile(preserveFocus: boolean, type?: string, token?: CancellationToken): Promise<{ editor: IEditorPane | null; created: boolean }>;
+ openConfigFile(options: { preserveFocus: boolean; type?: string; useInitialConfigs?: boolean }, token?: CancellationToken): Promise<{ editor: IEditorPane | null; created: boolean }>;
}
// Debug service interfaces
diff --git a/src/vs/workbench/contrib/deprecatedExtensionMigrator/browser/deprecatedExtensionMigrator.contribution.ts b/src/vs/workbench/contrib/deprecatedExtensionMigrator/browser/deprecatedExtensionMigrator.contribution.ts
new file mode 100644
index 00000000000..3abb2f55315
--- /dev/null
+++ b/src/vs/workbench/contrib/deprecatedExtensionMigrator/browser/deprecatedExtensionMigrator.contribution.ts
@@ -0,0 +1,103 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { Action } from 'vs/base/common/actions';
+import { onUnexpectedError } from 'vs/base/common/errors';
+import { isDefined } from 'vs/base/common/types';
+import { localize } from 'vs/nls';
+import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
+import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
+import { IOpenerService } from 'vs/platform/opener/common/opener';
+import { Registry } from 'vs/platform/registry/common/platform';
+import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
+import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
+import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions';
+import { EnablementState } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
+import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
+
+class DeprecatedExtensionMigratorContribution {
+ constructor(
+ @IConfigurationService private readonly configurationService: IConfigurationService,
+ @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
+ @IStorageService private readonly storageService: IStorageService,
+ @INotificationService private readonly notificationService: INotificationService,
+ @IOpenerService private readonly openerService: IOpenerService
+ ) {
+ this.init().catch(onUnexpectedError);
+ }
+
+ private async init(): Promise<void> {
+ const bracketPairColorizerId = 'coenraads.bracket-pair-colorizer';
+
+ await this.extensionsWorkbenchService.queryLocal();
+ const extension = this.extensionsWorkbenchService.installed.find(e => e.identifier.id === bracketPairColorizerId);
+ if (
+ !extension ||
+ ((extension.enablementState !== EnablementState.EnabledGlobally) &&
+ (extension.enablementState !== EnablementState.EnabledWorkspace))
+ ) {
+ return;
+ }
+
+ const state = await this.getState();
+ const disablementLogEntry = state.disablementLog.some(d => d.extensionId === bracketPairColorizerId);
+
+ if (disablementLogEntry) {
+ return;
+ }
+
+ state.disablementLog.push({ extensionId: bracketPairColorizerId, disablementDateTime: new Date().getTime() });
+ await this.setState(state);
+
+ await this.extensionsWorkbenchService.setEnablement(extension, EnablementState.DisabledGlobally);
+
+ const nativeBracketPairColorizationEnabledKey = 'editor.bracketPairColorization.enabled';
+ const bracketPairColorizationEnabled = !!this.configurationService.inspect(nativeBracketPairColorizationEnabledKey).user;
+
+ this.notificationService.notify({
+ message: localize('bracketPairColorizer.notification', "The extension 'Bracket pair Colorizer' got disabled because it was deprecated."),
+ severity: Severity.Info,
+ actions: {
+ primary: [
+ new Action('', localize('bracketPairColorizer.notification.action.uninstall', "Uninstall Extension"), undefined, undefined, () => {
+ this.extensionsWorkbenchService.uninstall(extension);
+ }),
+ ],
+ secondary: [
+ !bracketPairColorizationEnabled ? new Action('', localize('bracketPairColorizer.notification.action.enableNative', "Enable Native Bracket Pair Colorization"), undefined, undefined, () => {
+ this.configurationService.updateValue(nativeBracketPairColorizationEnabledKey, true, ConfigurationTarget.USER);
+ }) : undefined,
+ new Action('', localize('bracketPairColorizer.notification.action.showMoreInfo', "More Info"), undefined, undefined, () => {
+ this.openerService.open('https://github.com/microsoft/vscode/issues/155179');
+ }),
+ ].filter(isDefined),
+ }
+ });
+ }
+
+ private readonly storageKey = 'deprecatedExtensionMigrator.state';
+
+ private async getState(): Promise<State> {
+ const jsonStr = await this.storageService.get(this.storageKey, StorageScope.APPLICATION, '');
+ if (jsonStr === '') {
+ return { disablementLog: [] };
+ }
+ return JSON.parse(jsonStr) as State;
+ }
+
+ private async setState(state: State): Promise<void> {
+ const json = JSON.stringify(state);
+ await this.storageService.store(this.storageKey, json, StorageScope.APPLICATION, StorageTarget.USER);
+ }
+}
+
+interface State {
+ disablementLog: {
+ extensionId: string;
+ disablementDateTime: number;
+ }[];
+}
+
+Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DeprecatedExtensionMigratorContribution, LifecyclePhase.Restored);
diff --git a/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts
index 12f620df779..9be6a5124f7 100644
--- a/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts
+++ b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts
@@ -85,6 +85,12 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
super();
if (this.environmentService.editSessionId !== undefined) {
+ type ResumeEvent = {};
+ type ResumeClassification = {
+ owner: 'joyceerhl'; comment: 'Reporting when an action is resumed from an edit session identifier.';
+ };
+ this.telemetryService.publicLog2<ResumeEvent, ResumeClassification>('editSessions.continue.resume');
+
void this.resumeEditSession(this.environmentService.editSessionId).finally(() => this.environmentService.editSessionId = undefined);
}
@@ -107,7 +113,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
}
const commands = new Map((extension.description.contributes?.commands ?? []).map(c => [c.command, c]));
for (const contribution of extension.value) {
- if (!contribution.command || !contribution.group || !contribution.when) {
+ if (!contribution.command || !contribution.when) {
continue;
}
const fullCommand = commands.get(contribution.command);
@@ -148,6 +154,12 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
}
async run(accessor: ServicesAccessor, workspaceUri: URI | undefined): Promise<void> {
+ type ContinueEditSessionEvent = {};
+ type ContinueEditSessionClassification = {
+ owner: 'joyceerhl'; comment: 'Reporting when the continue edit session action is run.';
+ };
+ that.telemetryService.publicLog2<ContinueEditSessionEvent, ContinueEditSessionClassification>('editSessions.continue.store');
+
let uri = workspaceUri ?? await that.pickContinueEditSessionDestination();
if (uri === undefined) { return; }
@@ -187,7 +199,15 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
await that.progressService.withProgress({
location: ProgressLocation.Notification,
title: localize('resuming edit session', 'Resuming edit session...')
- }, async () => await that.resumeEditSession());
+ }, async () => {
+ type ResumeEvent = {};
+ type ResumeClassification = {
+ owner: 'joyceerhl'; comment: 'Reporting when the resume edit session action is invoked.';
+ };
+ that.telemetryService.publicLog2<ResumeEvent, ResumeClassification>('editSessions.resume');
+
+ await that.resumeEditSession();
+ });
}
}));
}
@@ -208,7 +228,15 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
await that.progressService.withProgress({
location: ProgressLocation.Notification,
title: localize('storing edit session', 'Storing edit session...')
- }, async () => await that.storeEditSession(true));
+ }, async () => {
+ type StoreEvent = {};
+ type StoreClassification = {
+ owner: 'joyceerhl'; comment: 'Reporting when the store edit session action is invoked.';
+ };
+ that.telemetryService.publicLog2<StoreEvent, StoreClassification>('editSessions.store');
+
+ await that.storeEditSession(true);
+ });
}
}));
}
@@ -242,7 +270,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
const folderRoot = this.contextService.getWorkspace().folders.find((f) => f.name === folder.name);
if (!folderRoot) {
this.logService.info(`Skipping applying ${folder.workingChanges.length} changes from edit session with ref ${ref} as no corresponding workspace folder named ${folder.name} is currently open.`);
- continue;
+ return;
}
for (const repository of this.scmService.repositories) {
diff --git a/src/vs/workbench/contrib/experiments/common/experimentService.ts b/src/vs/workbench/contrib/experiments/common/experimentService.ts
index 235217dbc64..297b08a29af 100644
--- a/src/vs/workbench/contrib/experiments/common/experimentService.ts
+++ b/src/vs/workbench/contrib/experiments/common/experimentService.ts
@@ -307,7 +307,8 @@ export class ExperimentService extends Disposable implements IExperimentService
return Promise.all(promises).then(() => {
type ExperimentsClassification = {
owner: 'sbatten';
- experiments: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
+ comment: 'Information about the experiments in this session';
+ experiments: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The list of experiments in this session' };
};
this.telemetryService.publicLog2<{ experiments: string[] }, ExperimentsClassification>('experiments', { experiments: this._experiments.map(e => e.id) });
});
diff --git a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts
index 2dbfdafd694..7c111e51eda 100644
--- a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts
+++ b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts
@@ -6,14 +6,13 @@
import * as assert from 'assert';
import * as sinon from 'sinon';
import { timeout } from 'vs/base/common/async';
-import { Emitter } from 'vs/base/common/event';
+import { Emitter, Event } from 'vs/base/common/event';
import { OS } from 'vs/base/common/platform';
import { URI } from 'vs/base/common/uri';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
-import { DidUninstallExtensionEvent, IExtensionIdentifier, IExtensionManagementService, ILocalExtension, InstallExtensionEvent, InstallExtensionResult } from 'vs/platform/extensionManagement/common/extensionManagement';
+import { DidUninstallExtensionEvent, IExtensionIdentifier, ILocalExtension, InstallExtensionEvent, InstallExtensionResult } from 'vs/platform/extensionManagement/common/extensionManagement';
import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
-import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { IProductService } from 'vs/platform/product/common/productService';
@@ -26,7 +25,8 @@ import { IURLService } from 'vs/platform/url/common/url';
import { NativeURLService } from 'vs/platform/url/common/urlService';
import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust';
import { currentSchemaVersion, ExperimentActionType, ExperimentService, ExperimentState, getCurrentActivationRecord, IExperiment } from 'vs/workbench/contrib/experiments/common/experimentService';
-import { IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
+import { IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
+import { ExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagementService';
import { TestExtensionEnablementService } from 'vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test';
import { IExtensionService, IWillActivateEvent } from 'vs/workbench/services/extensions/common/extensions';
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
@@ -84,15 +84,16 @@ suite('Experiment Service', () => {
instantiationService.stub(IExtensionService, TestExtensionService);
instantiationService.stub(IExtensionService, 'onWillActivateByEvent', activationEvent.event);
instantiationService.stub(IUriIdentityService, UriIdentityService);
- instantiationService.stub(IExtensionManagementService, ExtensionManagementService);
- instantiationService.stub(IExtensionManagementService, 'onInstallExtension', installEvent.event);
- instantiationService.stub(IExtensionManagementService, 'onDidInstallExtensions', didInstallEvent.event);
- instantiationService.stub(IExtensionManagementService, 'onUninstallExtension', uninstallEvent.event);
- instantiationService.stub(IExtensionManagementService, 'onDidUninstallExtension', didUninstallEvent.event);
+ instantiationService.stub(IWorkbenchExtensionManagementService, ExtensionManagementService);
+ instantiationService.stub(IWorkbenchExtensionManagementService, 'onInstallExtension', installEvent.event);
+ instantiationService.stub(IWorkbenchExtensionManagementService, 'onDidInstallExtensions', didInstallEvent.event);
+ instantiationService.stub(IWorkbenchExtensionManagementService, 'onUninstallExtension', uninstallEvent.event);
+ instantiationService.stub(IWorkbenchExtensionManagementService, 'onDidUninstallExtension', didUninstallEvent.event);
+ instantiationService.stub(IWorkbenchExtensionManagementService, 'onDidChangeProfileExtensions', Event.None);
instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
instantiationService.stub(ITelemetryService, NullTelemetryService);
instantiationService.stub(IURLService, NativeURLService);
- instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]);
+ instantiationService.stubPromise(IWorkbenchExtensionManagementService, 'getInstalled', [local]);
testConfigurationService = new TestConfigurationService();
instantiationService.stub(IConfigurationService, testConfigurationService);
instantiationService.stub(ILifecycleService, new TestLifecycleService());
diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts
index c8632a19ecb..d155971ec3a 100644
--- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts
+++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts
@@ -29,7 +29,7 @@ import {
UpdateAction, ReloadAction, EnableDropDownAction, DisableDropDownAction, ExtensionStatusLabelAction, SetFileIconThemeAction, SetColorThemeAction,
RemoteInstallAction, ExtensionStatusAction, LocalInstallAction, ToggleSyncExtensionAction, SetProductIconThemeAction,
ActionWithDropDownAction, InstallDropdownAction, InstallingLabelAction, UninstallAction, ExtensionActionWithDropdownActionViewItem, ExtensionDropDownAction,
- InstallAnotherVersionAction, ExtensionEditorManageExtensionAction, WebInstallAction, SwitchToPreReleaseVersionAction, SwitchToReleasedVersionAction, MigrateDeprecatedExtensionAction, SetLanguageAction
+ InstallAnotherVersionAction, ExtensionEditorManageExtensionAction, WebInstallAction, SwitchToPreReleaseVersionAction, SwitchToReleasedVersionAction, MigrateDeprecatedExtensionAction, SetLanguageAction, ClearLanguageAction
} from 'vs/workbench/contrib/extensions/browser/extensionsActions';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
@@ -326,10 +326,11 @@ export class ExtensionEditor extends EditorPane {
this.instantiationService.createInstance(SetColorThemeAction),
this.instantiationService.createInstance(SetFileIconThemeAction),
this.instantiationService.createInstance(SetProductIconThemeAction),
+ this.instantiationService.createInstance(SetLanguageAction),
+ this.instantiationService.createInstance(ClearLanguageAction),
this.instantiationService.createInstance(EnableDropDownAction),
this.instantiationService.createInstance(DisableDropDownAction),
- this.instantiationService.createInstance(SetLanguageAction),
this.instantiationService.createInstance(RemoteInstallAction, false),
this.instantiationService.createInstance(LocalInstallAction),
this.instantiationService.createInstance(WebInstallAction),
diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts
index f53f6a7de8a..13edddae38d 100644
--- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts
+++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts
@@ -15,7 +15,7 @@ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWo
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 { ReinstallAction, InstallSpecificVersionOfExtensionAction, ConfigureWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, PromptExtensionInstallFailureAction, SearchExtensionsAction, SwitchToPreReleaseVersionAction, SwitchToReleasedVersionAction, SetColorThemeAction, SetFileIconThemeAction, SetProductIconThemeAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
+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';
@@ -1339,6 +1339,24 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi
}
}
});
+ this.registerExtensionAction({
+ id: ClearLanguageAction.ID,
+ title: ClearLanguageAction.TITLE,
+ menu: {
+ id: MenuId.ExtensionContext,
+ group: INSTALL_ACTIONS_GROUP,
+ order: 0,
+ when: ContextKeyExpr.and(ContextKeyExpr.not('inExtensionEditor'), ContextKeyExpr.has('canSetLanguage'), ContextKeyExpr.has('isActiveLanguagePackExtension'))
+ },
+ run: async (accessor: ServicesAccessor, extensionId: string) => {
+ const instantiationService = accessor.get(IInstantiationService);
+ const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService);
+ const extension = (await extensionsWorkbenchService.getExtensions([{ id: extensionId }], CancellationToken.None))[0];
+ const action = instantiationService.createInstance(ClearLanguageAction);
+ action.extension = extension;
+ return action.run();
+ }
+ });
this.registerExtensionAction({
id: 'workbench.extensions.action.copyExtension',
diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts
index 23ddb1a771c..d2247bf2519 100644
--- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts
+++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts
@@ -67,6 +67,7 @@ import { flatten } from 'vs/base/common/arrays';
import { fromNow } from 'vs/base/common/date';
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
import { ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks';
+import { ILocaleService } from 'vs/workbench/contrib/localization/common/locale';
export class PromptExtensionInstallFailureAction extends Action {
@@ -981,6 +982,8 @@ export class DropDownMenuActionViewItem extends ActionViewItem {
async function getContextMenuActionsGroups(extension: IExtension | undefined | null, contextKeyService: IContextKeyService, instantiationService: IInstantiationService): Promise<[string, Array<MenuItemAction | SubmenuItemAction>][]> {
return instantiationService.invokeFunction(async accessor => {
+ const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService);
+ const languagePackService = accessor.get(ILanguagePackService);
const menuService = accessor.get(IMenuService);
const extensionRecommendationsService = accessor.get(IExtensionRecommendationsService);
const extensionIgnoredRecommendationsService = accessor.get(IExtensionIgnoredRecommendationsService);
@@ -1006,6 +1009,9 @@ async function getContextMenuActionsGroups(extension: IExtension | undefined | n
cksOverlay.push(['extensionHasColorThemes', colorThemes.some(theme => isThemeFromExtension(theme, extension))]);
cksOverlay.push(['extensionHasFileIconThemes', fileIconThemes.some(theme => isThemeFromExtension(theme, extension))]);
cksOverlay.push(['extensionHasProductIconThemes', productIconThemes.some(theme => isThemeFromExtension(theme, extension))]);
+
+ cksOverlay.push(['canSetLanguage', extensionsWorkbenchService.canSetLanguage(extension)]);
+ cksOverlay.push(['isActiveLanguagePackExtension', extension.gallery && language === languagePackService.getLocale(extension.gallery)]);
}
const menu = menuService.createMenu(MenuId.ExtensionContext, contextKeyService.createOverlay(cksOverlay));
@@ -1791,10 +1797,10 @@ export class SetProductIconThemeAction extends ExtensionAction {
export class SetLanguageAction extends ExtensionAction {
- static readonly ID = 'workbench.extensions.action.setLanguageTheme';
- static readonly TITLE = { value: localize('workbench.extensions.action.setLanguageTheme', "Set Display Language"), original: 'Set Display Language' };
+ static readonly ID = 'workbench.extensions.action.setDisplayLanguage';
+ static readonly TITLE = { value: localize('workbench.extensions.action.setDisplayLanguage', "Set Display Language"), original: 'Set Display Language' };
- private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} theme`;
+ private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} language`;
private static readonly DisabledClass = `${SetLanguageAction.EnabledClass} disabled`;
constructor(
@@ -1826,6 +1832,44 @@ export class SetLanguageAction extends ExtensionAction {
}
}
+export class ClearLanguageAction extends ExtensionAction {
+
+ static readonly ID = 'workbench.extensions.action.clearLanguage';
+ static readonly TITLE = { value: localize('workbench.extensions.action.clearLanguage', "Clear Display Language"), original: 'Clear Display Language' };
+
+ private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} language`;
+ private static readonly DisabledClass = `${ClearLanguageAction.EnabledClass} disabled`;
+
+ constructor(
+ @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
+ @ILanguagePackService private readonly languagePackService: ILanguagePackService,
+ @ILocaleService private readonly localeService: ILocaleService,
+ ) {
+ super(ClearLanguageAction.ID, ClearLanguageAction.TITLE.value, ClearLanguageAction.DisabledClass, false);
+ this.update();
+ }
+
+ update(): void {
+ this.enabled = false;
+ this.class = ClearLanguageAction.DisabledClass;
+ if (!this.extension) {
+ return;
+ }
+ if (!this.extensionsWorkbenchService.canSetLanguage(this.extension)) {
+ return;
+ }
+ if (this.extension.gallery && language !== this.languagePackService.getLocale(this.extension.gallery)) {
+ return;
+ }
+ this.enabled = true;
+ this.class = ClearLanguageAction.EnabledClass;
+ }
+
+ override async run(): Promise<any> {
+ return this.extension && this.localeService.clearLocalePreference();
+ }
+}
+
export class ShowRecommendedExtensionAction extends Action {
static readonly ID = 'workbench.extensions.action.showRecommendedExtension';
diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts
index e390f694942..8ceb6a8e9ee 100644
--- a/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts
+++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts
@@ -14,10 +14,8 @@ import { IListService, WorkbenchAsyncDataTree } from 'vs/platform/list/browser/l
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
-import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { IAsyncDataSource, ITreeNode } from 'vs/base/browser/ui/tree/tree';
import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list';
-import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { CancellationToken } from 'vs/base/common/cancellation';
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { IColorMapping } from 'vs/platform/theme/common/styler';
@@ -244,8 +242,6 @@ export class ExtensionsTree extends WorkbenchAsyncDataTree<IExtensionData, IExte
@IThemeService themeService: IThemeService,
@IInstantiationService instantiationService: IInstantiationService,
@IConfigurationService configurationService: IConfigurationService,
- @IKeybindingService keybindingService: IKeybindingService,
- @IAccessibilityService accessibilityService: IAccessibilityService,
@IExtensionsWorkbenchService extensionsWorkdbenchService: IExtensionsWorkbenchService
) {
const delegate = new VirualDelegate();
@@ -278,7 +274,7 @@ export class ExtensionsTree extends WorkbenchAsyncDataTree<IExtensionData, IExte
}
}
},
- contextKeyService, listService, themeService, configurationService, keybindingService, accessibilityService
+ instantiationService, contextKeyService, listService, themeService, configurationService
);
this.setInput(input);
diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts
index 31cc538fa56..ae27fb19d2b 100644
--- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts
+++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts
@@ -33,7 +33,6 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IContextKeyService, ContextKeyExpr, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
-import { getMaliciousExtensionsSet } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { ILogService } from 'vs/platform/log/common/log';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IHostService } from 'vs/workbench/services/host/browser/host';
@@ -60,6 +59,7 @@ import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/b
import { coalesce } from 'vs/base/common/arrays';
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);
@@ -807,12 +807,11 @@ export class MaliciousExtensionChecker implements IWorkbenchContribution {
}
private checkForMaliciousExtensions(): Promise<void> {
- return this.extensionsManagementService.getExtensionsControlManifest().then(report => {
- const maliciousSet = getMaliciousExtensionsSet(report);
+ return this.extensionsManagementService.getExtensionsControlManifest().then(extensionsControlManifest => {
return this.extensionsManagementService.getInstalled(ExtensionType.User).then(installed => {
const maliciousExtensions = installed
- .filter(e => maliciousSet.has(e.identifier.id));
+ .filter(e => extensionsControlManifest.malicious.some(identifier => areSameExtensions(e.identifier, identifier)));
if (maliciousExtensions.length) {
return Promises.settled(maliciousExtensions.map(e => this.extensionsManagementService.uninstall(e).then(() => {
diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css b/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css
index 13d0d87abca..b50af123be2 100644
--- a/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css
+++ b/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css
@@ -54,6 +54,7 @@
.monaco-action-bar .action-item.disabled .action-label.extension-action.update,
.monaco-action-bar .action-item.disabled .action-label.extension-action.migrate,
.monaco-action-bar .action-item.disabled .action-label.extension-action.theme,
+.monaco-action-bar .action-item.disabled .action-label.extension-action.language,
.monaco-action-bar .action-item.disabled .action-label.extension-action.extension-sync,
.monaco-action-bar .action-item.action-dropdown-item.disabled,
.monaco-action-bar .action-item.action-dropdown-item .action-label.extension-action.hide,
diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts
index 67ab692b603..d3ae1ad9f6b 100644
--- a/src/vs/workbench/contrib/files/browser/fileCommands.ts
+++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts
@@ -629,7 +629,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
{
isOptional: true,
name: 'New Untitled File args',
- description: 'The editor view type and language ID if known',
+ description: 'The editor view type, language ID, or resource path if known',
schema: {
'type': 'object',
'properties': {
@@ -638,17 +638,20 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
},
'languageId': {
'type': 'string'
+ },
+ 'path': {
+ 'type': 'string'
}
}
}
}
]
},
- handler: async (accessor, args?: { languageId?: string; viewType?: string }) => {
+ handler: async (accessor, args?: { languageId?: string; viewType?: string; path?: string }) => {
const editorService = accessor.get(IEditorService);
await editorService.openEditor({
- resource: undefined,
+ resource: args?.path ? URI.from({ scheme: Schemas.untitled, path: args.path }) : undefined,
options: {
override: args?.viewType,
pinned: true
diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts
index 1ebf70ce257..79e57d4312d 100644
--- a/src/vs/workbench/contrib/files/browser/files.contribution.ts
+++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts
@@ -404,7 +404,7 @@ configurationRegistry.registerConfiguration({
},
'explorer.expandSingleFolderWorkspaces': {
'type': 'boolean',
- 'description': nls.localize('expandSingleFolderWorkspaces', "Controls whether the explorer should expand multi-root workspaces containing only one folder during initilization"),
+ 'description': nls.localize('expandSingleFolderWorkspaces', "Controls whether the explorer should expand multi-root workspaces containing only one folder during initialization"),
'default': true
},
'explorer.sortOrder': {
diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts
index 6185a906efa..a9cd6b8a8c9 100644
--- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts
+++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts
@@ -67,23 +67,28 @@ interface IExplorerViewStyles {
listDropBackground?: Color;
}
-// Accepts a single or multiple workspace folders
-function hasExpandedRootChild(tree: WorkbenchCompressibleAsyncDataTree<ExplorerItem | ExplorerItem[], ExplorerItem, FuzzyScore>, treeInput: ExplorerItem | ExplorerItem[]): boolean {
- const inputsToCheck = [];
- if (Array.isArray(treeInput)) {
- inputsToCheck.push(...treeInput.filter(folder => tree.hasNode(folder) && !tree.isCollapsed(folder)));
- } else {
- inputsToCheck.push(treeInput);
- }
-
- for (const folder of inputsToCheck) {
- for (const [, child] of folder.children.entries()) {
- if (tree.hasNode(child) && tree.isCollapsible(child) && !tree.isCollapsed(child)) {
- return true;
+function hasExpandedRootChild(tree: WorkbenchCompressibleAsyncDataTree<ExplorerItem | ExplorerItem[], ExplorerItem, FuzzyScore>, treeInput: ExplorerItem[]): boolean {
+ for (const folder of treeInput) {
+ if (tree.hasNode(folder) && !tree.isCollapsed(folder)) {
+ for (const [, child] of folder.children.entries()) {
+ if (tree.hasNode(child) && tree.isCollapsible(child) && !tree.isCollapsed(child)) {
+ return true;
+ }
}
}
}
+ return false;
+}
+/**
+ * Whether or not any of the nodes in the tree are expanded
+ */
+function hasExpandedNode(tree: WorkbenchCompressibleAsyncDataTree<ExplorerItem | ExplorerItem[], ExplorerItem, FuzzyScore>, treeInput: ExplorerItem[]): boolean {
+ for (const folder of treeInput) {
+ if (tree.hasNode(folder) && !tree.isCollapsed(folder)) {
+ return true;
+ }
+ }
return false;
}
@@ -786,15 +791,6 @@ export class ExplorerView extends ViewPane implements IExplorerView {
this.tree.domFocus();
}
- const treeInput = this.tree.getInput();
- if (Array.isArray(treeInput)) {
- treeInput.forEach(folder => {
- folder.children.forEach(child => this.tree.hasNode(child) && this.tree.expand(child, true));
- });
-
- return;
- }
-
this.tree.expandAll();
}
@@ -871,7 +867,9 @@ export class ExplorerView extends ViewPane implements IExplorerView {
if (treeInput === undefined) {
return;
}
- this.viewHasSomeCollapsibleRootItem.set(hasExpandedRootChild(this.tree, treeInput));
+ const treeInputArray = Array.isArray(treeInput) ? treeInput : Array.from(treeInput.children.values());
+ // Has collapsible root when anything is expanded
+ this.viewHasSomeCollapsibleRootItem.set(hasExpandedNode(this.tree, treeInputArray));
}
styleListDropBackground(styles: IExplorerViewStyles): void {
diff --git a/src/vs/workbench/contrib/format/browser/formatActionsNone.ts b/src/vs/workbench/contrib/format/browser/formatActionsNone.ts
index b32e1dc76fb..0177b09bf8f 100644
--- a/src/vs/workbench/contrib/format/browser/formatActionsNone.ts
+++ b/src/vs/workbench/contrib/format/browser/formatActionsNone.ts
@@ -68,9 +68,10 @@ registerEditorAction(class FormatDocumentMultipleAction extends EditorAction {
const res = await dialogService.show(
Severity.Info,
message,
- [nls.localize('cancel', "Cancel"), nls.localize('install.formatter', "Install Formatter...")]
+ [nls.localize('install.formatter', "Install Formatter..."), nls.localize('cancel', "Cancel")],
+ { cancelId: 1 }
);
- if (res.choice === 1) {
+ if (res.choice !== 1) {
showExtensionQuery(paneCompositeService, `category:formatters ${langName}`);
}
}
diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts
index 81992e7547d..549ca51417f 100644
--- a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts
+++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts
@@ -57,6 +57,7 @@ import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/co
import { NOTEBOOK_KERNEL } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
import { ICursorPositionChangedEvent } from 'vs/editor/common/cursorEvents';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
+import { isEqual } from 'vs/base/common/resources';
const DECORATION_KEY = 'interactiveInputDecoration';
const INTERACTIVE_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'InteractiveEditorViewState';
@@ -152,9 +153,11 @@ export class InteractiveEditor extends EditorPane {
codeEditorService.registerDecorationType('interactive-decoration', DECORATION_KEY, {});
this._register(this.#keybindingService.onDidUpdateKeybindings(this.#updateInputDecoration, this));
this._register(this.#notebookExecutionStateService.onDidChangeCellExecution((e) => {
- const cell = this.#notebookWidget.value?.getCellByHandle(e.cellHandle);
- if (cell && e.changed?.state) {
- this.#scrollIfNecessary(cell);
+ if (isEqual(e.notebook, this.#notebookWidget.value?.viewModel?.notebookDocument.uri)) {
+ const cell = this.#notebookWidget.value?.getCellByHandle(e.cellHandle);
+ if (cell && e.changed?.state) {
+ this.#scrollIfNecessary(cell);
+ }
}
}));
}
@@ -333,6 +336,7 @@ export class InteractiveEditor extends EditorPane {
menuIds: {
notebookToolbar: MenuId.InteractiveToolbar,
cellTitleToolbar: MenuId.InteractiveCellTitle,
+ cellDeleteToolbar: MenuId.InteractiveCellDelete,
cellInsertToolbar: MenuId.NotebookCellBetween,
cellTopInsertToolbar: MenuId.NotebookCellListTop,
cellExecuteToolbar: MenuId.InteractiveCellExecute,
diff --git a/src/vs/workbench/contrib/list/browser/list.contribution.ts b/src/vs/workbench/contrib/list/browser/list.contribution.ts
index f396bf16f84..a2347a33886 100644
--- a/src/vs/workbench/contrib/list/browser/list.contribution.ts
+++ b/src/vs/workbench/contrib/list/browser/list.contribution.ts
@@ -3,22 +3,20 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
-import { WorkbenchListAutomaticKeyboardNavigationKey } from 'vs/platform/list/browser/listService';
+import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
-export const WorkbenchListSupportsKeyboardNavigation = new RawContextKey<boolean>('listSupportsKeyboardNavigation', true);
-export const WorkbenchListAutomaticKeyboardNavigation = new RawContextKey<boolean>(WorkbenchListAutomaticKeyboardNavigationKey, true);
-
export class ListContext implements IWorkbenchContribution {
constructor(
@IContextKeyService contextKeyService: IContextKeyService
) {
- WorkbenchListSupportsKeyboardNavigation.bindTo(contextKeyService);
- WorkbenchListAutomaticKeyboardNavigation.bindTo(contextKeyService);
+ contextKeyService.createKey<boolean>('listSupportsTypeNavigation', true);
+
+ // @deprecated in favor of listSupportsTypeNavigation
+ contextKeyService.createKey('listSupportsKeyboardNavigation', true);
}
}
diff --git a/src/vs/workbench/contrib/markers/browser/markersView.ts b/src/vs/workbench/contrib/markers/browser/markersView.ts
index bf75c6804ca..1b322657c38 100644
--- a/src/vs/workbench/contrib/markers/browser/markersView.ts
+++ b/src/vs/workbench/contrib/markers/browser/markersView.ts
@@ -40,7 +40,6 @@ import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/marke
import { withUndefinedAsNull } from 'vs/base/common/types';
import { MementoObject, Memento } from 'vs/workbench/common/memento';
import { IIdentityProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
-import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { KeyCode } from 'vs/base/common/keyCodes';
import { editorLightBulbForeground, editorLightBulbAutoFixForeground } from 'vs/platform/theme/common/colorRegistry';
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane';
@@ -922,14 +921,13 @@ class MarkersTree extends WorkbenchObjectTree<MarkerElement, FilterData> impleme
delegate: IListVirtualDelegate<MarkerElement>,
renderers: ITreeRenderer<MarkerElement, FilterData, any>[],
options: IWorkbenchObjectTreeOptions<MarkerElement, FilterData>,
+ @IInstantiationService instantiationService: IInstantiationService,
@IContextKeyService contextKeyService: IContextKeyService,
@IListService listService: IListService,
@IThemeService themeService: IThemeService,
@IConfigurationService configurationService: IConfigurationService,
- @IKeybindingService keybindingService: IKeybindingService,
- @IAccessibilityService accessibilityService: IAccessibilityService
) {
- super(user, container, delegate, renderers, options, contextKeyService, listService, themeService, configurationService, keybindingService, accessibilityService);
+ super(user, container, delegate, renderers, options, instantiationService, contextKeyService, listService, themeService, configurationService);
this.visibilityContextKey = MarkersContextKeys.MarkersTreeVisibilityContextKey.bindTo(contextKeyService);
}
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts
index 892febf378e..ca56d104c54 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts
@@ -9,6 +9,7 @@ import { localize } from 'vs/nls';
import { ILocalizedString } from 'vs/platform/action/common/action';
import { Action2, MenuId } from 'vs/platform/actions/common/actions';
import { ICommandService } from 'vs/platform/commands/common/commands';
+import { EditorResolution } from 'vs/platform/editor/common/editor';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { API_OPEN_DIFF_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands';
@@ -35,7 +36,7 @@ export class OpenMergeEditor extends Action2 {
validatedArgs.input2,
validatedArgs.output,
);
- accessor.get(IEditorService).openEditor(input, { preserveFocus: true });
+ accessor.get(IEditorService).openEditor(input, { preserveFocus: true, override: EditorResolution.DISABLED });
}
}
@@ -167,6 +168,35 @@ const mergeEditorCategory: ILocalizedString = {
original: 'Merge Editor',
};
+export class OpenResultResource extends Action2 {
+ constructor() {
+ super({
+ id: 'merge.openResult',
+ icon: Codicon.goToFile,
+ title: {
+ value: localize('openfile', 'Open File'),
+ original: 'Open File',
+ },
+ category: mergeEditorCategory,
+ menu: [{
+ id: MenuId.EditorTitle,
+ when: ctxIsMergeEditor,
+ group: 'navigation',
+ order: 1,
+ }],
+ precondition: ctxIsMergeEditor,
+ });
+ }
+
+ async run(accessor: ServicesAccessor): Promise<void> {
+ const opener = accessor.get(IOpenerService);
+ const { activeEditor } = accessor.get(IEditorService);
+ if (activeEditor instanceof MergeEditorInput) {
+ await opener.open(activeEditor.result);
+ }
+ }
+}
+
export class GoToNextConflict extends Action2 {
constructor() {
super({
@@ -182,6 +212,7 @@ export class GoToNextConflict extends Action2 {
id: MenuId.EditorTitle,
when: ctxIsMergeEditor,
group: 'navigation',
+ order: 3
},
],
f1: true,
@@ -215,6 +246,7 @@ export class GoToPreviousConflict extends Action2 {
id: MenuId.EditorTitle,
when: ctxIsMergeEditor,
group: 'navigation',
+ order: 2
},
],
f1: true,
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts b/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts
index 6728dc93b1a..6fdc90a8b40 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts
@@ -19,6 +19,7 @@ import { IWorkbenchFileService } from 'vs/workbench/services/files/common/files'
import { URI } from 'vs/base/common/uri';
import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput';
import { ctxIsMergeEditor } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor';
+import { EditorResolution } from 'vs/platform/editor/common/editor';
interface MergeEditorContents {
languageId: string;
@@ -160,6 +161,6 @@ export class MergeEditorOpenContents extends Action2 {
{ uri: input2Uri, title: 'Input 2', description: 'Input 2', detail: '(from JSON)' },
resultUri,
);
- editorService.openEditor(input);
+ editorService.openEditor(input, { override: EditorResolution.DISABLED });
}
}
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts
index 09206f43520..c06a6e0447c 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts
@@ -10,10 +10,10 @@ import { Registry } from 'vs/platform/registry/common/platform';
import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor';
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor';
-import { CompareInput1WithBaseCommand, CompareInput2WithBaseCommand, GoToNextConflict, GoToPreviousConflict, OpenBaseFile, OpenMergeEditor, SetColumnLayout, SetMixedLayout, ToggleActiveConflictInput1, ToggleActiveConflictInput2 } from 'vs/workbench/contrib/mergeEditor/browser/commands/commands';
+import { CompareInput1WithBaseCommand, CompareInput2WithBaseCommand, GoToNextConflict, GoToPreviousConflict, OpenBaseFile, OpenMergeEditor, OpenResultResource, SetColumnLayout, SetMixedLayout, ToggleActiveConflictInput1, ToggleActiveConflictInput2 } from 'vs/workbench/contrib/mergeEditor/browser/commands/commands';
import { MergeEditorCopyContentsToJSON, MergeEditorOpenContents } from 'vs/workbench/contrib/mergeEditor/browser/commands/devCommands';
import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput';
-import { MergeEditor, MergeEditorOpenHandlerContribution } from 'vs/workbench/contrib/mergeEditor/browser/view/mergeEditor';
+import { MergeEditor, MergeEditorResolverContribution, MergeEditorOpenHandlerContribution } from 'vs/workbench/contrib/mergeEditor/browser/view/mergeEditor';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { MergeEditorSerializer } from './mergeEditorSerializer';
@@ -33,6 +33,7 @@ Registry.as<IEditorFactoryRegistry>(EditorExtensions.EditorFactory).registerEdit
MergeEditorSerializer
);
+registerAction2(OpenResultResource);
registerAction2(SetMixedLayout);
registerAction2(SetColumnLayout);
registerAction2(OpenMergeEditor);
@@ -54,3 +55,7 @@ registerAction2(CompareInput2WithBaseCommand);
Registry
.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
.registerWorkbenchContribution(MergeEditorOpenHandlerContribution, LifecyclePhase.Restored);
+
+Registry
+ .as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
+ .registerWorkbenchContribution(MergeEditorResolverContribution, LifecyclePhase.Starting);
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts
index 4f29941dc13..9a4286f26b6 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts
@@ -13,7 +13,7 @@ import { ConfirmResult, IDialogService } from 'vs/platform/dialogs/common/dialog
import { IFileService } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILabelService } from 'vs/platform/label/common/label';
-import { IEditorIdentifier, IUntypedEditorInput } from 'vs/workbench/common/editor';
+import { EditorInputCapabilities, IEditorIdentifier, IResourceMergeEditorInput, isResourceMergeEditorInput, IUntypedEditorInput } from 'vs/workbench/common/editor';
import { EditorInput, IEditorCloseHandler } from 'vs/workbench/common/editor/editorInput';
import { AbstractTextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput';
import { EditorWorkerServiceDiffComputer } from 'vs/workbench/contrib/mergeEditor/browser/model/diffComputer';
@@ -84,6 +84,10 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
return MergeEditorInput.ID;
}
+ override get capabilities(): EditorInputCapabilities {
+ return super.capabilities | EditorInputCapabilities.MultipleEditors;
+ }
+
override getName(): string {
return localize('name', "Merging: {0}", super.getName());
}
@@ -134,14 +138,37 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
return this._model;
}
+ override toUntyped(): IResourceMergeEditorInput {
+ return {
+ input1: { resource: this.input1.uri, label: this.input1.title, description: this.input1.description },
+ input2: { resource: this.input2.uri, label: this.input2.title, description: this.input2.description },
+ base: { resource: this.base },
+ result: { resource: this.result },
+ options: {
+ override: this.typeId
+ }
+ };
+ }
+
override matches(otherInput: EditorInput | IUntypedEditorInput): boolean {
- if (!(otherInput instanceof MergeEditorInput)) {
- return false;
+ if (this === otherInput) {
+ return true;
}
- return isEqual(this.base, otherInput.base)
- && isEqual(this.input1.uri, otherInput.input1.uri)
- && isEqual(this.input2.uri, otherInput.input2.uri)
- && isEqual(this.result, otherInput.result);
+ if (otherInput instanceof MergeEditorInput) {
+ return isEqual(this.base, otherInput.base)
+ && isEqual(this.input1.uri, otherInput.input1.uri)
+ && isEqual(this.input2.uri, otherInput.input2.uri)
+ && isEqual(this.result, otherInput.result);
+ }
+ if (isResourceMergeEditorInput(otherInput)) {
+ return this.editorId === otherInput.options?.override
+ && isEqual(this.base, otherInput.base.resource)
+ && isEqual(this.input1.uri, otherInput.input1.resource)
+ && isEqual(this.input2.uri, otherInput.input2.resource)
+ && isEqual(this.result, otherInput.result.resource);
+ }
+
+ return false;
}
// ---- FileEditorInput
@@ -173,21 +200,25 @@ class MergeEditorCloseHandler implements IEditorCloseHandler {
return !this._ignoreUnhandledConflicts && this._model.hasUnhandledConflicts.get();
}
- async confirm(editors?: readonly IEditorIdentifier[] | undefined): Promise<ConfirmResult> {
+ async confirm(editors: readonly IEditorIdentifier[]): Promise<ConfirmResult> {
- const handler: MergeEditorCloseHandler[] = [this];
- editors?.forEach(candidate => candidate.editor.closeHandler instanceof MergeEditorCloseHandler && handler.push(candidate.editor.closeHandler));
+ const handler: MergeEditorCloseHandler[] = [];
+ let someAreDirty = false;
- const inputsWithUnhandledConflicts = handler
- .filter(input => input._model && input._model.hasUnhandledConflicts.get());
+ for (const { editor } of editors) {
+ if (editor.closeHandler instanceof MergeEditorCloseHandler && editor.closeHandler._model.hasUnhandledConflicts.get()) {
+ handler.push(editor.closeHandler);
+ someAreDirty = someAreDirty || editor.isDirty();
+ }
+ }
- if (inputsWithUnhandledConflicts.length === 0) {
+ if (handler.length === 0) {
// shouldn't happen
return ConfirmResult.SAVE;
}
const actions: string[] = [
- localize('unhandledConflicts.ignore', "Continue with Conflicts"),
+ someAreDirty ? localize('unhandledConflicts.saveAndIgnore', "Save & Continue with Conflicts") : localize('unhandledConflicts.ignore', "Continue with Conflicts"),
localize('unhandledConflicts.discard', "Discard Merge Changes"),
localize('unhandledConflicts.cancel', "Cancel"),
];
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts
index 7289cc61637..6b7f4670cb0 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts
@@ -31,7 +31,7 @@ export class EditorGutter<T extends IGutterItemInfo = IGutterItemInfo> extends D
super();
this._domNode.className = 'gutter monaco-editor';
const scrollDecoration = this._domNode.appendChild(
- h('div.scroll-decoration', { role: 'presentation', ariaHidden: true, style: { width: '100%' } })
+ h('div.scroll-decoration', { role: 'presentation', ariaHidden: 'true', style: { width: '100%' } })
.root
);
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts
index 321d1594dd5..d1988a2cfd9 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts
@@ -24,14 +24,14 @@ export abstract class CodeEditorView extends Disposable {
readonly model = this._viewModel.map(m => /** @description model */ m?.model);
protected readonly htmlElements = h('div.code-view', [
- h('div.title', { $: 'header' }, [
- h('span.title', { $: 'title' }),
- h('span.description', { $: 'description' }),
- h('span.detail', { $: 'detail' }),
+ h('div.title@header', [
+ h('span.title@title'),
+ h('span.description@description'),
+ h('span.detail@detail'),
]),
h('div.container', [
- h('div.gutter', { $: 'gutterDiv' }),
- h('div', { $: 'editor' }),
+ h('div.gutter@gutterDiv'),
+ h('div@editor'),
]),
]);
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts
index abd5efbe468..d98aa657980 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts
@@ -36,7 +36,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { FloatingClickWidget } from 'vs/workbench/browser/codeeditor';
import { AbstractTextEditor } from 'vs/workbench/browser/parts/editor/textEditor';
-import { IEditorOpenContext } from 'vs/workbench/common/editor';
+import { DEFAULT_EDITOR_ASSOCIATION, EditorInputWithOptions, IEditorOpenContext, IResourceMergeEditorInput } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { applyTextEditorOptions } from 'vs/workbench/common/editor/editorOptions';
import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput';
@@ -47,6 +47,7 @@ import { MergeEditorViewModel } from 'vs/workbench/contrib/mergeEditor/browser/v
import { ctxBaseResourceScheme, ctxIsMergeEditor, ctxMergeEditorLayout, MergeEditorLayoutTypes } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor';
import { settingsSashBorder } from 'vs/workbench/contrib/preferences/common/settingsEditorColorRegistry';
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
+import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import './colors';
import { InputCodeEditorView } from './editors/inputCodeEditorView';
@@ -452,7 +453,6 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
protected computeEditorViewState(resource: URI): IMergeEditorViewState | undefined {
if (!isEqual(this.model?.result.uri, resource)) {
- // TODO@bpasero Why not check `input#resource` and don't ask me for "forgein" resources?
return undefined;
}
const result = this.inputResultView.editor.saveViewState();
@@ -502,6 +502,71 @@ export class MergeEditorOpenHandlerContribution extends Disposable {
}
}
+export class MergeEditorResolverContribution extends Disposable {
+
+ constructor(
+ @IEditorResolverService editorResolverService: IEditorResolverService,
+ @IInstantiationService instantiationService: IInstantiationService,
+ ) {
+ super();
+
+ this._register(editorResolverService.registerEditor(
+ `*`,
+ {
+ id: MergeEditorInput.ID,
+ label: localize('editor.mergeEditor.label', "Merge Editor"),
+ detail: DEFAULT_EDITOR_ASSOCIATION.providerDisplayName,
+ priority: RegisteredEditorPriority.option
+ },
+ {},
+ (editor) => {
+ return {
+ editor: instantiationService.createInstance(
+ MergeEditorInput,
+ editor.resource,
+ {
+ uri: editor.resource,
+ title: '',
+ description: '',
+ detail: ''
+ },
+ {
+ uri: editor.resource,
+ title: '',
+ description: '',
+ detail: ''
+ },
+ editor.resource
+ )
+ };
+ },
+ undefined,
+ undefined,
+ (mergeEditor: IResourceMergeEditorInput): EditorInputWithOptions => {
+ return {
+ editor: instantiationService.createInstance(
+ MergeEditorInput,
+ mergeEditor.base.resource,
+ {
+ uri: mergeEditor.input1.resource,
+ title: localize('input1Title', "First Version"),
+ description: '',
+ detail: ''
+ },
+ {
+ uri: mergeEditor.input2.resource,
+ title: localize('input2Title', "Second Version"),
+ description: '',
+ detail: ''
+ },
+ mergeEditor.result.resource
+ )
+ };
+ }
+ ));
+ }
+}
+
type IMergeEditorViewState = ICodeEditorViewState & {
readonly input1State?: ICodeEditorViewState;
readonly input2State?: ICodeEditorViewState;
diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.css b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.css
index d9701c95197..52cfc8adcac 100644
--- a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.css
+++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.css
@@ -9,9 +9,9 @@
position: absolute;
top: -45px;
right: 18px;
- width: 318px;
+ width: var(--notebook-find-width);
max-width: calc(100% - 28px - 28px - 8px);
- pointer-events: none;
+ padding:0 var(--notebook-find-horizontal-padding);
transition: top 200ms linear;
visibility: hidden;
}
@@ -158,3 +158,6 @@
.monaco-workbench .simple-fr-replace-part .monaco-inputbox > .ibwrapper > .input {
height: 24px;
}
+.monaco-workbench .simple-fr-find-part-wrapper .monaco-sash {
+ left: 0 !important;
+}
diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts
index 59f060f6421..f2d0a44853c 100644
--- a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts
+++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts
@@ -18,7 +18,7 @@ import * as nls from 'vs/nls';
import { ContextScopedReplaceInput, registerAndCreateHistoryNavigationContext } from 'vs/platform/history/browser/contextScopedHistoryWidget';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
-import { editorWidgetBackground, editorWidgetForeground, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, widgetShadow } from 'vs/platform/theme/common/colorRegistry';
+import { editorWidgetBackground, editorWidgetBorder, editorWidgetForeground, editorWidgetResizeBorder, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, widgetShadow } from 'vs/platform/theme/common/colorRegistry';
import { registerIcon, widgetClose } from 'vs/platform/theme/common/iconRegistry';
import { attachProgressBarStyler } from 'vs/platform/theme/common/styler';
import { IColorTheme, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService';
@@ -35,6 +35,8 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { filterIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons';
import { NotebookFindFilters } from 'vs/workbench/contrib/notebook/browser/contrib/find/findFilters';
import { isSafari } from 'vs/base/common/platform';
+import { ISashEvent, Orientation, Sash } from 'vs/base/browser/ui/sash/sash';
+import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
const NLS_FIND_INPUT_LABEL = nls.localize('label.find', "Find");
const NLS_FIND_INPUT_PLACEHOLDER = nls.localize('placeholder.find', "Find");
@@ -55,6 +57,8 @@ const NOTEBOOK_FIND_IN_MARKUP_PREVIEW = nls.localize('notebook.find.filter.findI
const NOTEBOOK_FIND_IN_CODE_INPUT = nls.localize('notebook.find.filter.findInCodeInput', "Code Cell Source");
const NOTEBOOK_FIND_IN_CODE_OUTPUT = nls.localize('notebook.find.filter.findInCodeOutput', "Cell Output");
+const NOTEBOOK_FIND_WIDGET_INITIAL_WIDTH = 318;
+const NOTEBOOK_FIND_WIDGET_INITIAL_HORIZONTAL_PADDING = 4;
class NotebookFindFilterActionViewItem extends DropdownMenuActionViewItem {
constructor(readonly filters: NotebookFindFilters, action: IAction, actionRunner: IActionRunner, @IContextMenuService contextMenuService: IContextMenuService) {
super(action,
@@ -256,6 +260,8 @@ export abstract class SimpleFindReplaceWidget extends Widget {
protected _replaceBtn!: SimpleButton;
protected _replaceAllBtn!: SimpleButton;
+ private readonly _resizeSash: Sash;
+ private _resizeOriginalWidth = NOTEBOOK_FIND_WIDGET_INITIAL_WIDTH;
private _isVisible: boolean = false;
private _isReplaceVisible: boolean = false;
@@ -274,7 +280,8 @@ export abstract class SimpleFindReplaceWidget extends Widget {
@IMenuService readonly menuService: IMenuService,
@IContextMenuService readonly contextMenuService: IContextMenuService,
@IInstantiationService readonly instantiationService: IInstantiationService,
- protected readonly _state: FindReplaceState<NotebookFindFilters> = new FindReplaceState<NotebookFindFilters>()
+ protected readonly _state: FindReplaceState<NotebookFindFilters> = new FindReplaceState<NotebookFindFilters>(),
+ protected readonly _notebookEditor: INotebookEditor,
) {
super();
@@ -339,7 +346,8 @@ export abstract class SimpleFindReplaceWidget extends Widget {
this.updateButtons(this.foundMatch);
return { content: e.message };
}
- }
+ },
+ flexibleWidth: true,
}
));
@@ -474,6 +482,58 @@ export abstract class SimpleFindReplaceWidget extends Widget {
this._innerReplaceDomNode.appendChild(this._replaceBtn.domNode);
this._innerReplaceDomNode.appendChild(this._replaceAllBtn.domNode);
+
+ this._resizeSash = this._register(new Sash(this._domNode, { getVerticalSashLeft: () => 0 }, { orientation: Orientation.VERTICAL, size: 2 }));
+
+ this._register(this._resizeSash.onDidStart(() => {
+ this._resizeOriginalWidth = this._getDomWidth();
+ }));
+
+ this._register(this._resizeSash.onDidChange((evt: ISashEvent) => {
+ let width = this._resizeOriginalWidth + evt.startX - evt.currentX;
+ if (width < NOTEBOOK_FIND_WIDGET_INITIAL_WIDTH) {
+ width = NOTEBOOK_FIND_WIDGET_INITIAL_WIDTH;
+ }
+
+ const maxWidth = this._getMaxWidth();
+ if (width > maxWidth) {
+ width = maxWidth;
+ }
+
+ this._domNode.style.width = `${width}px`;
+
+ if (this._isReplaceVisible) {
+ this._replaceInput.width = dom.getTotalWidth(this._findInput.domNode);
+ }
+
+ this._findInput.inputBox.layout();
+ }));
+
+ this._register(this._resizeSash.onDidReset(() => {
+ // users double click on the sash
+ // try to emulate what happens with editor findWidget
+ const currentWidth = this._getDomWidth();
+ let width = NOTEBOOK_FIND_WIDGET_INITIAL_WIDTH;
+
+ if (currentWidth <= NOTEBOOK_FIND_WIDGET_INITIAL_WIDTH) {
+ width = this._getMaxWidth();
+ }
+
+ this._domNode.style.width = `${width}px`;
+ if (this._isReplaceVisible) {
+ this._replaceInput.width = dom.getTotalWidth(this._findInput.domNode);
+ }
+
+ this._findInput.inputBox.layout();
+ }));
+ }
+
+ private _getMaxWidth() {
+ return this._notebookEditor.getLayoutInfo().width - 64;
+ }
+
+ private _getDomWidth() {
+ return dom.getTotalWidth(this._domNode) - (NOTEBOOK_FIND_WIDGET_INITIAL_HORIZONTAL_PADDING * 2);
}
getCellToolbarActions(menu: IMenu): { primary: IAction[]; secondary: IAction[] } {
@@ -727,4 +787,16 @@ registerThemingParticipant((theme, collector) => {
if (inputActiveOptionBackgroundColor) {
collector.addRule(`.simple-fr-find-part .find-filter-button > .monaco-action-bar .action-label.notebook-filters.checked { background-color: ${inputActiveOptionBackgroundColor}; }`);
}
+
+ const resizeBorderBackground = theme.getColor(editorWidgetResizeBorder) ?? theme.getColor(editorWidgetBorder);
+ if (resizeBorderBackground) {
+ collector.addRule(`.monaco-workbench .simple-fr-find-part-wrapper .monaco-sash { background-color: ${resizeBorderBackground}; }`);
+ }
+
+ collector.addRule(`
+ :root {
+ --notebook-find-width: ${NOTEBOOK_FIND_WIDGET_INITIAL_WIDTH}px;
+ --notebook-find-horizontal-padding: ${NOTEBOOK_FIND_WIDGET_INITIAL_HORIZONTAL_PADDING}px;
+ }
+ `);
});
diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts
index c027d8137d4..9c38190c342 100644
--- a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts
+++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts
@@ -48,7 +48,7 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote
private _findModel: FindModel;
constructor(
- private readonly _notebookEditor: INotebookEditor,
+ _notebookEditor: INotebookEditor,
@IContextViewService contextViewService: IContextViewService,
@IContextKeyService contextKeyService: IContextKeyService,
@IThemeService themeService: IThemeService,
@@ -57,7 +57,7 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote
@IMenuService menuService: IMenuService,
@IInstantiationService instantiationService: IInstantiationService,
) {
- super(contextViewService, contextKeyService, themeService, configurationService, menuService, contextMenuService, instantiationService, new FindReplaceState<NotebookFindFilters>());
+ super(contextViewService, contextKeyService, themeService, configurationService, menuService, contextMenuService, instantiationService, new FindReplaceState<NotebookFindFilters>(), _notebookEditor);
this._findModel = new FindModel(this._notebookEditor, this._state, this._configurationService);
DOM.append(this._notebookEditor.getDomNode(), this.getDomNode());
diff --git a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts
index 1f64bddb5e3..06e089a7c6b 100644
--- a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts
+++ b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts
@@ -11,9 +11,8 @@ import { getIconClasses } from 'vs/editor/common/services/getIconClasses';
import { IModelService } from 'vs/editor/common/services/model';
import { ILanguageService } from 'vs/editor/common/languages/language';
import { localize } from 'vs/nls';
-import { MenuId, MenuItemAction, registerAction2 } from 'vs/platform/actions/common/actions';
-import { ICommandService } from 'vs/platform/commands/common/commands';
-import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
+import { MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
+import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { InputFocusedContext, InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
@@ -35,26 +34,6 @@ const EDIT_CELL_COMMAND_ID = 'notebook.cell.edit';
const DELETE_CELL_COMMAND_ID = 'notebook.cell.delete';
const CLEAR_CELL_OUTPUTS_COMMAND_ID = 'notebook.cell.clearOutputs';
-export class DeleteCellAction extends MenuItemAction {
- constructor(
- @IContextKeyService contextKeyService: IContextKeyService,
- @ICommandService commandService: ICommandService
- ) {
- super(
- {
- id: DELETE_CELL_COMMAND_ID,
- title: localize('notebookActions.deleteCell', "Delete Cell"),
- icon: icons.deleteCellIcon,
- precondition: NOTEBOOK_EDITOR_EDITABLE.isEqualTo(true)
- },
- undefined,
- { shouldForwardArgs: true },
- undefined,
- contextKeyService,
- commandService);
- }
-}
-
registerAction2(class EditCellAction extends NotebookCellAction {
constructor() {
super(
@@ -158,6 +137,18 @@ registerAction2(class DeleteCellAction extends NotebookCellAction {
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, ContextKeyExpr.not(InputFocusedContextKey)),
weight: KeybindingWeight.WorkbenchContrib
},
+ menu: [
+ {
+ id: MenuId.NotebookCellDelete,
+ when: NOTEBOOK_EDITOR_EDITABLE,
+ group: CELL_TITLE_CELL_GROUP_ID
+ },
+ {
+ id: MenuId.InteractiveCellDelete,
+ when: NOTEBOOK_EDITOR_EDITABLE,
+ group: CELL_TITLE_CELL_GROUP_ID
+ }
+ ],
icon: icons.deleteCellIcon
});
}
diff --git a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts
index 3e54598b92b..7b190d89c14 100644
--- a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts
+++ b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts
@@ -15,7 +15,7 @@ import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { EditorsOrder } from 'vs/workbench/common/editor';
import { insertCell } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations';
import { cellExecutionArgs, CellToolbarOrder, CELL_TITLE_CELL_GROUP_ID, executeNotebookCondition, getContextFromActiveEditor, getContextFromUri, INotebookActionContext, INotebookCellActionContext, INotebookCellToolbarActionContext, INotebookCommandContext, NotebookAction, NotebookCellAction, NotebookMultiCellAction, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT, parseMultiCellExecutionArgs } from 'vs/workbench/contrib/notebook/browser/controller/coreActions';
-import { NOTEBOOK_CELL_EXECUTING, NOTEBOOK_CELL_EXECUTION_STATE, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_TYPE, NOTEBOOK_HAS_RUNNING_CELL, NOTEBOOK_INTERRUPTIBLE_KERNEL, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_KERNEL_SOURCE_COUNT, NOTEBOOK_MISSING_KERNEL_EXTENSION } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
+import { NOTEBOOK_CELL_EXECUTING, NOTEBOOK_CELL_EXECUTION_STATE, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_TYPE, NOTEBOOK_HAS_RUNNING_CELL, NOTEBOOK_INTERRUPTIBLE_KERNEL, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_KERNEL_SOURCE_COUNT, NOTEBOOK_LAST_CELL_FAILED, NOTEBOOK_MISSING_KERNEL_EXTENSION } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
import { CellEditState, CellFocusMode, EXECUTE_CELL_COMMAND_ID } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons';
import { CellKind, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon';
@@ -35,6 +35,7 @@ const EXECUTE_CELL_AND_BELOW = 'notebook.cell.executeCellAndBelow';
const EXECUTE_CELLS_ABOVE = 'notebook.cell.executeCellsAbove';
const RENDER_ALL_MARKDOWN_CELLS = 'notebook.renderAllMarkdownCells';
const REVEAL_RUNNING_CELL = 'notebook.revealRunningCell';
+const REVEAL_LAST_FAILED_CELL = 'notebook.revealLastFailedCell';
// If this changes, update getCodeCellExecutionContextKeyService to match
export const executeCondition = ContextKeyExpr.and(
@@ -594,3 +595,52 @@ registerAction2(class RevealRunningCellAction extends NotebookAction {
}
}
});
+
+registerAction2(class RevealLastFailedCellAction extends NotebookAction {
+ constructor() {
+ super({
+ id: REVEAL_LAST_FAILED_CELL,
+ title: localize('revealLastFailedCell', "Go to Most Recently Failed Cell"),
+ tooltip: localize('revealLastFailedCell', "Go to Most Recently Failed Cell"),
+ shortTitle: localize('revealLastFailedCellShort', "Go To"),
+ precondition: NOTEBOOK_LAST_CELL_FAILED,
+ menu: [
+ {
+ id: MenuId.EditorTitle,
+ when: ContextKeyExpr.and(
+ NOTEBOOK_IS_ACTIVE_EDITOR,
+ NOTEBOOK_LAST_CELL_FAILED,
+ NOTEBOOK_HAS_RUNNING_CELL.toNegated(),
+ ContextKeyExpr.notEquals('config.notebook.globalToolbar', true)
+ ),
+ group: 'navigation',
+ order: 0
+ },
+ {
+ id: MenuId.NotebookToolbar,
+ when: ContextKeyExpr.and(
+ NOTEBOOK_IS_ACTIVE_EDITOR,
+ NOTEBOOK_LAST_CELL_FAILED,
+ NOTEBOOK_HAS_RUNNING_CELL.toNegated(),
+ ContextKeyExpr.equals('config.notebook.globalToolbar', true)
+ ),
+ group: 'navigation/execute',
+ order: 0
+ },
+ ],
+ icon: icons.errorStateIcon,
+ });
+ }
+
+ async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise<void> {
+ const notebookExecutionStateService = accessor.get(INotebookExecutionStateService);
+ const notebook = context.notebookEditor.textModel.uri;
+ const lastFailedCellHandle = notebookExecutionStateService.getLastFailedCellForNotebook(notebook);
+ if (lastFailedCellHandle !== undefined) {
+ const lastFailedCell = context.notebookEditor.getCellByHandle(lastFailedCellHandle);
+ if (lastFailedCell) {
+ context.notebookEditor.focusNotebookCell(lastFailedCell, 'container');
+ }
+ }
+ }
+});
diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts
index c0951594b1c..bfaa6a81660 100644
--- a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts
+++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts
@@ -232,7 +232,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD
keyboardSupport: false,
mouseSupport: true,
multipleSelectionSupport: false,
- enableKeyboardNavigation: true,
+ typeNavigationEnabled: true,
additionalScrollHeight: 0,
// transformOptimization: (isMacintosh && isNative) || getTitleBarStyle(this.configurationService, this.environmentService) === 'native',
styleController: (_suffix: string) => { return this._list!; },
diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts
index 244f65f50ca..e98ae9b79f1 100644
--- a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts
+++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts
@@ -445,22 +445,6 @@ export class NotebookTextDiffList extends WorkbenchList<DiffElementViewModelBase
`);
}
- if (styles.listFilterWidgetBackground) {
- content.push(`.monaco-list-type-filter { background-color: ${styles.listFilterWidgetBackground} }`);
- }
-
- if (styles.listFilterWidgetOutline) {
- content.push(`.monaco-list-type-filter { border: 1px solid ${styles.listFilterWidgetOutline}; }`);
- }
-
- if (styles.listFilterWidgetNoMatchesOutline) {
- content.push(`.monaco-list-type-filter.no-matches { border: 1px solid ${styles.listFilterWidgetNoMatchesOutline}; }`);
- }
-
- if (styles.listMatchesShadow) {
- content.push(`.monaco-list-type-filter { box-shadow: 1px 1px 1px ${styles.listMatchesShadow}; }`);
- }
-
const newStyles = content.join('\n');
if (newStyles !== this.styleElement.textContent) {
this.styleElement.textContent = newStyles;
diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebookToolbar.css b/src/vs/workbench/contrib/notebook/browser/media/notebookToolbar.css
index 4b61bafdfd8..f7ddb6665a7 100644
--- a/src/vs/workbench/contrib/notebook/browser/media/notebookToolbar.css
+++ b/src/vs/workbench/contrib/notebook/browser/media/notebookToolbar.css
@@ -80,3 +80,7 @@
.monaco-workbench .notebookOverlay .notebook-toolbar-container .monaco-action-bar:not(.vertical) .action-item.active {
background-color: unset;
}
+
+.monaco-workbench .notebookOverlay .notebook-toolbar-container .monaco-action-bar .action-item .codicon-notebook-state-error {
+ color: var(--notebook-cell-status-icon-error);
+}
diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts
index 84614d859fe..9b245482c3d 100644
--- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts
+++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts
@@ -325,6 +325,7 @@ export interface INotebookEditorCreationOptions {
readonly menuIds: {
notebookToolbar: MenuId;
cellTitleToolbar: MenuId;
+ cellDeleteToolbar: MenuId;
cellInsertToolbar: MenuId;
cellTopInsertToolbar: MenuId;
cellExecuteToolbar: MenuId;
diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts
index 99d1d8de1f0..084590f8a0c 100644
--- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts
+++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts
@@ -33,7 +33,7 @@ import { NotebooKernelActionViewItem } from 'vs/workbench/contrib/notebook/brows
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { NOTEBOOK_EDITOR_ID } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput';
-import { clearMarks, getAndClearMarks, mark } from 'vs/workbench/contrib/notebook/common/notebookPerformance';
+import { NotebookPerfMarks } from 'vs/workbench/contrib/notebook/common/notebookPerformance';
import { IEditorDropService } from 'vs/workbench/services/editor/browser/editorDropService';
import { GroupsOrder, IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
@@ -172,8 +172,8 @@ export class NotebookEditor extends EditorPane implements IEditorPaneWithSelecti
override async setInput(input: NotebookEditorInput, options: INotebookEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken, noRetry?: boolean): Promise<void> {
try {
- clearMarks(input.resource);
- mark(input.resource, 'startTime');
+ const perf = new NotebookPerfMarks();
+ perf.mark('startTime');
const group = this.group!;
this._inputListener.value = input.onDidChangeCapabilities(() => this._onDidChangeInputCapabilities(input));
@@ -203,8 +203,8 @@ export class NotebookEditor extends EditorPane implements IEditorPaneWithSelecti
// only now `setInput` and yield/await. this is AFTER the actual widget is ready. This is very important
// so that others synchronously receive a notebook editor with the correct widget being set
await super.setInput(input, options, context, token);
- const model = await input.resolve();
- mark(input.resource, 'inputLoaded');
+ const model = await input.resolve(perf);
+ perf.mark('inputLoaded');
// Check for cancellation
if (token.isCancellationRequested) {
@@ -230,7 +230,7 @@ export class NotebookEditor extends EditorPane implements IEditorPaneWithSelecti
const viewState = options?.viewState ?? this._loadNotebookEditorViewState(input);
this._widget.value?.setParentContextKeyService(this._contextKeyService);
- await this._widget.value!.setModel(model.notebook, viewState);
+ await this._widget.value!.setModel(model.notebook, viewState, perf);
const isReadOnly = input.hasCapability(EditorInputCapabilities.Readonly);
await this._widget.value!.setOptions({ ...options, isReadOnly });
this._widgetDisposableStore.add(this._widget.value!.onDidFocusWidget(() => this._onDidFocusWidget.fire()));
@@ -240,19 +240,19 @@ export class NotebookEditor extends EditorPane implements IEditorPaneWithSelecti
containsGroup: (group) => this.group?.id === group.id
}));
- mark(input.resource, 'editorLoaded');
+ perf.mark('editorLoaded');
type WorkbenchNotebookOpenClassification = {
owner: 'rebornix';
comment: 'The notebook file open metrics. Used to get a better understanding of the performance of notebook file opening';
- scheme: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
- ext: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
- viewType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
- extensionActivated: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
- inputLoaded: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
- webviewCommLoaded: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
- customMarkdownLoaded: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
- editorLoaded: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
+ scheme: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'File system provider scheme for the notebook resource' };
+ ext: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'File extension for the notebook resource' };
+ viewType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The view type of the notebook editor' };
+ extensionActivated: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extension activation time for the resource opening' };
+ inputLoaded: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Editor Input loading time for the resource opening' };
+ webviewCommLoaded: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Webview initialization time for the resource opening' };
+ customMarkdownLoaded: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Custom markdown loading time for the resource opening' };
+ editorLoaded: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Overall editor loading time for the resource opening' };
};
type WorkbenchNotebookOpenEvent = {
@@ -266,8 +266,7 @@ export class NotebookEditor extends EditorPane implements IEditorPaneWithSelecti
editorLoaded: number;
};
- const perfMarks = getAndClearMarks(input.resource);
-
+ const perfMarks = perf.value;
if (perfMarks) {
const startTime = perfMarks['startTime'];
const extensionActivated = perfMarks['extensionActivated'];
diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts
index 3debc2599a6..a3f275365dd 100644
--- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts
+++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts
@@ -77,7 +77,6 @@ import { INotebookExecutionService } from 'vs/workbench/contrib/notebook/common/
import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
import { NotebookOptions, OutputInnerContainerTopPadding } from 'vs/workbench/contrib/notebook/common/notebookOptions';
-import { mark } from 'vs/workbench/contrib/notebook/common/notebookPerformance';
import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange';
import { INotebookRendererMessagingService } from 'vs/workbench/contrib/notebook/common/notebookRendererMessagingService';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
@@ -85,6 +84,7 @@ import { editorGutterModifiedBackground } from 'vs/workbench/contrib/scm/browser
import { IWebview } from 'vs/workbench/contrib/webview/browser/webview';
import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
+import { NotebookPerfMarks } from 'vs/workbench/contrib/notebook/common/notebookPerformance';
const $ = DOM.$;
@@ -190,6 +190,7 @@ export function getDefaultNotebookCreationOptions(): INotebookEditorCreationOpti
menuIds: {
notebookToolbar: MenuId.NotebookToolbar,
cellTitleToolbar: MenuId.NotebookCellTitle,
+ cellDeleteToolbar: MenuId.NotebookCellDelete,
cellInsertToolbar: MenuId.NotebookCellBetween,
cellTopInsertToolbar: MenuId.NotebookCellListTop,
cellExecuteToolbar: MenuId.NotebookCellExecute,
@@ -914,7 +915,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
mouseSupport: true,
multipleSelectionSupport: true,
selectionNavigation: true,
- enableKeyboardNavigation: true,
+ typeNavigationEnabled: true,
additionalScrollHeight: 0,
transformOptimization: false, //(isMacintosh && isNative) || getTitleBarStyle(this.configurationService, this.environmentService) === 'native',
styleController: (_suffix: string) => { return this._list; },
@@ -1079,12 +1080,12 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
this.scopedContextKeyService.updateParent(parentContextKeyService);
}
- async setModel(textModel: NotebookTextModel, viewState: INotebookEditorViewState | undefined): Promise<void> {
+ async setModel(textModel: NotebookTextModel, viewState: INotebookEditorViewState | undefined, perf?: NotebookPerfMarks): Promise<void> {
if (this.viewModel === undefined || !this.viewModel.equal(textModel)) {
const oldTopInsertToolbarHeight = this._notebookOptions.computeTopInsertToolbarHeight(this.viewModel?.viewType);
const oldBottomToolbarDimensions = this._notebookOptions.computeBottomToolbarDimensions(this.viewModel?.viewType);
this._detachModel();
- await this._attachModel(textModel, viewState);
+ await this._attachModel(textModel, viewState, perf);
const newTopInsertToolbarHeight = this._notebookOptions.computeTopInsertToolbarHeight(this.viewModel?.viewType);
const newBottomToolbarDimensions = this._notebookOptions.computeBottomToolbarDimensions(this.viewModel?.viewType);
@@ -1101,9 +1102,9 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
type WorkbenchNotebookOpenClassification = {
owner: 'rebornix';
comment: 'Identify the notebook editor view type';
- scheme: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
- ext: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
- viewType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
+ scheme: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'File system provider scheme for the resource' };
+ ext: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'File extension for the resource' };
+ viewType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'View type of the notebook editor' };
};
type WorkbenchNotebookOpenEvent = {
@@ -1388,7 +1389,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
this._list.attachWebview(this._webview.element);
}
- private async _attachModel(textModel: NotebookTextModel, viewState: INotebookEditorViewState | undefined) {
+ private async _attachModel(textModel: NotebookTextModel, viewState: INotebookEditorViewState | undefined, perf?: NotebookPerfMarks) {
await this._createWebview(this.getId(), textModel.uri);
this.viewModel = this.instantiationService.createInstance(NotebookViewModel, textModel.viewType, textModel, this._viewContext, this.getLayoutInfo(), { isReadOnly: this._readOnly });
this._viewContext.eventDispatcher.emit([new NotebookLayoutChangedEvent({ width: true, fontInfo: true }, this.getLayoutInfo())]);
@@ -1471,7 +1472,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
// init rendering
await this._warmupWithMarkdownRenderer(this.viewModel, viewState);
- mark(textModel.uri, 'customMarkdownLoaded');
+ perf?.mark('customMarkdownLoaded');
// model attached
this._localCellStateListeners = this.viewModel.viewCells.map(cell => this._bindCellListener(cell));
diff --git a/src/vs/workbench/contrib/notebook/browser/notebookExecutionStateServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookExecutionStateServiceImpl.ts
index e32dbce7ba8..0cf3e56c598 100644
--- a/src/vs/workbench/contrib/notebook/browser/notebookExecutionStateServiceImpl.ts
+++ b/src/vs/workbench/contrib/notebook/browser/notebookExecutionStateServiceImpl.ts
@@ -13,7 +13,7 @@ import { ILogService } from 'vs/platform/log/common/log';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { CellEditType, CellUri, ICellEditOperation, NotebookCellExecutionState, NotebookCellInternalMetadata, NotebookTextModelWillAddRemoveEvent } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellExecutionUpdateType, INotebookExecutionService } from 'vs/workbench/contrib/notebook/common/notebookExecutionService';
-import { ICellExecuteUpdate, ICellExecutionComplete, ICellExecutionStateChangedEvent, ICellExecutionStateUpdate, INotebookCellExecution, INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
+import { ICellExecuteUpdate, ICellExecutionComplete, ICellExecutionStateChangedEvent, ICellExecutionStateUpdate, INotebookCellExecution, INotebookExecutionStateService, INotebookFailStateChangedEvent } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
export class NotebookExecutionStateService extends Disposable implements INotebookExecutionStateService {
@@ -22,10 +22,14 @@ export class NotebookExecutionStateService extends Disposable implements INotebo
private readonly _executions = new ResourceMap<Map<number, CellExecution>>();
private readonly _notebookListeners = new ResourceMap<NotebookExecutionListeners>();
private readonly _cellListeners = new ResourceMap<IDisposable>();
+ private readonly _lastFailedCells = new ResourceMap<number>();
private readonly _onDidChangeCellExecution = this._register(new Emitter<ICellExecutionStateChangedEvent>());
onDidChangeCellExecution = this._onDidChangeCellExecution.event;
+ private readonly _onDidChangeLastRunFailState = this._register(new Emitter<INotebookFailStateChangedEvent>());
+ onDidChangeLastRunFailState = this._onDidChangeLastRunFailState.event;
+
constructor(
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@ILogService private readonly _logService: ILogService,
@@ -34,6 +38,10 @@ export class NotebookExecutionStateService extends Disposable implements INotebo
super();
}
+ getLastFailedCellForNotebook(notebook: URI): number | undefined {
+ return this._lastFailedCells.get(notebook);
+ }
+
forceCancelNotebookExecutions(notebookUri: URI): void {
const notebookExecutions = this._executions.get(notebookUri);
if (!notebookExecutions) {
@@ -68,7 +76,7 @@ export class NotebookExecutionStateService extends Disposable implements INotebo
this._onDidChangeCellExecution.fire(new NotebookExecutionEvent(notebookUri, cellHandle, exe));
}
- private _onCellExecutionDidComplete(notebookUri: URI, cellHandle: number, exe: CellExecution): void {
+ private _onCellExecutionDidComplete(notebookUri: URI, cellHandle: number, exe: CellExecution, lastRunSuccess?: boolean): void {
const notebookExecutions = this._executions.get(notebookUri);
if (!notebookExecutions) {
this._logService.debug(`NotebookExecutionStateService#_onCellExecutionDidComplete - unknown notebook ${notebookUri.toString()}`);
@@ -86,6 +94,14 @@ export class NotebookExecutionStateService extends Disposable implements INotebo
this._notebookListeners.delete(notebookUri);
}
+ if (lastRunSuccess !== undefined) {
+ if (lastRunSuccess) {
+ this._clearLastFailedCell(notebookUri);
+ } else {
+ this._setLastFailedCell(notebookUri, cellHandle);
+ }
+ }
+
this._onDidChangeCellExecution.fire(new NotebookExecutionEvent(notebookUri, cellHandle));
}
@@ -119,12 +135,22 @@ export class NotebookExecutionStateService extends Disposable implements INotebo
const exe: CellExecution = this._instantiationService.createInstance(CellExecution, cellHandle, notebook);
const disposable = combinedDisposable(
exe.onDidUpdate(() => this._onCellExecutionDidChange(notebookUri, cellHandle, exe)),
- exe.onDidComplete(() => this._onCellExecutionDidComplete(notebookUri, cellHandle, exe)));
+ exe.onDidComplete(lastRunSuccess => this._onCellExecutionDidComplete(notebookUri, cellHandle, exe, lastRunSuccess)));
this._cellListeners.set(CellUri.generate(notebookUri, cellHandle), disposable);
return exe;
}
+ private _setLastFailedCell(notebook: URI, cellHandle: number) {
+ this._lastFailedCells.set(notebook, cellHandle);
+ this._onDidChangeLastRunFailState.fire({ failed: true, notebook });
+ }
+
+ private _clearLastFailedCell(notebook: URI) {
+ this._lastFailedCells.delete(notebook);
+ this._onDidChangeLastRunFailState.fire({ failed: false, notebook: notebook });
+ }
+
override dispose(): void {
super.dispose();
this._executions.forEach(executionMap => {
@@ -250,7 +276,7 @@ class CellExecution extends Disposable implements INotebookCellExecution {
private readonly _onDidUpdate = this._register(new Emitter<void>());
readonly onDidUpdate = this._onDidUpdate.event;
- private readonly _onDidComplete = this._register(new Emitter<void>());
+ private readonly _onDidComplete = this._register(new Emitter<boolean | undefined>());
readonly onDidComplete = this._onDidComplete.event;
private _state: NotebookCellExecutionState = NotebookCellExecutionState.Unconfirmed;
@@ -350,7 +376,7 @@ class CellExecution extends Disposable implements INotebookCellExecution {
this._applyExecutionEdits([edit]);
}
- this._onDidComplete.fire();
+ this._onDidComplete.fire(completionData.lastRunSuccess);
}
private _applyExecutionEdits(edits: ICellEditOperation[]): void {
diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts
index 35502e4e1a9..9b3a6397be2 100644
--- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts
+++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts
@@ -18,7 +18,6 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { INotebookCellActionContext } from 'vs/workbench/contrib/notebook/browser/controller/coreActions';
-import { DeleteCellAction } from 'vs/workbench/contrib/notebook/browser/controller/editActions';
import { ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { CodiconActionViewItem } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView';
import { CellPart } from 'vs/workbench/contrib/notebook/browser/view/cellPart';
@@ -93,22 +92,23 @@ export interface ICssClassDelegate {
export class CellTitleToolbarPart extends CellPart {
private _toolbar: ToolBar;
- private _deleteToolbar: ToolBar;
private _titleMenu: IMenu;
- private _actionsDisposables = this._register(new DisposableStore());
-
- private _hasActions = false;
+ private _deleteToolbar: ToolBar;
+ private _deleteMenu: IMenu;
+ private _toolbarActionsDisposables = this._register(new DisposableStore());
+ private _deleteActionsDisposables = this._register(new DisposableStore());
private readonly _onDidUpdateActions: Emitter<void> = this._register(new Emitter<void>());
readonly onDidUpdateActions: Event<void> = this._onDidUpdateActions.event;
get hasActions(): boolean {
- return this._hasActions;
+ return this._toolbar.getItemsLength() + this._deleteToolbar.getItemsLength() > 0;
}
constructor(
private readonly toolbarContainer: HTMLElement,
private readonly _rootClassDelegate: ICssClassDelegate,
toolbarId: MenuId,
+ deleteToolbarId: MenuId,
private readonly _notebookEditor: INotebookEditorDelegate,
@IContextKeyService contextKeyService: IContextKeyService,
@IMenuService menuService: IMenuService,
@@ -120,11 +120,14 @@ export class CellTitleToolbarPart extends CellPart {
this._titleMenu = this._register(menuService.createMenu(toolbarId, contextKeyService));
this._deleteToolbar = this._register(instantiationService.invokeFunction(accessor => createToolbar(accessor, toolbarContainer, 'cell-delete-toolbar')));
+ this._deleteMenu = this._register(menuService.createMenu(deleteToolbarId, contextKeyService));
if (!this._notebookEditor.creationOptions.isReadOnly) {
- this._deleteToolbar.setActions([instantiationService.createInstance(DeleteCellAction)]);
+ const deleteActions = getCellToolbarActions(this._deleteMenu);
+ this._deleteToolbar.setActions(deleteActions.primary, deleteActions.secondary);
}
- this.setupChangeListeners();
+ this.setupChangeListeners(this._toolbar, this._titleMenu, this._toolbarActionsDisposables);
+ this.setupChangeListeners(this._deleteToolbar, this._deleteMenu, this._deleteActionsDisposables);
}
override didRenderCell(element: ICellViewModel): void {
@@ -143,22 +146,22 @@ export class CellTitleToolbarPart extends CellPart {
this._deleteToolbar.context = toolbarContext;
}
- private setupChangeListeners(): void {
+ private setupChangeListeners(toolbar: ToolBar, menu: IMenu, actionDisposables: DisposableStore): void {
// #103926
let dropdownIsVisible = false;
let deferredUpdate: (() => void) | undefined;
- this.updateActions();
- this._register(this._titleMenu.onDidChange(() => {
+ this.updateActions(toolbar, menu, actionDisposables);
+ this._register(menu.onDidChange(() => {
if (dropdownIsVisible) {
- deferredUpdate = () => this.updateActions();
+ deferredUpdate = () => this.updateActions(toolbar, menu, actionDisposables);
return;
}
- this.updateActions();
+ this.updateActions(toolbar, menu, actionDisposables);
}));
this._rootClassDelegate.toggle('cell-toolbar-dropdown-active', false);
- this._register(this._toolbar.onDidChangeDropdownVisibility(visible => {
+ this._register(toolbar.onDidChangeDropdownVisibility(visible => {
dropdownIsVisible = visible;
this._rootClassDelegate.toggle('cell-toolbar-dropdown-active', visible);
@@ -172,24 +175,22 @@ export class CellTitleToolbarPart extends CellPart {
}));
}
- private updateActions() {
- this._actionsDisposables.clear();
- const actions = getCellToolbarActions(this._titleMenu);
- this._actionsDisposables.add(actions.disposable);
+ private updateActions(toolbar: ToolBar, menu: IMenu, actionDisposables: DisposableStore) {
+ actionDisposables.clear();
+ const actions = getCellToolbarActions(menu);
+ actionDisposables.add(actions.disposable);
- const hadFocus = DOM.isAncestor(document.activeElement, this._toolbar.getElement());
- this._toolbar.setActions(actions.primary, actions.secondary);
+ const hadFocus = DOM.isAncestor(document.activeElement, toolbar.getElement());
+ toolbar.setActions(actions.primary, actions.secondary);
if (hadFocus) {
this._notebookEditor.focus();
}
if (actions.primary.length || actions.secondary.length) {
this._rootClassDelegate.toggle('cell-has-toolbar-actions', true);
- this._hasActions = true;
this._onDidUpdateActions.fire();
} else {
this._rootClassDelegate.toggle('cell-has-toolbar-actions', false);
- this._hasActions = false;
this._onDidUpdateActions.fire();
}
}
diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts
index b62e89274d0..ace2ec61b1b 100644
--- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts
+++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts
@@ -1393,22 +1393,6 @@ export class NotebookCellList extends WorkbenchList<CellViewModel> implements ID
`);
}
- if (styles.listFilterWidgetBackground) {
- content.push(`.monaco-list-type-filter { background-color: ${styles.listFilterWidgetBackground} }`);
- }
-
- if (styles.listFilterWidgetOutline) {
- content.push(`.monaco-list-type-filter { border: 1px solid ${styles.listFilterWidgetOutline}; }`);
- }
-
- if (styles.listFilterWidgetNoMatchesOutline) {
- content.push(`.monaco-list-type-filter.no-matches { border: 1px solid ${styles.listFilterWidgetNoMatchesOutline}; }`);
- }
-
- if (styles.listMatchesShadow) {
- content.push(`.monaco-list-type-filter { box-shadow: 1px 1px 1px ${styles.listMatchesShadow}; }`);
- }
-
const newStyles = content.join('\n');
if (newStyles !== this.styleElement.textContent) {
this.styleElement.textContent = newStyles;
diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts
index 2bcf55c8531..77ed53a9ce3 100644
--- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts
+++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts
@@ -160,6 +160,7 @@ export class MarkupCellRenderer extends AbstractCellRenderer implements IListRen
titleToolbarContainer,
rootClassDelegate,
this.notebookEditor.creationOptions.menuIds.cellTitleToolbar,
+ this.notebookEditor.creationOptions.menuIds.cellDeleteToolbar,
this.notebookEditor));
const focusIndicatorBottom = new FastDomNode(DOM.append(container, $('.cell-focus-indicator.cell-focus-indicator-bottom')));
@@ -299,6 +300,7 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende
titleToolbarContainer,
rootClassDelegate,
this.notebookEditor.creationOptions.menuIds.cellTitleToolbar,
+ this.notebookEditor.creationOptions.menuIds.cellDeleteToolbar,
this.notebookEditor));
const focusIndicatorPart = templateDisposables.add(new CellFocusIndicator(this.notebookEditor, titleToolbar, focusIndicatorTop, focusIndicatorLeft, focusIndicatorRight, focusIndicatorBottom));
diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts
index 3702ac26261..27384f830d4 100644
--- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts
+++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts
@@ -6,8 +6,8 @@
import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ICellViewModel, INotebookEditorDelegate, KERNEL_EXTENSIONS } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
-import { NOTEBOOK_CELL_TOOLBAR_LOCATION, NOTEBOOK_HAS_OUTPUTS, NOTEBOOK_HAS_RUNNING_CELL, NOTEBOOK_INTERRUPTIBLE_KERNEL, NOTEBOOK_KERNEL, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_KERNEL_SELECTED, NOTEBOOK_KERNEL_SOURCE_COUNT, NOTEBOOK_MISSING_KERNEL_EXTENSION, NOTEBOOK_USE_CONSOLIDATED_OUTPUT_BUTTON, NOTEBOOK_VIEW_TYPE } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
-import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
+import { NOTEBOOK_CELL_TOOLBAR_LOCATION, NOTEBOOK_HAS_OUTPUTS, NOTEBOOK_HAS_RUNNING_CELL, NOTEBOOK_INTERRUPTIBLE_KERNEL, NOTEBOOK_KERNEL, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_KERNEL_SELECTED, NOTEBOOK_KERNEL_SOURCE_COUNT, NOTEBOOK_LAST_CELL_FAILED, NOTEBOOK_MISSING_KERNEL_EXTENSION, NOTEBOOK_USE_CONSOLIDATED_OUTPUT_BUTTON, NOTEBOOK_VIEW_TYPE } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
+import { INotebookExecutionStateService, INotebookFailStateChangedEvent } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
@@ -24,6 +24,7 @@ export class NotebookEditorContextKeys {
private readonly _viewType!: IContextKey<string>;
private readonly _missingKernelExtension: IContextKey<boolean>;
private readonly _cellToolbarLocation: IContextKey<'left' | 'right' | 'hidden'>;
+ private readonly _lastCellFailed: IContextKey<boolean>;
private readonly _disposables = new DisposableStore();
private readonly _viewModelDisposables = new DisposableStore();
@@ -47,6 +48,7 @@ export class NotebookEditorContextKeys {
this._missingKernelExtension = NOTEBOOK_MISSING_KERNEL_EXTENSION.bindTo(contextKeyService);
this._notebookKernelSourceCount = NOTEBOOK_KERNEL_SOURCE_COUNT.bindTo(contextKeyService);
this._cellToolbarLocation = NOTEBOOK_CELL_TOOLBAR_LOCATION.bindTo(contextKeyService);
+ this._lastCellFailed = NOTEBOOK_LAST_CELL_FAILED.bindTo(contextKeyService);
this._handleDidChangeModel();
this._updateForNotebookOptions();
@@ -58,6 +60,7 @@ export class NotebookEditorContextKeys {
this._disposables.add(_editor.notebookOptions.onDidChangeOptions(this._updateForNotebookOptions, this));
this._disposables.add(_extensionService.onDidChangeExtensions(this._updateForInstalledExtension, this));
this._disposables.add(_notebookExecutionStateService.onDidChangeCellExecution(this._updateForCellExecution, this));
+ this._disposables.add(_notebookExecutionStateService.onDidChangeLastRunFailState(this._updateForLastRunFailState, this));
}
dispose(): void {
@@ -132,6 +135,12 @@ export class NotebookEditorContextKeys {
}
}
+ private _updateForLastRunFailState(e: INotebookFailStateChangedEvent): void {
+ if (e.notebook === this._editor.textModel?.uri) {
+ this._lastCellFailed.set(e.failed);
+ }
+ }
+
private async _updateForInstalledExtension(): Promise<void> {
if (!this._editor.hasModel()) {
return;
diff --git a/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts b/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts
index e629ed034d2..fd9692eba0f 100644
--- a/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts
+++ b/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts
@@ -24,6 +24,7 @@ export const NOTEBOOK_USE_CONSOLIDATED_OUTPUT_BUTTON = new RawContextKey<boolean
export const NOTEBOOK_BREAKPOINT_MARGIN_ACTIVE = new RawContextKey<boolean>('notebookBreakpointMargin', false);
export const NOTEBOOK_CELL_TOOLBAR_LOCATION = new RawContextKey<'left' | 'right' | 'hidden'>('notebookCellToolbarLocation', 'left');
export const NOTEBOOK_CURSOR_NAVIGATION_MODE = new RawContextKey<boolean>('notebookCursorNavigationMode', false);
+export const NOTEBOOK_LAST_CELL_FAILED = new RawContextKey<boolean>('notebookLastCellFailed', false);
// Cell keys
export const NOTEBOOK_VIEW_TYPE = new RawContextKey<string>('notebookType', undefined);
diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts
index b8cbd4985d0..54e0ac40f5e 100644
--- a/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts
+++ b/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts
@@ -16,7 +16,6 @@ import { IDisposable, IReference } from 'vs/base/common/lifecycle';
import { CellEditType, IResolvedNotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { ILabelService } from 'vs/platform/label/common/label';
import { Schemas } from 'vs/base/common/network';
-import { mark } from 'vs/workbench/contrib/notebook/common/notebookPerformance';
import { FileSystemProviderCapabilities, IFileService } from 'vs/platform/files/common/files';
import { AbstractResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
import { IResourceEditorInput } from 'vs/platform/editor/common/editor';
@@ -24,6 +23,7 @@ import { onUnexpectedError } from 'vs/base/common/errors';
import { VSBuffer } from 'vs/base/common/buffer';
import { IWorkingCopyIdentifier } from 'vs/workbench/services/workingCopy/common/workingCopy';
import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider';
+import { NotebookPerfMarks } from 'vs/workbench/contrib/notebook/common/notebookPerformance';
export interface NotebookEditorInputOptions {
startDirty?: boolean;
@@ -231,12 +231,12 @@ export class NotebookEditorInput extends AbstractResourceEditorInput {
}
}
- override async resolve(): Promise<IResolvedNotebookEditorModel | null> {
+ override async resolve(perf?: NotebookPerfMarks): Promise<IResolvedNotebookEditorModel | null> {
if (!await this._notebookService.canResolve(this.viewType)) {
return null;
}
- mark(this.resource, 'extensionActivated');
+ perf?.mark('extensionActivated');
// we are now loading the notebook and don't need to listen to
// "other" loading anymore
diff --git a/src/vs/workbench/contrib/notebook/common/notebookExecutionStateService.ts b/src/vs/workbench/contrib/notebook/common/notebookExecutionStateService.ts
index f6317245bb1..d1fbe75907f 100644
--- a/src/vs/workbench/contrib/notebook/common/notebookExecutionStateService.ts
+++ b/src/vs/workbench/contrib/notebook/common/notebookExecutionStateService.ts
@@ -39,6 +39,10 @@ export interface ICellExecutionStateChangedEvent {
affectsCell(cell: URI): boolean;
affectsNotebook(notebook: URI): boolean;
}
+export interface INotebookFailStateChangedEvent {
+ failed: boolean;
+ notebook: URI;
+}
export const INotebookExecutionStateService = createDecorator<INotebookExecutionStateService>('INotebookExecutionStateService');
@@ -46,11 +50,13 @@ export interface INotebookExecutionStateService {
_serviceBrand: undefined;
onDidChangeCellExecution: Event<ICellExecutionStateChangedEvent>;
+ onDidChangeLastRunFailState: Event<INotebookFailStateChangedEvent>;
forceCancelNotebookExecutions(notebookUri: URI): void;
getCellExecutionStatesForNotebook(notebook: URI): INotebookCellExecution[];
getCellExecution(cellUri: URI): INotebookCellExecution | undefined;
createCellExecution(notebook: URI, cellHandle: number): INotebookCellExecution;
+ getLastFailedCellForNotebook(notebook: URI): number | undefined;
}
export interface INotebookCellExecution {
diff --git a/src/vs/workbench/contrib/notebook/common/notebookPerformance.ts b/src/vs/workbench/contrib/notebook/common/notebookPerformance.ts
index bd59c7bfbf1..c47b6eeeb2c 100644
--- a/src/vs/workbench/contrib/notebook/common/notebookPerformance.ts
+++ b/src/vs/workbench/contrib/notebook/common/notebookPerformance.ts
@@ -3,39 +3,23 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { URI } from 'vs/base/common/uri';
-
export type PerfName = 'startTime' | 'extensionActivated' | 'inputLoaded' | 'webviewCommLoaded' | 'customMarkdownLoaded' | 'editorLoaded';
type PerformanceMark = { [key in PerfName]?: number };
-const perfMarks = new Map<string, PerformanceMark>();
+export class NotebookPerfMarks {
+ private _marks: PerformanceMark = {};
+
+ get value(): PerformanceMark {
+ return { ...this._marks };
+ }
-export function mark(resource: URI, name: PerfName): void {
- const key = resource.toString();
- if (!perfMarks.has(key)) {
- const perfMark: PerformanceMark = {};
- perfMark[name] = Date.now();
- perfMarks.set(key, perfMark);
- } else {
- if (perfMarks.get(key)![name]) {
+ mark(name: PerfName): void {
+ if (this._marks[name]) {
console.error(`Skipping overwrite of notebook perf value: ${name}`);
return;
}
- perfMarks.get(key)![name] = Date.now();
- }
-}
-export function clearMarks(resource: URI): void {
- const key = resource.toString();
-
- perfMarks.delete(key);
-}
-
-export function getAndClearMarks(resource: URI): PerformanceMark | null {
- const key = resource.toString();
-
- const perfMark = perfMarks.get(key) || null;
- perfMarks.delete(key);
- return perfMark;
+ this._marks[name] = Date.now();
+ }
}
diff --git a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts
index 8570bf9146a..1d667408085 100644
--- a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts
+++ b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts
@@ -44,7 +44,7 @@ import { ViewContext } from 'vs/workbench/contrib/notebook/browser/viewModel/vie
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { CellKind, CellUri, INotebookDiffEditorModel, INotebookEditorModel, INotebookSearchOptions, IOutputDto, IResolvedNotebookEditorModel, NotebookCellExecutionState, NotebookCellMetadata, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
-import { ICellExecuteUpdate, ICellExecutionComplete, ICellExecutionStateChangedEvent, INotebookCellExecution, INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
+import { ICellExecuteUpdate, ICellExecutionComplete, ICellExecutionStateChangedEvent, INotebookCellExecution, INotebookExecutionStateService, INotebookFailStateChangedEvent } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
import { NotebookOptions } from 'vs/workbench/contrib/notebook/common/notebookOptions';
import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange';
import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService';
@@ -366,7 +366,6 @@ export function createNotebookCellList(instantiationService: TestInstantiationSe
{
supportDynamicHeights: true,
multipleSelectionSupport: true,
- enableKeyboardNavigation: true,
focusNextPreviousDelegate: {
onFocusNext: (applyFocusNext: () => void) => { applyFocusNext(); },
onFocusPrevious: (applyFocusPrevious: () => void) => { applyFocusPrevious(); },
@@ -405,11 +404,17 @@ class TestCellExecution implements INotebookCellExecution {
}
class TestNotebookExecutionStateService implements INotebookExecutionStateService {
+
+ getLastFailedCellForNotebook(notebook: URI): number | undefined {
+ return;
+ }
+
_serviceBrand: undefined;
private _executions = new ResourceMap<INotebookCellExecution>();
onDidChangeCellExecution = new Emitter<ICellExecutionStateChangedEvent>().event;
+ onDidChangeLastRunFailState = new Emitter<INotebookFailStateChangedEvent>().event;
forceCancelNotebookExecutions(notebookUri: URI): void {
}
diff --git a/src/vs/workbench/contrib/outline/browser/outlinePane.ts b/src/vs/workbench/contrib/outline/browser/outlinePane.ts
index 8b8a7ab8180..d41777b2c54 100644
--- a/src/vs/workbench/contrib/outline/browser/outlinePane.ts
+++ b/src/vs/workbench/contrib/outline/browser/outlinePane.ts
@@ -35,7 +35,7 @@ import { EditorResourceAccessor, IEditorPane } from 'vs/workbench/common/editor'
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { Event } from 'vs/base/common/event';
import { ITreeSorter } from 'vs/base/browser/ui/tree/tree';
-import { AbstractTreeViewState, IAbstractTreeViewState } from 'vs/base/browser/ui/tree/abstractTree';
+import { AbstractTreeViewState, IAbstractTreeViewState, TreeFindMode } from 'vs/base/browser/ui/tree/abstractTree';
const _ctxFollowsCursor = new RawContextKey('outlineFollowsCursor', false);
const _ctxFilterOnType = new RawContextKey('outlineFiltersOnType', false);
@@ -259,7 +259,7 @@ export class OutlinePane extends ViewPane {
expandOnlyOnTwistieClick: true,
multipleSelectionSupport: false,
hideTwistiesOfChildlessElements: true,
- filterOnType: this._outlineViewState.filterOnType,
+ defaultFindMode: this._outlineViewState.filterOnType ? TreeFindMode.Filter : TreeFindMode.Highlight,
overrideStyles: { listBackground: this.getBackgroundColor() }
}
);
@@ -286,6 +286,7 @@ export class OutlinePane extends ViewPane {
};
updateTree();
this._editorControlDisposables.add(newOutline.onDidChange(updateTree));
+ tree.findMode = this._outlineViewState.filterOnType ? TreeFindMode.Filter : TreeFindMode.Highlight;
// feature: apply panel background to tree
this._editorControlDisposables.add(this.viewDescriptorService.onDidChangeLocation(({ views }) => {
@@ -295,7 +296,7 @@ export class OutlinePane extends ViewPane {
}));
// feature: filter on type - keep tree and menu in sync
- this._editorControlDisposables.add(tree.onDidUpdateOptions(e => this._outlineViewState.filterOnType = Boolean(e.filterOnType)));
+ this._editorControlDisposables.add(tree.onDidChangeFindMode(mode => this._outlineViewState.filterOnType = mode === TreeFindMode.Filter));
// feature: reveal outline selection in editor
// on change -> reveal/select defining range
@@ -328,7 +329,7 @@ export class OutlinePane extends ViewPane {
this._editorControlDisposables.add(this._outlineViewState.onDidChange((e: { followCursor?: boolean; sortBy?: boolean; filterOnType?: boolean }) => {
this._outlineViewState.persist(this._storageService);
if (e.filterOnType) {
- tree.updateOptions({ filterOnType: this._outlineViewState.filterOnType });
+ tree.findMode = this._outlineViewState.filterOnType ? TreeFindMode.Filter : TreeFindMode.Highlight;
}
if (e.followCursor) {
revealActiveElement();
@@ -341,8 +342,8 @@ export class OutlinePane extends ViewPane {
// feature: expand all nodes when filtering (not when finding)
let viewState: AbstractTreeViewState | undefined;
- this._editorControlDisposables.add(tree.onDidChangeTypeFilterPattern(pattern => {
- if (!tree.options.filterOnType) {
+ this._editorControlDisposables.add(tree.onDidChangeFindPattern(pattern => {
+ if (tree.findMode === TreeFindMode.Highlight) {
return;
}
if (!viewState && pattern) {
diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsWidgets.css b/src/vs/workbench/contrib/preferences/browser/media/settingsWidgets.css
index f92d003cc0d..ac9b41bff4c 100644
--- a/src/vs/workbench/contrib/preferences/browser/media/settingsWidgets.css
+++ b/src/vs/workbench/contrib/preferences/browser/media/settingsWidgets.css
@@ -30,7 +30,8 @@
.settings-editor > .settings-body .settings-tree-container .setting-item-bool .setting-list-object-input-key-checkbox .setting-value-checkbox {
margin-top: 3px;
}
-.settings-editor > .settings-body .settings-tree-container .setting-item-bool .setting-list-object-value {
+.settings-editor > .settings-body .settings-tree-container .setting-item.setting-item-list .setting-list-object-widget .setting-item-bool .setting-list-object-value {
+ width: 100%;
cursor: pointer;
}
diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts
index 16dd1bb7a51..39df9facda5 100644
--- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts
+++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts
@@ -54,7 +54,6 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { IList } from 'vs/base/browser/ui/tree/indexTreeModel';
import { IListService, WorkbenchObjectTree } from 'vs/platform/list/browser/listService';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
-import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { ILogService } from 'vs/platform/log/common/log';
import { settingsMoreActionIcon } from 'vs/workbench/contrib/preferences/browser/preferencesIcons';
import { IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
@@ -2334,8 +2333,6 @@ export class SettingsTree extends WorkbenchObjectTree<SettingsTreeElement> {
@IListService listService: IListService,
@IThemeService themeService: IThemeService,
@IConfigurationService configurationService: IConfigurationService,
- @IKeybindingService keybindingService: IKeybindingService,
- @IAccessibilityService accessibilityService: IAccessibilityService,
@IInstantiationService instantiationService: IInstantiationService,
@ILanguageService languageService: ILanguageService
) {
@@ -2356,12 +2353,11 @@ export class SettingsTree extends WorkbenchObjectTree<SettingsTreeElement> {
smoothScrolling: configurationService.getValue<boolean>('workbench.list.smoothScrolling'),
multipleSelectionSupport: false,
},
+ instantiationService,
contextKeyService,
listService,
themeService,
configurationService,
- keybindingService,
- accessibilityService,
);
this.disposables.add(registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
diff --git a/src/vs/workbench/contrib/preferences/browser/tocTree.ts b/src/vs/workbench/contrib/preferences/browser/tocTree.ts
index 1451c190687..522b5e13b80 100644
--- a/src/vs/workbench/contrib/preferences/browser/tocTree.ts
+++ b/src/vs/workbench/contrib/preferences/browser/tocTree.ts
@@ -9,11 +9,9 @@ import { DefaultStyleController, IListAccessibilityProvider } from 'vs/base/brow
import { ITreeElement, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree';
import { Iterable } from 'vs/base/common/iterator';
import { localize } from 'vs/nls';
-import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
-import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IListService, IWorkbenchObjectTreeOptions, WorkbenchObjectTree } from 'vs/platform/list/browser/listService';
import { editorBackground, focusBorder, foreground, transparent } from 'vs/platform/theme/common/colorRegistry';
import { attachStyler } from 'vs/platform/theme/common/styler';
@@ -203,8 +201,6 @@ export class TOCTree extends WorkbenchObjectTree<SettingsTreeGroupElement> {
@IListService listService: IListService,
@IThemeService themeService: IThemeService,
@IConfigurationService configurationService: IConfigurationService,
- @IKeybindingService keybindingService: IKeybindingService,
- @IAccessibilityService accessibilityService: IAccessibilityService,
@IInstantiationService instantiationService: IInstantiationService,
) {
// test open mode
@@ -231,12 +227,11 @@ export class TOCTree extends WorkbenchObjectTree<SettingsTreeGroupElement> {
new TOCTreeDelegate(),
[new TOCRenderer()],
options,
+ instantiationService,
contextKeyService,
listService,
themeService,
configurationService,
- keybindingService,
- accessibilityService,
);
this.disposables.add(attachStyler(themeService, {
diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts
index e18a8624103..e5ebb7468a5 100644
--- a/src/vs/workbench/contrib/remote/browser/remote.ts
+++ b/src/vs/workbench/contrib/remote/browser/remote.ts
@@ -779,10 +779,10 @@ export class RemoteAgentConnectionStatusListener extends Disposable implements I
type ReconnectReloadClassification = {
owner: 'alexdima';
comment: 'The reload button in the builtin permanent reconnection failure dialog was pressed';
- remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
- reconnectionToken: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
- millisSinceLastIncomingData: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
- attempt: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
+ remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of the resolver.' };
+ reconnectionToken: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The identifier of the connection.' };
+ millisSinceLastIncomingData: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Elapsed time (in ms) since data was last received.' };
+ attempt: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The reconnection attempt counter.' };
};
type ReconnectReloadEvent = {
remoteName: string | undefined;
@@ -825,8 +825,8 @@ export class RemoteAgentConnectionStatusListener extends Disposable implements I
type RemoteConnectionLostClassification = {
owner: 'alexdima';
comment: 'The remote connection state is now `ConnectionLost`';
- remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
- reconnectionToken: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
+ remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of the resolver.' };
+ reconnectionToken: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The identifier of the connection.' };
};
type RemoteConnectionLostEvent = {
remoteName: string | undefined;
@@ -861,10 +861,10 @@ export class RemoteAgentConnectionStatusListener extends Disposable implements I
type RemoteReconnectionRunningClassification = {
owner: 'alexdima';
comment: 'The remote connection state is now `ReconnectionRunning`';
- remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
- reconnectionToken: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
- millisSinceLastIncomingData: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
- attempt: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
+ remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of the resolver.' };
+ reconnectionToken: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The identifier of the connection.' };
+ millisSinceLastIncomingData: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Elapsed time (in ms) since data was last received.' };
+ attempt: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The reconnection attempt counter.' };
};
type RemoteReconnectionRunningEvent = {
remoteName: string | undefined;
@@ -902,11 +902,11 @@ export class RemoteAgentConnectionStatusListener extends Disposable implements I
type RemoteReconnectionPermanentFailureClassification = {
owner: 'alexdima';
comment: 'The remote connection state is now `ReconnectionPermanentFailure`';
- remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
- reconnectionToken: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
- millisSinceLastIncomingData: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
- attempt: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
- handled: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
+ remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of the resolver.' };
+ reconnectionToken: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The identifier of the connection.' };
+ millisSinceLastIncomingData: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Elapsed time (in ms) since data was last received.' };
+ attempt: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The reconnection attempt counter.' };
+ handled: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The error was handled by the resolver.' };
};
type RemoteReconnectionPermanentFailureEvent = {
remoteName: string | undefined;
@@ -947,10 +947,10 @@ export class RemoteAgentConnectionStatusListener extends Disposable implements I
type RemoteConnectionGainClassification = {
owner: 'alexdima';
comment: 'The remote connection state is now `ConnectionGain`';
- remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
- reconnectionToken: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
- millisSinceLastIncomingData: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
- attempt: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
+ remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of the resolver.' };
+ reconnectionToken: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The identifier of the connection.' };
+ millisSinceLastIncomingData: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Elapsed time (in ms) since data was last received.' };
+ attempt: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The reconnection attempt counter.' };
};
type RemoteConnectionGainEvent = {
remoteName: string | undefined;
diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts
index 0b2e94b01bc..1bf27f3153c 100644
--- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts
+++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts
@@ -190,9 +190,9 @@ class InitialRemoteConnectionHealthContribution implements IWorkbenchContributio
type RemoteConnectionSuccessClassification = {
owner: 'alexdima';
comment: 'The initial connection succeeded';
- web: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
+ web: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Is web ui.' };
connectionTimeMs: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Time, in ms, until connected'; isMeasurement: true };
- remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
+ remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of the resolver.' };
};
type RemoteConnectionSuccessEvent = {
web: boolean;
@@ -212,10 +212,10 @@ class InitialRemoteConnectionHealthContribution implements IWorkbenchContributio
type RemoteConnectionFailureClassification = {
owner: 'alexdima';
comment: 'The initial connection failed';
- web: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
- remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
+ web: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Is web ui.' };
+ remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of the resolver.' };
connectionTimeMs: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Time, in ms, until connection failure'; isMeasurement: true };
- message: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
+ message: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Error message' };
};
type RemoteConnectionFailureEvent = {
web: boolean;
diff --git a/src/vs/workbench/contrib/remote/electron-sandbox/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-sandbox/remote.contribution.ts
index 37c91b0b6b8..4e10ac0bea3 100644
--- a/src/vs/workbench/contrib/remote/electron-sandbox/remote.contribution.ts
+++ b/src/vs/workbench/contrib/remote/electron-sandbox/remote.contribution.ts
@@ -117,8 +117,8 @@ class RemoteEmptyWorkbenchPresentation extends Disposable implements IWorkbenchC
return shouldShowExplorer();
}
- const { remoteAuthority, filesToDiff, filesToOpenOrCreate, filesToWait } = environmentService;
- if (remoteAuthority && contextService.getWorkbenchState() === WorkbenchState.EMPTY && !filesToDiff?.length && !filesToOpenOrCreate?.length && !filesToWait) {
+ const { remoteAuthority, filesToDiff, filesToMerge, filesToOpenOrCreate, filesToWait } = environmentService;
+ if (remoteAuthority && contextService.getWorkbenchState() === WorkbenchState.EMPTY && !filesToDiff?.length && !filesToMerge?.length && !filesToOpenOrCreate?.length && !filesToWait) {
remoteAuthorityResolverService.resolveAuthority(remoteAuthority).then(() => {
if (shouldShowExplorer()) {
commandService.executeCommand('workbench.view.explorer');
diff --git a/src/vs/workbench/contrib/snippets/browser/commands/abstractSnippetsActions.ts b/src/vs/workbench/contrib/snippets/browser/commands/abstractSnippetsActions.ts
new file mode 100644
index 00000000000..46f62be9e1c
--- /dev/null
+++ b/src/vs/workbench/contrib/snippets/browser/commands/abstractSnippetsActions.ts
@@ -0,0 +1,29 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { EditorAction2 } from 'vs/editor/browser/editorExtensions';
+import { localize } from 'vs/nls';
+import { Action2, IAction2Options } from 'vs/platform/actions/common/actions';
+
+const defaultOptions: Partial<IAction2Options> = {
+ category: {
+ value: localize('snippets', 'Snippets'),
+ original: 'Snippets'
+ },
+};
+
+export abstract class SnippetsAction extends Action2 {
+
+ constructor(desc: Readonly<IAction2Options>) {
+ super({ ...defaultOptions, ...desc });
+ }
+}
+
+export abstract class SnippetEditorAction extends EditorAction2 {
+
+ constructor(desc: Readonly<IAction2Options>) {
+ super({ ...defaultOptions, ...desc });
+ }
+}
diff --git a/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts b/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts
index 917e0da44e4..3bc1b0b6b89 100644
--- a/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts
+++ b/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts
@@ -3,21 +3,22 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import * as nls from 'vs/nls';
-import { ILanguageService } from 'vs/editor/common/languages/language';
+import { isValidBasename } from 'vs/base/common/extpath';
import { extname } from 'vs/base/common/path';
-import { MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/actions';
-import { IOpenerService } from 'vs/platform/opener/common/opener';
+import { basename, joinPath } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
-import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets.contribution';
-import { IQuickPickItem, IQuickInputService, QuickPickInput } from 'vs/platform/quickinput/common/quickInput';
-import { SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile';
-import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
+import { ILanguageService } from 'vs/editor/common/languages/language';
+import * as nls from 'vs/nls';
+import { MenuId } from 'vs/platform/actions/common/actions';
import { IFileService } from 'vs/platform/files/common/files';
-import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
-import { isValidBasename } from 'vs/base/common/extpath';
-import { joinPath, basename } from 'vs/base/common/resources';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
+import { IOpenerService } from 'vs/platform/opener/common/opener';
+import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput';
+import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
+import { SnippetsAction } from 'vs/workbench/contrib/snippets/browser/commands/abstractSnippetsActions';
+import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets';
+import { SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile';
+import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
namespace ISnippetPick {
@@ -199,7 +200,7 @@ async function createLanguageSnippetFile(pick: ISnippetPick, fileService: IFileS
await textFileService.write(pick.filepath, contents);
}
-registerAction2(class ConfigureSnippets extends Action2 {
+export class ConfigureSnippets extends SnippetsAction {
constructor() {
super({
@@ -221,7 +222,7 @@ registerAction2(class ConfigureSnippets extends Action2 {
});
}
- async run(accessor: ServicesAccessor, ...args: any[]): Promise<any> {
+ async run(accessor: ServicesAccessor): Promise<any> {
const snippetService = accessor.get(ISnippetsService);
const quickInputService = accessor.get(IQuickInputService);
@@ -275,4 +276,4 @@ registerAction2(class ConfigureSnippets extends Action2 {
}
}
-});
+}
diff --git a/src/vs/workbench/contrib/snippets/browser/commands/emptyFileSnippets.ts b/src/vs/workbench/contrib/snippets/browser/commands/emptyFileSnippets.ts
new file mode 100644
index 00000000000..963c92e60cb
--- /dev/null
+++ b/src/vs/workbench/contrib/snippets/browser/commands/emptyFileSnippets.ts
@@ -0,0 +1,116 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { groupBy, isFalsyOrEmpty } from 'vs/base/common/arrays';
+import { compare } from 'vs/base/common/strings';
+import { getCodeEditor } from 'vs/editor/browser/editorBrowser';
+import { ILanguageService } from 'vs/editor/common/languages/language';
+import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2';
+import { localize } from 'vs/nls';
+import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
+import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
+import { SnippetsAction } from 'vs/workbench/contrib/snippets/browser/commands/abstractSnippetsActions';
+import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets';
+import { Snippet } from 'vs/workbench/contrib/snippets/browser/snippetsFile';
+import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
+
+export class SelectSnippetForEmptyFile extends SnippetsAction {
+
+ static readonly Id = 'workbench.action.populateFromSnippet';
+
+ constructor() {
+ super({
+ id: SelectSnippetForEmptyFile.Id,
+ title: {
+ value: localize('label', 'Populate from Snippet'),
+ original: 'Populate from Snippet'
+ },
+ f1: true,
+ });
+ }
+
+ async run(accessor: ServicesAccessor): Promise<void> {
+ const snippetService = accessor.get(ISnippetsService);
+ const quickInputService = accessor.get(IQuickInputService);
+ const editorService = accessor.get(IEditorService);
+ const langService = accessor.get(ILanguageService);
+
+ const editor = getCodeEditor(editorService.activeTextEditorControl);
+ if (!editor || !editor.hasModel()) {
+ return;
+ }
+
+ const snippets = await snippetService.getSnippets(undefined, { topLevelSnippets: true, noRecencySort: true, includeNoPrefixSnippets: true });
+ if (snippets.length === 0) {
+ return;
+ }
+
+ const selection = await this._pick(quickInputService, langService, snippets);
+ if (!selection) {
+ return;
+ }
+
+ if (editor.hasModel()) {
+ // apply snippet edit -> replaces everything
+ SnippetController2.get(editor)?.apply([{
+ range: editor.getModel().getFullModelRange(),
+ template: selection.snippet.body
+ }]);
+
+ // set language if possible
+ if (langService.isRegisteredLanguageId(selection.langId)) {
+ editor.getModel().setMode(selection.langId);
+ }
+ }
+ }
+
+ private async _pick(quickInputService: IQuickInputService, langService: ILanguageService, snippets: Snippet[]) {
+
+ // spread snippet onto each language it supports
+ type SnippetAndLanguage = { langId: string; snippet: Snippet };
+ const all: SnippetAndLanguage[] = [];
+ for (const snippet of snippets) {
+ if (isFalsyOrEmpty(snippet.scopes)) {
+ all.push({ langId: '', snippet });
+ } else {
+ for (const langId of snippet.scopes) {
+ all.push({ langId, snippet });
+ }
+ }
+ }
+
+ type SnippetAndLanguagePick = IQuickPickItem & { snippet: SnippetAndLanguage };
+ const picks: (SnippetAndLanguagePick | IQuickPickSeparator)[] = [];
+
+ const groups = groupBy(all, (a, b) => compare(a.langId, b.langId));
+
+ for (const group of groups) {
+ let first = true;
+ for (const item of group) {
+
+ if (first) {
+ picks.push({
+ type: 'separator',
+ label: langService.getLanguageName(item.langId) ?? item.langId
+ });
+ first = false;
+ }
+
+ picks.push({
+ snippet: item,
+ label: item.snippet.prefix || item.snippet.name,
+ detail: item.snippet.description
+ });
+ }
+ }
+
+ const pick = await quickInputService.pick(picks, {
+ placeHolder: localize('placeholder', 'Select a snippet'),
+ matchOnDetail: true,
+ });
+
+ return pick?.snippet;
+ }
+}
diff --git a/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts b/src/vs/workbench/contrib/snippets/browser/commands/insertSnippet.ts
index 3a0088a1f48..aba3f4c5582 100644
--- a/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts
+++ b/src/vs/workbench/contrib/snippets/browser/commands/insertSnippet.ts
@@ -3,19 +3,18 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import * as nls from 'vs/nls';
-import { registerEditorAction, ServicesAccessor, EditorAction } from 'vs/editor/browser/editorExtensions';
+import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
+import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
+import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { ILanguageService } from 'vs/editor/common/languages/language';
-import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands';
-import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets.contribution';
import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2';
-import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
-import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
-import { Snippet, SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile';
+import * as nls from 'vs/nls';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
+import { SnippetEditorAction } from 'vs/workbench/contrib/snippets/browser/commands/abstractSnippetsActions';
import { pickSnippet } from 'vs/workbench/contrib/snippets/browser/snippetPicker';
-
+import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets';
+import { Snippet, SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile';
class Args {
@@ -45,13 +44,16 @@ class Args {
) { }
}
-class InsertSnippetAction extends EditorAction {
+export class InsertSnippetAction extends SnippetEditorAction {
constructor() {
super({
id: 'editor.action.insertSnippet',
- label: nls.localize('snippet.suggestions.label', "Insert Snippet"),
- alias: 'Insert Snippet',
+ title: {
+ value: nls.localize('snippet.suggestions.label', "Insert Snippet"),
+ original: 'Insert Snippet'
+ },
+ f1: true,
precondition: EditorContextKeys.writable,
description: {
description: `Insert Snippet`,
@@ -77,7 +79,8 @@ class InsertSnippetAction extends EditorAction {
});
}
- async run(accessor: ServicesAccessor, editor: ICodeEditor, arg: any): Promise<void> {
+ async runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, arg: any) {
+
const languageService = accessor.get(ILanguageService);
const snippetService = accessor.get(ISnippetsService);
@@ -95,6 +98,7 @@ class InsertSnippetAction extends EditorAction {
if (snippet) {
return resolve(new Snippet(
+ false,
[],
'',
'',
@@ -102,6 +106,7 @@ class InsertSnippetAction extends EditorAction {
snippet,
'',
SnippetSource.User,
+ `random/${Math.random()}`
));
}
@@ -143,12 +148,6 @@ class InsertSnippetAction extends EditorAction {
clipboardText = await clipboardService.readText();
}
SnippetController2.get(editor)?.insert(snippet.codeSnippet, { clipboardText });
+ snippetService.updateUsageTimestamp(snippet);
}
}
-
-registerEditorAction(InsertSnippetAction);
-
-// compatibility command to make sure old keybinding are still working
-CommandsRegistry.registerCommand('editor.action.showSnippets', accessor => {
- return accessor.get(ICommandService).executeCommand('editor.action.insertSnippet');
-});
diff --git a/src/vs/workbench/contrib/snippets/browser/commands/surroundWithSnippet.ts b/src/vs/workbench/contrib/snippets/browser/commands/surroundWithSnippet.ts
new file mode 100644
index 00000000000..7a771724f3a
--- /dev/null
+++ b/src/vs/workbench/contrib/snippets/browser/commands/surroundWithSnippet.ts
@@ -0,0 +1,159 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { IDisposable } from 'vs/base/common/lifecycle';
+import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
+import { Position } from 'vs/editor/common/core/position';
+import { IRange, Range } from 'vs/editor/common/core/range';
+import { Selection } from 'vs/editor/common/core/selection';
+import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
+import { CodeAction, CodeActionList, CodeActionProvider } from 'vs/editor/common/languages';
+import { ITextModel } from 'vs/editor/common/model';
+import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
+import { CodeActionKind } from 'vs/editor/contrib/codeAction/browser/types';
+import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2';
+import { localize } from 'vs/nls';
+import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
+import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
+import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
+import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
+import { SnippetEditorAction } from 'vs/workbench/contrib/snippets/browser/commands/abstractSnippetsActions';
+import { pickSnippet } from 'vs/workbench/contrib/snippets/browser/snippetPicker';
+import { Snippet } from 'vs/workbench/contrib/snippets/browser/snippetsFile';
+import { ISnippetsService } from '../snippets';
+
+async function getSurroundableSnippets(snippetsService: ISnippetsService, model: ITextModel, position: Position, includeDisabledSnippets: boolean): Promise<Snippet[]> {
+
+ const { lineNumber, column } = position;
+ model.tokenization.tokenizeIfCheap(lineNumber);
+ const languageId = model.getLanguageIdAtPosition(lineNumber, column);
+
+ const allSnippets = await snippetsService.getSnippets(languageId, { includeNoPrefixSnippets: true, includeDisabledSnippets });
+ return allSnippets.filter(snippet => snippet.usesSelection);
+}
+
+export class SurroundWithSnippetEditorAction extends SnippetEditorAction {
+
+ static readonly options = {
+ id: 'editor.action.surroundWithSnippet',
+ title: {
+ value: localize('label', 'Surround With Snippet...'),
+ original: 'Surround With Snippet...'
+ }
+ };
+
+ constructor() {
+ super({
+ ...SurroundWithSnippetEditorAction.options,
+ precondition: ContextKeyExpr.and(
+ EditorContextKeys.writable,
+ EditorContextKeys.hasNonEmptySelection
+ ),
+ f1: true,
+ });
+ }
+
+ async runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor) {
+ if (!editor.hasModel()) {
+ return;
+ }
+
+ const instaService = accessor.get(IInstantiationService);
+ const snippetsService = accessor.get(ISnippetsService);
+ const clipboardService = accessor.get(IClipboardService);
+
+ const snippets = await getSurroundableSnippets(snippetsService, editor.getModel(), editor.getPosition(), true);
+ if (!snippets.length) {
+ return;
+ }
+
+ const snippet = await instaService.invokeFunction(pickSnippet, snippets);
+ if (!snippet) {
+ return;
+ }
+
+ let clipboardText: string | undefined;
+ if (snippet.needsClipboard) {
+ clipboardText = await clipboardService.readText();
+ }
+
+ SnippetController2.get(editor)?.insert(snippet.codeSnippet, { clipboardText });
+ snippetsService.updateUsageTimestamp(snippet);
+ }
+}
+
+
+export class SurroundWithSnippetCodeActionProvider implements CodeActionProvider, IWorkbenchContribution {
+
+ private static readonly _MAX_CODE_ACTIONS = 4;
+
+ private static readonly _overflowCommandCodeAction: CodeAction = {
+ kind: CodeActionKind.Refactor.value,
+ title: SurroundWithSnippetEditorAction.options.title.value,
+ command: {
+ id: SurroundWithSnippetEditorAction.options.id,
+ title: SurroundWithSnippetEditorAction.options.title.value,
+ },
+ };
+
+ private readonly _registration: IDisposable;
+
+ constructor(
+ @ISnippetsService private readonly _snippetService: ISnippetsService,
+ @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService,
+ ) {
+ this._registration = languageFeaturesService.codeActionProvider.register('*', this);
+ }
+
+ dispose(): void {
+ this._registration.dispose();
+ }
+
+ async provideCodeActions(model: ITextModel, range: Range | Selection): Promise<CodeActionList | undefined> {
+
+ if (range.isEmpty()) {
+ return undefined;
+ }
+
+ const position = Selection.isISelection(range) ? range.getPosition() : range.getStartPosition();
+ const snippets = await getSurroundableSnippets(this._snippetService, model, position, false);
+ if (!snippets.length) {
+ return undefined;
+ }
+
+ const actions: CodeAction[] = [];
+ const hasMore = snippets.length > SurroundWithSnippetCodeActionProvider._MAX_CODE_ACTIONS;
+ const len = Math.min(snippets.length, SurroundWithSnippetCodeActionProvider._MAX_CODE_ACTIONS);
+
+ for (let i = 0; i < len; i++) {
+ actions.push(this._makeCodeActionForSnippet(snippets[i], model, range));
+ }
+ if (hasMore) {
+ actions.push(SurroundWithSnippetCodeActionProvider._overflowCommandCodeAction);
+ }
+ return {
+ actions,
+ dispose() { }
+ };
+ }
+
+ private _makeCodeActionForSnippet(snippet: Snippet, model: ITextModel, range: IRange): CodeAction {
+ return {
+ title: localize('codeAction', "Surround With: {0}", snippet.name),
+ kind: CodeActionKind.Refactor.value,
+ edit: {
+ edits: [{
+ versionId: model.getVersionId(),
+ resource: model.uri,
+ textEdit: {
+ range,
+ text: snippet.body,
+ insertAsSnippet: true,
+ }
+ }]
+ }
+ };
+ }
+}
diff --git a/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts
index 3e8ea30e666..6569bf2ce1f 100644
--- a/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts
+++ b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts
@@ -8,17 +8,29 @@ import { compare, compareSubstring } from 'vs/base/common/strings';
import { Position } from 'vs/editor/common/core/position';
import { IRange, Range } from 'vs/editor/common/core/range';
import { ITextModel } from 'vs/editor/common/model';
-import { CompletionItem, CompletionItemKind, CompletionItemProvider, CompletionList, CompletionItemInsertTextRule, CompletionContext, CompletionTriggerKind, CompletionItemLabel } from 'vs/editor/common/languages';
+import { CompletionItem, CompletionItemKind, CompletionItemProvider, CompletionList, CompletionItemInsertTextRule, CompletionContext, CompletionTriggerKind, CompletionItemLabel, Command } from 'vs/editor/common/languages';
import { ILanguageService } from 'vs/editor/common/languages/language';
import { SnippetParser } from 'vs/editor/contrib/snippet/browser/snippetParser';
import { localize } from 'vs/nls';
-import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets.contribution';
+import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets';
import { Snippet, SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile';
import { isPatternInWord } from 'vs/base/common/filters';
import { StopWatch } from 'vs/base/common/stopwatch';
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
import { getWordAtText } from 'vs/editor/common/core/wordHelper';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
+import { CommandsRegistry } from 'vs/platform/commands/common/commands';
+
+
+const markSnippetAsUsed = '_snippet.markAsUsed';
+
+CommandsRegistry.registerCommand(markSnippetAsUsed, (accessor, ...args) => {
+ const snippetsService = accessor.get(ISnippetsService);
+ const [first] = args;
+ if (first instanceof Snippet) {
+ snippetsService.updateUsageTimestamp(first);
+ }
+});
export class SnippetCompletion implements CompletionItem {
@@ -31,10 +43,11 @@ export class SnippetCompletion implements CompletionItem {
kind: CompletionItemKind;
insertTextRules: CompletionItemInsertTextRule;
extensionId?: ExtensionIdentifier;
+ command?: Command;
constructor(
readonly snippet: Snippet,
- range: IRange | { insert: IRange; replace: IRange }
+ range: IRange | { insert: IRange; replace: IRange },
) {
this.label = { label: snippet.prefix, description: snippet.name };
this.detail = localize('detail.snippet', "{0} ({1})", snippet.description || snippet.name, snippet.source);
@@ -44,6 +57,7 @@ export class SnippetCompletion implements CompletionItem {
this.sortText = `${snippet.snippetSource === SnippetSource.Extension ? 'z' : 'a'}-${snippet.prefix}`;
this.kind = CompletionItemKind.Snippet;
this.insertTextRules = CompletionItemInsertTextRule.InsertAsSnippet;
+ this.command = { id: markSnippetAsUsed, title: '', arguments: [snippet] };
}
resolve(): this {
diff --git a/src/vs/workbench/contrib/snippets/browser/snippetPicker.ts b/src/vs/workbench/contrib/snippets/browser/snippetPicker.ts
index 9cb08b37047..0f72c05ab50 100644
--- a/src/vs/workbench/contrib/snippets/browser/snippetPicker.ts
+++ b/src/vs/workbench/contrib/snippets/browser/snippetPicker.ts
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
-import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets.contribution';
+import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets';
import { Snippet, SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile';
import { IQuickPickItem, IQuickInputService, QuickPickInput } from 'vs/platform/quickinput/common/quickInput';
import { Codicon } from 'vs/base/common/codicons';
@@ -27,7 +27,7 @@ export async function pickSnippet(accessor: ServicesAccessor, languageIdOrSnippe
snippets = (await snippetService.getSnippets(languageIdOrSnippets, { includeDisabledSnippets: true, includeNoPrefixSnippets: true }));
}
- snippets.sort(Snippet.compare);
+ snippets.sort((a, b) => a.snippetSource - b.snippetSource);
const makeSnippetPicks = () => {
const result: QuickPickInput<ISnippetPick>[] = [];
diff --git a/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts b/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts
index 1e1bdd82c69..39f0c8233c5 100644
--- a/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts
+++ b/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts
@@ -4,34 +4,37 @@
*--------------------------------------------------------------------------------------------*/
import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema';
-import { Registry } from 'vs/platform/registry/common/platform';
-import * as JSONContributionRegistry from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import * as nls from 'vs/nls';
-import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
-import { SnippetFile, Snippet } from 'vs/workbench/contrib/snippets/browser/snippetsFile';
-
-export const ISnippetsService = createDecorator<ISnippetsService>('snippetService');
-
-export interface ISnippetGetOptions {
- includeDisabledSnippets?: boolean;
- includeNoPrefixSnippets?: boolean;
-}
-
-export interface ISnippetsService {
-
- readonly _serviceBrand: undefined;
-
- getSnippetFiles(): Promise<Iterable<SnippetFile>>;
-
- isEnabled(snippet: Snippet): boolean;
-
- updateEnablement(snippet: Snippet, enabled: boolean): void;
-
- getSnippets(languageId: string, opt?: ISnippetGetOptions): Promise<Snippet[]>;
-
- getSnippetsSync(languageId: string, opt?: ISnippetGetOptions): Snippet[];
-}
-
+import { registerAction2 } from 'vs/platform/actions/common/actions';
+import { CommandsRegistry } from 'vs/platform/commands/common/commands';
+import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
+import * as JSONContributionRegistry from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
+import { Registry } from 'vs/platform/registry/common/platform';
+import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
+import { ConfigureSnippets } from 'vs/workbench/contrib/snippets/browser/commands/configureSnippets';
+import { SelectSnippetForEmptyFile } from 'vs/workbench/contrib/snippets/browser/commands/emptyFileSnippets';
+import { InsertSnippetAction } from 'vs/workbench/contrib/snippets/browser/commands/insertSnippet';
+import { SurroundWithSnippetCodeActionProvider, SurroundWithSnippetEditorAction } from 'vs/workbench/contrib/snippets/browser/commands/surroundWithSnippet';
+import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets';
+import { SnippetsService } from 'vs/workbench/contrib/snippets/browser/snippetsService';
+import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
+
+import 'vs/workbench/contrib/snippets/browser/tabCompletion';
+
+// service
+registerSingleton(ISnippetsService, SnippetsService, true);
+
+// actions
+registerAction2(InsertSnippetAction);
+CommandsRegistry.registerCommandAlias('editor.action.showSnippets', 'editor.action.insertSnippet');
+registerAction2(SurroundWithSnippetEditorAction);
+registerAction2(ConfigureSnippets);
+registerAction2(SelectSnippetForEmptyFile);
+
+// workbench contribs
+Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(SurroundWithSnippetCodeActionProvider, LifecyclePhase.Restored);
+
+// schema
const languageScopeSchemaId = 'vscode://schemas/snippets';
const snippetSchemaProperties: IJSONSchemaMap = {
@@ -39,6 +42,10 @@ const snippetSchemaProperties: IJSONSchemaMap = {
description: nls.localize('snippetSchema.json.prefix', 'The prefix to use when selecting the snippet in intellisense'),
type: ['string', 'array']
},
+ isTopLevel: {
+ description: nls.localize('snippetSchema.json.isTopLevel', 'The snippet is only applicable to empty files.'),
+ type: 'boolean'
+ },
body: {
markdownDescription: nls.localize('snippetSchema.json.body', 'The snippet content. Use `$1`, `${1:defaultText}` to define cursor positions, use `$0` for the final cursor position. Insert variable values with `${varName}` and `${varName:defaultText}`, e.g. `This is file: $TM_FILENAME`.'),
type: ['string', 'array'],
diff --git a/src/vs/workbench/contrib/snippets/browser/snippets.ts b/src/vs/workbench/contrib/snippets/browser/snippets.ts
new file mode 100644
index 00000000000..fa485ab0f2f
--- /dev/null
+++ b/src/vs/workbench/contrib/snippets/browser/snippets.ts
@@ -0,0 +1,33 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
+import { SnippetFile, Snippet } from 'vs/workbench/contrib/snippets/browser/snippetsFile';
+
+export const ISnippetsService = createDecorator<ISnippetsService>('snippetService');
+
+export interface ISnippetGetOptions {
+ includeDisabledSnippets?: boolean;
+ includeNoPrefixSnippets?: boolean;
+ noRecencySort?: boolean;
+ topLevelSnippets?: boolean;
+}
+
+export interface ISnippetsService {
+
+ readonly _serviceBrand: undefined;
+
+ getSnippetFiles(): Promise<Iterable<SnippetFile>>;
+
+ isEnabled(snippet: Snippet): boolean;
+
+ updateEnablement(snippet: Snippet, enabled: boolean): void;
+
+ updateUsageTimestamp(snippet: Snippet): void;
+
+ getSnippets(languageId: string | undefined, opt?: ISnippetGetOptions): Promise<Snippet[]>;
+
+ getSnippetsSync(languageId: string, opt?: ISnippetGetOptions): Snippet[];
+}
diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts b/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts
index 784296b70de..b6ce272ef28 100644
--- a/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts
+++ b/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts
@@ -8,7 +8,6 @@ import { localize } from 'vs/nls';
import { extname, basename } from 'vs/base/common/path';
import { SnippetParser, Variable, Placeholder, Text } from 'vs/editor/contrib/snippet/browser/snippetParser';
import { KnownSnippetVariableNames } from 'vs/editor/contrib/snippet/browser/snippetVariables';
-import { isFalsyOrWhitespace } from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
import { IFileService } from 'vs/platform/files/common/files';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
@@ -106,6 +105,7 @@ export class Snippet {
readonly prefixLow: string;
constructor(
+ readonly isTopLevel: boolean,
readonly scopes: string[],
readonly name: string,
readonly prefix: string,
@@ -113,7 +113,7 @@ export class Snippet {
readonly body: string,
readonly source: string,
readonly snippetSource: SnippetSource,
- readonly snippetIdentifier?: string,
+ readonly snippetIdentifier: string,
readonly extensionId?: ExtensionIdentifier,
) {
this.prefixLow = prefix.toLowerCase();
@@ -139,30 +139,13 @@ export class Snippet {
get usesSelection(): boolean {
return this._bodyInsights.value.usesSelectionVariable;
}
-
- static compare(a: Snippet, b: Snippet): number {
- if (a.snippetSource < b.snippetSource) {
- return -1;
- } else if (a.snippetSource > b.snippetSource) {
- return 1;
- } else if (a.source < b.source) {
- return -1;
- } else if (a.source > b.source) {
- return 1;
- } else if (a.name > b.name) {
- return 1;
- } else if (a.name < b.name) {
- return -1;
- } else {
- return 0;
- }
- }
}
interface JsonSerializedSnippet {
+ isTopLevel?: boolean;
body: string | string[];
- scope: string;
+ scope?: string;
prefix: string | string[] | undefined;
description: string;
}
@@ -195,7 +178,7 @@ export class SnippetFile {
public defaultScopes: string[] | undefined,
private readonly _extension: IExtensionDescription | undefined,
private readonly _fileService: IFileService,
- private readonly _extensionResourceLoaderService: IExtensionResourceLoaderService
+ private readonly _extensionResourceLoaderService: IExtensionResourceLoaderService,
) {
this.isGlobalSnippets = extname(location.path) === '.code-snippets';
this.isUserSnippets = !this._extension;
@@ -278,7 +261,7 @@ export class SnippetFile {
private _parseSnippet(name: string, snippet: JsonSerializedSnippet, bucket: Snippet[]): void {
- let { prefix, body, description } = snippet;
+ let { isTopLevel, prefix, body, description } = snippet;
if (!prefix) {
prefix = '';
@@ -299,7 +282,7 @@ export class SnippetFile {
if (this.defaultScopes) {
scopes = this.defaultScopes;
} else if (typeof snippet.scope === 'string') {
- scopes = snippet.scope.split(',').map(s => s.trim()).filter(s => !isFalsyOrWhitespace(s));
+ scopes = snippet.scope.split(',').map(s => s.trim()).filter(Boolean);
} else {
scopes = [];
}
@@ -323,6 +306,7 @@ export class SnippetFile {
for (const _prefix of Array.isArray(prefix) ? prefix : Iterable.single(prefix)) {
bucket.push(new Snippet(
+ Boolean(isTopLevel),
scopes,
name,
_prefix,
@@ -330,7 +314,7 @@ export class SnippetFile {
body,
source,
this.source,
- this._extension && `${relativePath(this._extension.extensionLocation, this.location)}/${name}`,
+ this._extension ? `${relativePath(this._extension.extensionLocation, this.location)}/${name}` : `${basename(this.location.path)}/${name}`,
this._extension?.identifier,
));
}
diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts
index abc007e863d..3c5ed85f665 100644
--- a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts
+++ b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts
@@ -14,11 +14,10 @@ import { setSnippetSuggestSupport } from 'vs/editor/contrib/suggest/browser/sugg
import { localize } from 'vs/nls';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { FileChangeType, IFileService } from 'vs/platform/files/common/files';
-import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { ILogService } from 'vs/platform/log/common/log';
import { IWorkspace, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
-import { ISnippetGetOptions, ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets.contribution';
+import { ISnippetGetOptions, ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets';
import { Snippet, SnippetFile, SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile';
import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { languagesExtPoint } from 'vs/workbench/services/language/common/languageService';
@@ -31,6 +30,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
+import { insertInto } from 'vs/base/common/arrays';
namespace snippetExt {
@@ -167,7 +167,43 @@ class SnippetEnablement {
}
}
-class SnippetsService implements ISnippetsService {
+class SnippetUsageTimestamps {
+
+ private static _key = 'snippets.usageTimestamps';
+
+ private readonly _usages: Map<string, number>;
+
+ constructor(
+ @IStorageService private readonly _storageService: IStorageService,
+ ) {
+
+ const raw = _storageService.get(SnippetUsageTimestamps._key, StorageScope.PROFILE, '');
+ let data: [string, number][] | undefined;
+ try {
+ data = JSON.parse(raw);
+ } catch {
+ data = [];
+ }
+
+ this._usages = Array.isArray(data) ? new Map(data) : new Map();
+ }
+
+ getUsageTimestamp(id: string): number | undefined {
+ return this._usages.get(id);
+ }
+
+ updateUsageTimestamp(id: string): void {
+ // map uses insertion order, we want most recent at the end
+ this._usages.delete(id);
+ this._usages.set(id, Date.now());
+
+ // persist last 100 item
+ const all = [...this._usages].slice(-100);
+ this._storageService.store(SnippetUsageTimestamps._key, JSON.stringify(all), StorageScope.PROFILE, StorageTarget.USER);
+ }
+}
+
+export class SnippetsService implements ISnippetsService {
declare readonly _serviceBrand: undefined;
@@ -175,6 +211,7 @@ class SnippetsService implements ISnippetsService {
private readonly _pendingWork: Promise<any>[] = [];
private readonly _files = new ResourceMap<SnippetFile>();
private readonly _enablement: SnippetEnablement;
+ private readonly _usageTimestamps: SnippetUsageTimestamps;
constructor(
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
@@ -198,6 +235,7 @@ class SnippetsService implements ISnippetsService {
setSnippetSuggestSupport(new SnippetCompletionProvider(this._languageService, this, languageConfigurationService));
this._enablement = instantiationService.createInstance(SnippetEnablement);
+ this._usageTimestamps = instantiationService.createInstance(SnippetUsageTimestamps);
}
dispose(): void {
@@ -205,13 +243,15 @@ class SnippetsService implements ISnippetsService {
}
isEnabled(snippet: Snippet): boolean {
- return !snippet.snippetIdentifier || !this._enablement.isIgnored(snippet.snippetIdentifier);
+ return !this._enablement.isIgnored(snippet.snippetIdentifier);
}
updateEnablement(snippet: Snippet, enabled: boolean): void {
- if (snippet.snippetIdentifier) {
- this._enablement.updateIgnored(snippet.snippetIdentifier, !enabled);
- }
+ this._enablement.updateIgnored(snippet.snippetIdentifier, !enabled);
+ }
+
+ updateUsageTimestamp(snippet: Snippet): void {
+ this._usageTimestamps.updateUsageTimestamp(snippet.snippetIdentifier);
}
private _joinSnippets(): Promise<any> {
@@ -225,22 +265,31 @@ class SnippetsService implements ISnippetsService {
return this._files.values();
}
- async getSnippets(languageId: string, opts?: ISnippetGetOptions): Promise<Snippet[]> {
+ async getSnippets(languageId: string | undefined, opts?: ISnippetGetOptions): Promise<Snippet[]> {
await this._joinSnippets();
const result: Snippet[] = [];
const promises: Promise<any>[] = [];
- if (this._languageService.isRegisteredLanguageId(languageId)) {
+ if (languageId) {
+ if (this._languageService.isRegisteredLanguageId(languageId)) {
+ for (const file of this._files.values()) {
+ promises.push(file.load()
+ .then(file => file.select(languageId, result))
+ .catch(err => this._logService.error(err, file.location.toString()))
+ );
+ }
+ }
+ } else {
for (const file of this._files.values()) {
promises.push(file.load()
- .then(file => file.select(languageId, result))
+ .then(file => insertInto(result, result.length, file.data))
.catch(err => this._logService.error(err, file.location.toString()))
);
}
}
await Promise.all(promises);
- return this._filterSnippets(result, opts);
+ return this._filterAndSortSnippets(result, opts);
}
getSnippetsSync(languageId: string, opts?: ISnippetGetOptions): Snippet[] {
@@ -253,16 +302,62 @@ class SnippetsService implements ISnippetsService {
file.select(languageId, result);
}
}
- return this._filterSnippets(result, opts);
+ return this._filterAndSortSnippets(result, opts);
}
- private _filterSnippets(snippets: Snippet[], opts?: ISnippetGetOptions): Snippet[] {
- return snippets.filter(snippet => {
- return (snippet.prefix || opts?.includeNoPrefixSnippets) // prefix or no-prefix wanted
- && (this.isEnabled(snippet) || opts?.includeDisabledSnippets); // enabled or disabled wanted
+ private _filterAndSortSnippets(snippets: Snippet[], opts?: ISnippetGetOptions): Snippet[] {
+
+ const result: Snippet[] = [];
+
+ for (const snippet of snippets) {
+ if (!snippet.prefix && !opts?.includeNoPrefixSnippets) {
+ // prefix or no-prefix wanted
+ continue;
+ }
+ if (!this.isEnabled(snippet) && !opts?.includeDisabledSnippets) {
+ // enabled or disabled wanted
+ continue;
+ }
+ if (typeof opts?.topLevelSnippets === 'boolean' && opts.topLevelSnippets !== snippet.isTopLevel) {
+ // isTopLevel requested but mismatching
+ continue;
+ }
+ result.push(snippet);
+ }
+
+
+ return result.sort((a, b) => {
+ let result = 0;
+ if (!opts?.noRecencySort) {
+ const val1 = this._usageTimestamps.getUsageTimestamp(a.snippetIdentifier) ?? -1;
+ const val2 = this._usageTimestamps.getUsageTimestamp(b.snippetIdentifier) ?? -1;
+ result = val2 - val1;
+ }
+ if (result === 0) {
+ result = this._compareSnippet(a, b);
+ }
+ return result;
});
}
+ private _compareSnippet(a: Snippet, b: Snippet): number {
+ if (a.snippetSource < b.snippetSource) {
+ return -1;
+ } else if (a.snippetSource > b.snippetSource) {
+ return 1;
+ } else if (a.source < b.source) {
+ return -1;
+ } else if (a.source > b.source) {
+ return 1;
+ } else if (a.name > b.name) {
+ return 1;
+ } else if (a.name < b.name) {
+ return -1;
+ } else {
+ return 0;
+ }
+ }
+
// --- loading, watching
private _initExtensionSnippets(): void {
@@ -408,7 +503,6 @@ class SnippetsService implements ISnippetsService {
}
}
-registerSingleton(ISnippetsService, SnippetsService, true);
export interface ISimpleModel {
getLineContent(lineNumber: number): string;
diff --git a/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts b/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts
deleted file mode 100644
index 80d25b05968..00000000000
--- a/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
-import { EditorAction2 } from 'vs/editor/browser/editorExtensions';
-import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
-import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2';
-import { localize } from 'vs/nls';
-import { registerAction2 } from 'vs/platform/actions/common/actions';
-import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
-import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
-import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
-import { pickSnippet } from 'vs/workbench/contrib/snippets/browser/snippetPicker';
-import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets.contribution';
-
-
-registerAction2(class SurroundWithAction extends EditorAction2 {
-
- constructor() {
- super({
- id: 'editor.action.surroundWithSnippet',
- title: { value: localize('label', 'Surround With Snippet...'), original: 'Surround With Snippet...' },
- precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasNonEmptySelection),
- f1: true
- });
- }
-
- async runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]) {
-
- const snippetService = accessor.get(ISnippetsService);
- const clipboardService = accessor.get(IClipboardService);
- const instaService = accessor.get(IInstantiationService);
-
- if (!editor.hasModel()) {
- return;
- }
-
- const { lineNumber, column } = editor.getPosition();
- editor.getModel().tokenization.tokenizeIfCheap(lineNumber);
- const languageId = editor.getModel().getLanguageIdAtPosition(lineNumber, column);
-
- const allSnippets = await snippetService.getSnippets(languageId, { includeNoPrefixSnippets: true, includeDisabledSnippets: true });
- const surroundSnippets = allSnippets.filter(snippet => snippet.usesSelection);
- const snippet = await instaService.invokeFunction(pickSnippet, surroundSnippets);
-
- if (!snippet) {
- return;
- }
-
-
- let clipboardText: string | undefined;
- if (snippet.needsClipboard) {
- clipboardText = await clipboardService.readText();
- }
-
- SnippetController2.get(editor)?.insert(snippet.codeSnippet, { clipboardText });
- }
-});
diff --git a/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts b/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts
index 1f4afaa0a94..0abc197abeb 100644
--- a/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts
+++ b/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts
@@ -6,7 +6,7 @@
import { KeyCode } from 'vs/base/common/keyCodes';
import { RawContextKey, IContextKeyService, ContextKeyExpr, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
-import { ISnippetsService } from './snippets.contribution';
+import { ISnippetsService } from './snippets';
import { getNonWhitespacePrefix } from './snippetsService';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
diff --git a/src/vs/workbench/contrib/snippets/test/browser/snippetFile.test.ts b/src/vs/workbench/contrib/snippets/test/browser/snippetFile.test.ts
index dffa5ea30ef..e9bb5b9853c 100644
--- a/src/vs/workbench/contrib/snippets/test/browser/snippetFile.test.ts
+++ b/src/vs/workbench/contrib/snippets/test/browser/snippetFile.test.ts
@@ -7,6 +7,7 @@ import * as assert from 'assert';
import { SnippetFile, Snippet, SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile';
import { URI } from 'vs/base/common/uri';
import { SnippetParser } from 'vs/editor/contrib/snippet/browser/snippetParser';
+import { generateUuid } from 'vs/base/common/uuid';
suite('Snippets', function () {
@@ -24,12 +25,12 @@ suite('Snippets', function () {
assert.strictEqual(bucket.length, 0);
file = new TestSnippetFile(URI.file('somepath/foo.code-snippets'), [
- new Snippet(['foo'], 'FooSnippet1', 'foo', '', 'snippet', 'test', SnippetSource.User),
- new Snippet(['foo'], 'FooSnippet2', 'foo', '', 'snippet', 'test', SnippetSource.User),
- new Snippet(['bar'], 'BarSnippet1', 'foo', '', 'snippet', 'test', SnippetSource.User),
- new Snippet(['bar.comment'], 'BarSnippet2', 'foo', '', 'snippet', 'test', SnippetSource.User),
- new Snippet(['bar.strings'], 'BarSnippet2', 'foo', '', 'snippet', 'test', SnippetSource.User),
- new Snippet(['bazz', 'bazz'], 'BazzSnippet1', 'foo', '', 'snippet', 'test', SnippetSource.User),
+ new Snippet(false, ['foo'], 'FooSnippet1', 'foo', '', 'snippet', 'test', SnippetSource.User, generateUuid()),
+ new Snippet(false, ['foo'], 'FooSnippet2', 'foo', '', 'snippet', 'test', SnippetSource.User, generateUuid()),
+ new Snippet(false, ['bar'], 'BarSnippet1', 'foo', '', 'snippet', 'test', SnippetSource.User, generateUuid()),
+ new Snippet(false, ['bar.comment'], 'BarSnippet2', 'foo', '', 'snippet', 'test', SnippetSource.User, generateUuid()),
+ new Snippet(false, ['bar.strings'], 'BarSnippet2', 'foo', '', 'snippet', 'test', SnippetSource.User, generateUuid()),
+ new Snippet(false, ['bazz', 'bazz'], 'BazzSnippet1', 'foo', '', 'snippet', 'test', SnippetSource.User, generateUuid()),
]);
bucket = [];
@@ -56,8 +57,8 @@ suite('Snippets', function () {
test('SnippetFile#select - any scope', function () {
const file = new TestSnippetFile(URI.file('somepath/foo.code-snippets'), [
- new Snippet([], 'AnySnippet1', 'foo', '', 'snippet', 'test', SnippetSource.User),
- new Snippet(['foo'], 'FooSnippet1', 'foo', '', 'snippet', 'test', SnippetSource.User),
+ new Snippet(false, [], 'AnySnippet1', 'foo', '', 'snippet', 'test', SnippetSource.User, generateUuid()),
+ new Snippet(false, ['foo'], 'FooSnippet1', 'foo', '', 'snippet', 'test', SnippetSource.User, generateUuid()),
]);
const bucket: Snippet[] = [];
@@ -69,7 +70,7 @@ suite('Snippets', function () {
test('Snippet#needsClipboard', function () {
function assertNeedsClipboard(body: string, expected: boolean): void {
- const snippet = new Snippet(['foo'], 'FooSnippet1', 'foo', '', body, 'test', SnippetSource.User);
+ const snippet = new Snippet(false, ['foo'], 'FooSnippet1', 'foo', '', body, 'test', SnippetSource.User, generateUuid());
assert.strictEqual(snippet.needsClipboard, expected);
assert.strictEqual(SnippetParser.guessNeedsClipboard(body), expected);
@@ -86,7 +87,7 @@ suite('Snippets', function () {
test('Snippet#isTrivial', function () {
function assertIsTrivial(body: string, expected: boolean): void {
- const snippet = new Snippet(['foo'], 'FooSnippet1', 'foo', '', body, 'test', SnippetSource.User);
+ const snippet = new Snippet(false, ['foo'], 'FooSnippet1', 'foo', '', body, 'test', SnippetSource.User, generateUuid());
assert.strictEqual(snippet.isTrivial, expected);
}
diff --git a/src/vs/workbench/contrib/snippets/test/browser/snippetsRewrite.test.ts b/src/vs/workbench/contrib/snippets/test/browser/snippetsRewrite.test.ts
index 50c826d5868..54e7e2794f0 100644
--- a/src/vs/workbench/contrib/snippets/test/browser/snippetsRewrite.test.ts
+++ b/src/vs/workbench/contrib/snippets/test/browser/snippetsRewrite.test.ts
@@ -4,12 +4,13 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
+import { generateUuid } from 'vs/base/common/uuid';
import { Snippet, SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile';
suite('SnippetRewrite', function () {
function assertRewrite(input: string, expected: string | boolean): void {
- const actual = new Snippet(['foo'], 'foo', 'foo', 'foo', input, 'foo', SnippetSource.User);
+ const actual = new Snippet(false, ['foo'], 'foo', 'foo', 'foo', input, 'foo', SnippetSource.User, generateUuid());
if (typeof expected === 'boolean') {
assert.strictEqual(actual.codeSnippet, input);
} else {
@@ -47,7 +48,7 @@ suite('SnippetRewrite', function () {
});
test('lazy bogous variable rewrite', function () {
- const snippet = new Snippet(['fooLang'], 'foo', 'prefix', 'desc', 'This is ${bogous} because it is a ${var}', 'source', SnippetSource.Extension);
+ const snippet = new Snippet(false, ['fooLang'], 'foo', 'prefix', 'desc', 'This is ${bogous} because it is a ${var}', 'source', SnippetSource.Extension, generateUuid());
assert.strictEqual(snippet.body, 'This is ${bogous} because it is a ${var}');
assert.strictEqual(snippet.codeSnippet, 'This is ${1:bogous} because it is a ${2:var}');
assert.strictEqual(snippet.isBogous, true);
diff --git a/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts
index 6c442beb9f2..a414f31246d 100644
--- a/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts
+++ b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts
@@ -7,7 +7,7 @@ import * as assert from 'assert';
import { SnippetCompletion, SnippetCompletionProvider } from 'vs/workbench/contrib/snippets/browser/snippetCompletionProvider';
import { Position } from 'vs/editor/common/core/position';
import { createModelServices, instantiateTextModel } from 'vs/editor/test/common/testTextModel';
-import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets.contribution';
+import { ISnippetsService } from "vs/workbench/contrib/snippets/browser/snippets";
import { Snippet, SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile';
import { CompletionContext, CompletionItemLabel, CompletionItemRanges, CompletionTriggerKind } from 'vs/editor/common/languages';
import { DisposableStore } from 'vs/base/common/lifecycle';
@@ -15,6 +15,7 @@ import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/te
import { EditOperation } from 'vs/editor/common/core/editOperation';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { ILanguageService } from 'vs/editor/common/languages/language';
+import { generateUuid } from 'vs/base/common/uuid';
class SimpleSnippetService implements ISnippetsService {
declare readonly _serviceBrand: undefined;
@@ -34,6 +35,9 @@ class SimpleSnippetService implements ISnippetsService {
updateEnablement(): void {
throw new Error();
}
+ updateUsageTimestamp(snippet: Snippet): void {
+ throw new Error();
+ }
}
suite('SnippetsService', function () {
@@ -53,21 +57,25 @@ suite('SnippetsService', function () {
extensions: ['.fooLang',]
}));
snippetService = new SimpleSnippetService([new Snippet(
+ false,
['fooLang'],
'barTest',
'bar',
'',
'barCodeSnippet',
'',
- SnippetSource.User
+ SnippetSource.User,
+ generateUuid()
), new Snippet(
+ false,
['fooLang'],
'bazzTest',
'bazz',
'',
'bazzCodeSnippet',
'',
- SnippetSource.User
+ SnippetSource.User,
+ generateUuid()
)]);
});
@@ -120,23 +128,26 @@ suite('SnippetsService', function () {
});
test('snippet completions - with different prefixes', async function () {
-
snippetService = new SimpleSnippetService([new Snippet(
+ false,
['fooLang'],
'barTest',
'bar',
'',
's1',
'',
- SnippetSource.User
+ SnippetSource.User,
+ generateUuid()
), new Snippet(
+ false,
['fooLang'],
'name',
'bar-bar',
'',
's2',
'',
- SnippetSource.User
+ SnippetSource.User,
+ generateUuid()
)]);
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
@@ -200,13 +211,15 @@ suite('SnippetsService', function () {
test('Cannot use "<?php" as user snippet prefix anymore, #26275', function () {
snippetService = new SimpleSnippetService([new Snippet(
+ false,
['fooLang'],
'',
'<?php',
'',
'insert me',
'',
- SnippetSource.User
+ SnippetSource.User,
+ generateUuid()
)]);
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
@@ -235,13 +248,15 @@ suite('SnippetsService', function () {
test('No user snippets in suggestions, when inside the code, #30508', function () {
snippetService = new SimpleSnippetService([new Snippet(
+ false,
['fooLang'],
'',
'foo',
'',
'<foo>$0</foo>',
'',
- SnippetSource.User
+ SnippetSource.User,
+ generateUuid()
)]);
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
@@ -257,21 +272,25 @@ suite('SnippetsService', function () {
test('SnippetSuggest - ensure extension snippets come last ', function () {
snippetService = new SimpleSnippetService([new Snippet(
+ false,
['fooLang'],
'second',
'second',
'',
'second',
'',
- SnippetSource.Extension
+ SnippetSource.Extension,
+ generateUuid()
), new Snippet(
+ false,
['fooLang'],
'first',
'first',
'',
'first',
'',
- SnippetSource.User
+ SnippetSource.User,
+ generateUuid()
)]);
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
@@ -293,13 +312,15 @@ suite('SnippetsService', function () {
test('Dash in snippets prefix broken #53945', async function () {
snippetService = new SimpleSnippetService([new Snippet(
+ false,
['fooLang'],
'p-a',
'p-a',
'',
'second',
'',
- SnippetSource.User
+ SnippetSource.User,
+ generateUuid()
)]);
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
@@ -317,13 +338,15 @@ suite('SnippetsService', function () {
test('No snippets suggestion on long lines beyond character 100 #58807', async function () {
snippetService = new SimpleSnippetService([new Snippet(
+ false,
['fooLang'],
'bug',
'bug',
'',
'second',
'',
- SnippetSource.User
+ SnippetSource.User,
+ generateUuid()
)]);
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
@@ -336,13 +359,15 @@ suite('SnippetsService', function () {
test('Type colon will trigger snippet #60746', async function () {
snippetService = new SimpleSnippetService([new Snippet(
+ false,
['fooLang'],
'bug',
'bug',
'',
'second',
'',
- SnippetSource.User
+ SnippetSource.User,
+ generateUuid()
)]);
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
@@ -355,13 +380,15 @@ suite('SnippetsService', function () {
test('substring of prefix can\'t trigger snippet #60737', async function () {
snippetService = new SimpleSnippetService([new Snippet(
+ false,
['fooLang'],
'mytemplate',
'mytemplate',
'',
'second',
'',
- SnippetSource.User
+ SnippetSource.User,
+ generateUuid()
)]);
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
@@ -378,13 +405,15 @@ suite('SnippetsService', function () {
test('No snippets suggestion beyond character 100 if not at end of line #60247', async function () {
snippetService = new SimpleSnippetService([new Snippet(
+ false,
['fooLang'],
'bug',
'bug',
'',
'second',
'',
- SnippetSource.User
+ SnippetSource.User,
+ generateUuid()
)]);
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
@@ -402,13 +431,15 @@ suite('SnippetsService', function () {
}));
snippetService = new SimpleSnippetService([new Snippet(
+ false,
['fooLang'],
'bug',
'-a-bug',
'',
'second',
'',
- SnippetSource.User
+ SnippetSource.User,
+ generateUuid()
)]);
const provider = new SnippetCompletionProvider(languageService, snippetService, languageConfigurationService);
@@ -421,13 +452,15 @@ suite('SnippetsService', function () {
test('No snippets shown when triggering completions at whitespace on line that already has text #62335', async function () {
snippetService = new SimpleSnippetService([new Snippet(
+ false,
['fooLang'],
'bug',
'bug',
'',
'second',
'',
- SnippetSource.User
+ SnippetSource.User,
+ generateUuid()
)]);
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
@@ -440,21 +473,25 @@ suite('SnippetsService', function () {
test('Snippet prefix with special chars and numbers does not work #62906', async function () {
snippetService = new SimpleSnippetService([new Snippet(
+ false,
['fooLang'],
'noblockwdelay',
'<<',
'',
'<= #dly"',
'',
- SnippetSource.User
+ SnippetSource.User,
+ generateUuid()
), new Snippet(
+ false,
['fooLang'],
'noblockwdelay',
'11',
'',
'eleven',
'',
- SnippetSource.User
+ SnippetSource.User,
+ generateUuid()
)]);
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
@@ -478,13 +515,15 @@ suite('SnippetsService', function () {
test('Snippet replace range', async function () {
snippetService = new SimpleSnippetService([new Snippet(
+ false,
['fooLang'],
'notWordTest',
'not word',
'',
'not word snippet',
'',
- SnippetSource.User
+ SnippetSource.User,
+ generateUuid()
)]);
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
@@ -520,13 +559,15 @@ suite('SnippetsService', function () {
test('Snippet replace-range incorrect #108894', async function () {
snippetService = new SimpleSnippetService([new Snippet(
+ false,
['fooLang'],
'eng',
'eng',
'',
'<span></span>',
'',
- SnippetSource.User
+ SnippetSource.User,
+ generateUuid()
)]);
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
@@ -552,13 +593,15 @@ suite('SnippetsService', function () {
}));
snippetService = new SimpleSnippetService([new Snippet(
+ false,
['fooLang'],
'PSCustomObject',
'[PSCustomObject]',
'',
'[PSCustomObject] @{ Key = Value }',
'',
- SnippetSource.User
+ SnippetSource.User,
+ generateUuid()
)]);
const provider = new SnippetCompletionProvider(languageService, snippetService, languageConfigurationService);
@@ -577,13 +620,15 @@ suite('SnippetsService', function () {
test('Leading whitespace in snippet prefix #123860', async function () {
snippetService = new SimpleSnippetService([new Snippet(
+ false,
['fooLang'],
'cite-name',
' cite',
'',
'~\\cite{$CLIPBOARD}',
'',
- SnippetSource.User
+ SnippetSource.User,
+ generateUuid()
)]);
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
@@ -602,9 +647,9 @@ suite('SnippetsService', function () {
test('still show suggestions in string when disable string suggestion #136611', async function () {
snippetService = new SimpleSnippetService([
- new Snippet(['fooLang'], 'aaa', 'aaa', '', 'value', '', SnippetSource.User),
- new Snippet(['fooLang'], 'bbb', 'bbb', '', 'value', '', SnippetSource.User),
- // new Snippet(['fooLang'], '\'ccc', '\'ccc', '', 'value', '', SnippetSource.User)
+ new Snippet(false, ['fooLang'], 'aaa', 'aaa', '', 'value', '', SnippetSource.User, generateUuid()),
+ new Snippet(false, ['fooLang'], 'bbb', 'bbb', '', 'value', '', SnippetSource.User, generateUuid()),
+ // new Snippet(['fooLang'], '\'ccc', '\'ccc', '', 'value', '', SnippetSource.User, generateUuid())
]);
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
@@ -624,9 +669,9 @@ suite('SnippetsService', function () {
test('still show suggestions in string when disable string suggestion #136611', async function () {
snippetService = new SimpleSnippetService([
- new Snippet(['fooLang'], 'aaa', 'aaa', '', 'value', '', SnippetSource.User),
- new Snippet(['fooLang'], 'bbb', 'bbb', '', 'value', '', SnippetSource.User),
- new Snippet(['fooLang'], '\'ccc', '\'ccc', '', 'value', '', SnippetSource.User)
+ new Snippet(false, ['fooLang'], 'aaa', 'aaa', '', 'value', '', SnippetSource.User, generateUuid()),
+ new Snippet(false, ['fooLang'], 'bbb', 'bbb', '', 'value', '', SnippetSource.User, generateUuid()),
+ new Snippet(false, ['fooLang'], '\'ccc', '\'ccc', '', 'value', '', SnippetSource.User, generateUuid())
]);
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
@@ -645,9 +690,9 @@ suite('SnippetsService', function () {
test('Snippet suggestions are too eager #138707 (word)', async function () {
snippetService = new SimpleSnippetService([
- new Snippet(['fooLang'], 'tys', 'tys', '', 'value', '', SnippetSource.User),
- new Snippet(['fooLang'], 'hell_or_tell', 'hell_or_tell', '', 'value', '', SnippetSource.User),
- new Snippet(['fooLang'], '^y', '^y', '', 'value', '', SnippetSource.User),
+ new Snippet(false, ['fooLang'], 'tys', 'tys', '', 'value', '', SnippetSource.User, generateUuid()),
+ new Snippet(false, ['fooLang'], 'hell_or_tell', 'hell_or_tell', '', 'value', '', SnippetSource.User, generateUuid()),
+ new Snippet(false, ['fooLang'], '^y', '^y', '', 'value', '', SnippetSource.User, generateUuid()),
]);
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
@@ -666,9 +711,9 @@ suite('SnippetsService', function () {
test('Snippet suggestions are too eager #138707 (no word)', async function () {
snippetService = new SimpleSnippetService([
- new Snippet(['fooLang'], 'tys', 'tys', '', 'value', '', SnippetSource.User),
- new Snippet(['fooLang'], 't', 't', '', 'value', '', SnippetSource.User),
- new Snippet(['fooLang'], '^y', '^y', '', 'value', '', SnippetSource.User),
+ new Snippet(false, ['fooLang'], 'tys', 'tys', '', 'value', '', SnippetSource.User, generateUuid()),
+ new Snippet(false, ['fooLang'], 't', 't', '', 'value', '', SnippetSource.User, generateUuid()),
+ new Snippet(false, ['fooLang'], '^y', '^y', '', 'value', '', SnippetSource.User, generateUuid()),
]);
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
@@ -687,8 +732,8 @@ suite('SnippetsService', function () {
test('Snippet suggestions are too eager #138707 (word/word)', async function () {
snippetService = new SimpleSnippetService([
- new Snippet(['fooLang'], 'async arrow function', 'async arrow function', '', 'value', '', SnippetSource.User),
- new Snippet(['fooLang'], 'foobarrrrrr', 'foobarrrrrr', '', 'value', '', SnippetSource.User),
+ new Snippet(false, ['fooLang'], 'async arrow function', 'async arrow function', '', 'value', '', SnippetSource.User, generateUuid()),
+ new Snippet(false, ['fooLang'], 'foobarrrrrr', 'foobarrrrrr', '', 'value', '', SnippetSource.User, generateUuid()),
]);
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
@@ -707,7 +752,7 @@ suite('SnippetsService', function () {
test('Strange and useless autosuggestion #region/#endregion PHP #140039', async function () {
snippetService = new SimpleSnippetService([
- new Snippet(['fooLang'], 'reg', '#region', '', 'value', '', SnippetSource.User),
+ new Snippet(false, ['fooLang'], 'reg', '#region', '', 'value', '', SnippetSource.User, generateUuid()),
]);
@@ -725,9 +770,9 @@ suite('SnippetsService', function () {
test.skip('Snippets disappear with . key #145960', async function () {
snippetService = new SimpleSnippetService([
- new Snippet(['fooLang'], 'div', 'div', '', 'div', '', SnippetSource.User),
- new Snippet(['fooLang'], 'div.', 'div.', '', 'div.', '', SnippetSource.User),
- new Snippet(['fooLang'], 'div#', 'div#', '', 'div#', '', SnippetSource.User),
+ new Snippet(false, ['fooLang'], 'div', 'div', '', 'div', '', SnippetSource.User, generateUuid()),
+ new Snippet(false, ['fooLang'], 'div.', 'div.', '', 'div.', '', SnippetSource.User, generateUuid()),
+ new Snippet(false, ['fooLang'], 'div#', 'div#', '', 'div#', '', SnippetSource.User, generateUuid()),
]);
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
diff --git a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTags.ts b/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTags.ts
index ab86a14c069..48bb97c05e7 100644
--- a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTags.ts
+++ b/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTags.ts
@@ -67,7 +67,7 @@ export class WorkspaceTags implements IWorkbenchContribution {
value = 'Unknown';
}
- this.telemetryService.publicLog2<{ edition: string }, { owner: 'sbatten'; edition: { classification: 'SystemMetaData'; purpose: 'BusinessInsight' } }>('windowsEdition', { edition: value });
+ this.telemetryService.publicLog2<{ edition: string }, { owner: 'sbatten'; comment: 'Information about the Windows edition.'; edition: { classification: 'SystemMetaData'; purpose: 'BusinessInsight'; comment: 'The Windows edition.' } }>('windowsEdition', { edition: value });
}
private async getWorkspaceInformation(): Promise<IWorkspaceInformation> {
diff --git a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts b/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts
index dd0ef223db9..30d07dcc5b3 100644
--- a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts
+++ b/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts
@@ -305,6 +305,7 @@ export class WorkspaceTagsService implements IWorkspaceTagsService {
"WorkspaceTags" : {
"workbench.filesToOpenOrCreate" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workbench.filesToDiff" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
+ "workbench.filesToMerge" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"workspace.roots" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
"workspace.empty" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
@@ -572,9 +573,10 @@ export class WorkspaceTagsService implements IWorkspaceTagsService {
tags['workspace.id'] = await this.getTelemetryWorkspaceId(workspace, state);
- const { filesToOpenOrCreate, filesToDiff } = this.environmentService;
+ const { filesToOpenOrCreate, filesToDiff, filesToMerge } = this.environmentService;
tags['workbench.filesToOpenOrCreate'] = filesToOpenOrCreate && filesToOpenOrCreate.length || 0;
tags['workbench.filesToDiff'] = filesToDiff && filesToDiff.length || 0;
+ tags['workbench.filesToMerge'] = filesToMerge && filesToMerge.length || 0;
const isEmpty = state === WorkbenchState.EMPTY;
tags['workspace.roots'] = isEmpty ? 0 : workspace.folders.length;
@@ -813,11 +815,13 @@ export class WorkspaceTagsService implements IWorkspaceTagsService {
}
private findFolder(): URI | undefined {
- const { filesToOpenOrCreate, filesToDiff } = this.environmentService;
+ const { filesToOpenOrCreate, filesToDiff, filesToMerge } = this.environmentService;
if (filesToOpenOrCreate && filesToOpenOrCreate.length) {
return this.parentURI(filesToOpenOrCreate[0].fileUri);
} else if (filesToDiff && filesToDiff.length) {
return this.parentURI(filesToDiff[0].fileUri);
+ } else if (filesToMerge && filesToMerge.length === 4) {
+ return this.parentURI(filesToMerge[3].fileUri) /* [3] is the resulting merge file */;
}
return undefined;
}
diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts
index 93cbe879116..c123a941880 100644
--- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts
+++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts
@@ -57,7 +57,7 @@ import {
TaskSettingId,
TasksSchemaProperties
} from 'vs/workbench/contrib/tasks/common/tasks';
-import { ITaskService, ITaskProvider, IProblemMatcherRunOptions, ICustomizationProperties, ITaskFilter, IWorkspaceFolderTaskResult, CustomExecutionSupportedContext, ShellExecutionSupportedContext, ProcessExecutionSupportedContext } from 'vs/workbench/contrib/tasks/common/taskService';
+import { ITaskService, ITaskProvider, IProblemMatcherRunOptions, ICustomizationProperties, ITaskFilter, IWorkspaceFolderTaskResult, CustomExecutionSupportedContext, ShellExecutionSupportedContext, ProcessExecutionSupportedContext, TaskCommandsRegistered } from 'vs/workbench/contrib/tasks/common/taskService';
import { getTemplates as getTaskTemplates } from 'vs/workbench/contrib/tasks/common/taskTemplates';
import * as TaskConfig from '../common/taskConfiguration';
@@ -78,7 +78,7 @@ import { ITextEditorSelection, TextEditorSelectionRevealType } from 'vs/platform
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { IViewsService, IViewDescriptorService } from 'vs/workbench/common/views';
-import { isWorkspaceFolder, ITaskQuickPickEntry, QUICKOPEN_DETAIL_CONFIG, TaskQuickPick, QUICKOPEN_SKIP_CONFIG, configureTaskIcon } from 'vs/workbench/contrib/tasks/browser/taskQuickPick';
+import { isWorkspaceFolder, ITaskQuickPickEntry, QUICKOPEN_DETAIL_CONFIG, TaskQuickPick, QUICKOPEN_SKIP_CONFIG, configureTaskIcon, ITaskTwoLevelQuickPickEntry } from 'vs/workbench/contrib/tasks/browser/taskQuickPick';
import { ILogService } from 'vs/platform/log/common/log';
import { once } from 'vs/base/common/functional';
import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
@@ -200,6 +200,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
private static _nextHandle: number = 0;
+ private _tasksReconnected: boolean = false;
private _schemaVersion: JsonSchemaVersion | undefined;
private _executionEngine: ExecutionEngine | undefined;
private _workspaceFolders: IWorkspaceFolder[] | undefined;
@@ -292,7 +293,9 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
}));
this._taskRunningState = TASK_RUNNING_STATE.bindTo(_contextKeyService);
this._onDidStateChange = this._register(new Emitter());
- this._registerCommands();
+ this._registerCommands().then(() => {
+ TaskCommandsRegistered.bindTo(this._contextKeyService).set(true);
+ });
this._configurationResolverService.contributeVariable('defaultBuildTask', async (): Promise<string | undefined> => {
let tasks = await this._getTasksForGroup(TaskGroup.Build);
if (tasks.length > 0) {
@@ -337,6 +340,27 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
processContext.set(process && !isVirtual);
}
this._onDidRegisterSupportedExecutions.fire();
+ if (this._jsonTasksSupported && !this._tasksReconnected) {
+ this._reconnectTasks();
+ }
+ }
+
+ private async _reconnectTasks(): Promise<void> {
+ const recentlyUsedTasks = await this.readRecentTasks();
+ if (!recentlyUsedTasks.length) {
+ return;
+ }
+ for (const task of recentlyUsedTasks) {
+ if (ConfiguringTask.is(task)) {
+ const resolved = await this.tryResolveTask(task);
+ if (resolved) {
+ this.run(resolved, undefined, TaskRunSource.Reconnect);
+ }
+ } else {
+ this.run(task, undefined, TaskRunSource.Reconnect);
+ }
+ }
+ this._tasksReconnected = true;
}
public get onDidStateChange(): Event<ITaskEvent> {
@@ -347,7 +371,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
return this.inTerminal();
}
- private _registerCommands(): void {
+ private async _registerCommands(): Promise<void> {
CommandsRegistry.registerCommand({
id: 'workbench.action.tasks.runTask',
handler: async (accessor, arg) => {
@@ -359,8 +383,30 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
description: 'Run Task',
args: [{
name: 'args',
+ isOptional: true,
+ description: nls.localize('runTask.arg', "Filters the tasks shown in the quickpick"),
schema: {
- 'type': 'string',
+ anyOf: [
+ {
+ type: 'string',
+ description: nls.localize('runTask.label', "The task's label or a term to filter by")
+ },
+ {
+ type: 'object',
+ properties: {
+ type: {
+ type: 'string',
+ description: nls.localize('runTask.type', "The contributed task type"),
+ enum: Array.from(this._providerTypes.values()).map(provider => provider)
+ },
+ taskName: {
+ type: 'string',
+ description: nls.localize('runTask.taskName', "The task's label or a term to filter by"),
+ enum: await this.tasks().then((tasks) => tasks.map(t => t._label))
+ }
+ }
+ }
+ ]
}
}]
}
@@ -383,7 +429,6 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
this._runTerminateCommand(arg);
}
});
-
CommandsRegistry.registerCommand('workbench.action.tasks.showLog', () => {
if (!this._canRunCommand()) {
return;
@@ -639,7 +684,6 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
}
}
}
-
return result;
}
@@ -895,7 +939,6 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
map.get(folder).push(task);
}
}
-
for (const entry of recentlyUsedTasks.entries()) {
const key = entry[0];
const task = JSON.parse(entry[1]);
@@ -904,6 +947,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
}
const readTasksMap: Map<string, (Task | ConfiguringTask)> = new Map();
+
async function readTasks(that: AbstractTaskService, map: Map<string, any>, isWorkspaceFile: boolean) {
for (const key of map.keys()) {
const custom: CustomTask[] = [];
@@ -932,7 +976,6 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
}
await readTasks(this, folderToTasksMap, false);
await readTasks(this, workspaceToTaskMap, true);
-
for (const key of recentlyUsedTasks.keys()) {
if (readTasksMap.has(key)) {
tasks.push(readTasksMap.get(key)!);
@@ -1727,8 +1770,11 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
? await this.getTask(taskFolder, taskIdentifier) : task) ?? task;
}
await ProblemMatcherRegistry.onReady();
- const executeResult = this._getTaskSystem().run(taskToRun, resolver);
- return this._handleExecuteResult(executeResult, runSource);
+ const executeResult = runSource === TaskRunSource.Reconnect ? this._getTaskSystem().reconnect(taskToRun, resolver) : this._getTaskSystem().run(taskToRun, resolver);
+ if (executeResult) {
+ return this._handleExecuteResult(executeResult, runSource);
+ }
+ return { exitCode: 0 };
}
private async _handleExecuteResult(executeResult: ITaskExecuteResult, runSource?: TaskRunSource): Promise<ITaskSummary> {
@@ -1759,6 +1805,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
throw new TaskError(Severity.Warning, nls.localize('TaskSystem.active', 'There is already a task running. Terminate it first before executing another task.'), TaskErrors.RunningTask);
}
}
+ this._setRecentlyUsedTask(executeResult.task);
return executeResult.promise;
}
@@ -2127,7 +2174,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
}
private get _jsonTasksSupported(): boolean {
- return !!ShellExecutionSupportedContext.getValue(this._contextKeyService) && !!ProcessExecutionSupportedContext.getValue(this._contextKeyService);
+ return ShellExecutionSupportedContext.getValue(this._contextKeyService) === true && ProcessExecutionSupportedContext.getValue(this._contextKeyService) === true;
}
private _computeWorkspaceFolderTasks(workspaceFolder: IWorkspaceFolder, runSource: TaskRunSource = TaskRunSource.User): Promise<IWorkspaceFolderTaskResult> {
@@ -2521,11 +2568,11 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
return entries;
}
- private async _showTwoLevelQuickPick(placeHolder: string, defaultEntry?: ITaskQuickPickEntry) {
- return TaskQuickPick.show(this, this._configurationService, this._quickInputService, this._notificationService, this._dialogService, this._themeService, placeHolder, defaultEntry);
+ private async _showTwoLevelQuickPick(placeHolder: string, defaultEntry?: ITaskQuickPickEntry, filter?: string) {
+ return TaskQuickPick.show(this, this._configurationService, this._quickInputService, this._notificationService, this._dialogService, this._themeService, placeHolder, defaultEntry, filter);
}
- private async _showQuickPick(tasks: Promise<Task[]> | Task[], placeHolder: string, defaultEntry?: ITaskQuickPickEntry, group: boolean = false, sort: boolean = false, selectedEntry?: ITaskQuickPickEntry, additionalEntries?: ITaskQuickPickEntry[]): Promise<ITaskQuickPickEntry | undefined | null> {
+ private async _showQuickPick(tasks: Promise<Task[]> | Task[], placeHolder: string, defaultEntry?: ITaskQuickPickEntry, group: boolean = false, sort: boolean = false, selectedEntry?: ITaskQuickPickEntry, additionalEntries?: ITaskQuickPickEntry[], filter?: string): Promise<ITaskQuickPickEntry | undefined | null> {
const tokenSource = new CancellationTokenSource();
const cancellationToken: CancellationToken = tokenSource.token;
const createEntries = new Promise<QuickPickInput<ITaskQuickPickEntry>[]>((resolve) => {
@@ -2564,7 +2611,6 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
const picker: IQuickPick<ITaskQuickPickEntry> = this._quickInputService.createQuickPick();
picker.placeholder = placeHolder;
picker.matchOnDescription = true;
-
picker.onDidTriggerItemButton(context => {
const task = context.item.task;
this._quickInputService.cancel();
@@ -2580,7 +2626,9 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
picker.items = entries;
});
picker.show();
-
+ if (filter) {
+ picker.value = filter;
+ }
return new Promise<ITaskQuickPickEntry | undefined | null>(resolve => {
this._register(picker.onDidAccept(async () => {
let selection = picker.selectedItems ? picker.selectedItems[0] : undefined;
@@ -2654,12 +2702,20 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
})) === true;
}
- private _runTaskCommand(arg?: any): void {
+ private async _runTaskCommand(filter?: { type?: string; taskName?: string } | string): Promise<void> {
if (!this._canRunCommand()) {
return;
}
- const identifier = this._getTaskIdentifier(arg);
- if (identifier !== undefined) {
+
+ let typeFilter: boolean = false;
+ if (filter && typeof filter !== 'string') {
+ // name takes precedence
+ typeFilter = !filter?.taskName && !!filter?.type;
+ filter = filter?.taskName || filter?.type;
+ }
+
+ const taskIdentifier: KeyedTaskIdentifier | undefined | string = this._getTaskIdentifier(filter);
+ if (taskIdentifier) {
this._getGroupedTasks().then(async (grouped) => {
const resolver = this._createResolver(grouped);
const folderURIs: (URI | string)[] = this._contextService.getWorkspace().folders.map(folder => folder.uri);
@@ -2668,7 +2724,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
}
folderURIs.push(USER_TASKS_GROUP_KEY);
for (const uri of folderURIs) {
- const task = await resolver.resolve(uri, identifier);
+ const task = await resolver.resolve(uri, taskIdentifier);
if (task) {
this.run(task).then(undefined, reason => {
// eat the error, it has already been surfaced to the user and we don't care about it here
@@ -2676,7 +2732,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
return;
}
}
- this._doRunTaskCommand(grouped.all());
+ this._doRunTaskCommand(grouped.all(), typeof taskIdentifier === 'string' ? taskIdentifier : undefined, typeFilter);
}, () => {
this._doRunTaskCommand();
});
@@ -2716,7 +2772,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
return { tasks, grouped };
}
- private _doRunTaskCommand(tasks?: Task[]): void {
+ private _doRunTaskCommand(tasks?: Task[], filter?: string, typeFilter?: boolean): void {
const pickThen = (task: Task | undefined | null) => {
if (task === undefined) {
return;
@@ -2732,28 +2788,58 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
const placeholder = nls.localize('TaskService.pickRunTask', 'Select the task to run');
- this._showIgnoredFoldersMessage().then(() => {
+ this._showIgnoredFoldersMessage().then(async () => {
if (this._configurationService.getValue(USE_SLOW_PICKER)) {
let taskResult: { tasks: Promise<Task[]>; grouped: Promise<TaskMap> } | undefined = undefined;
if (!tasks) {
taskResult = this._tasksAndGroupedTasks();
}
+ if (filter && typeFilter) {
+ const picker: IQuickPick<ITaskTwoLevelQuickPickEntry> = this._quickInputService.createQuickPick();
+ picker.placeholder = nls.localize('TaskService.pickRunTask', 'Select the task to run');
+ picker.matchOnDescription = true;
+ picker.ignoreFocusOut = false;
+ const taskQuickPick = new TaskQuickPick(this, this._configurationService, this._quickInputService, this._notificationService, this._themeService, this._dialogService);
+ const result = await taskQuickPick.doPickerSecondLevel(picker, filter);
+ if (result?.task) {
+ pickThen(result.task as Task);
+ taskQuickPick.dispose();
+ }
+ return;
+ }
this._showQuickPick(tasks ? tasks : taskResult!.tasks, placeholder,
{
label: '$(plus) ' + nls.localize('TaskService.noEntryToRun', 'Configure a Task'),
task: null
},
- true).
+ true, false, undefined, undefined, typeof filter === 'string' ? filter : undefined).
then((entry) => {
return pickThen(entry ? entry.task : undefined);
});
} else {
- this._showTwoLevelQuickPick(placeholder,
- {
- label: '$(plus) ' + nls.localize('TaskService.noEntryToRun', 'Configure a Task'),
- task: null
- }).
- then(pickThen);
+ if (filter && typeFilter) {
+ const picker: IQuickPick<ITaskTwoLevelQuickPickEntry> = this._quickInputService.createQuickPick();
+ picker.placeholder = nls.localize('TaskService.pickRunTask', 'Select the task to run');
+ picker.matchOnDescription = true;
+ picker.ignoreFocusOut = false;
+ const taskQuickPick = new TaskQuickPick(this, this._configurationService, this._quickInputService, this._notificationService, this._themeService, this._dialogService);
+ const result = await taskQuickPick.doPickerSecondLevel(picker, filter);
+ if (result?.task) {
+ pickThen(result.task as Task);
+ picker.dispose();
+ taskQuickPick.dispose();
+ return;
+ } else {
+ return;
+ }
+ } else {
+ this._showTwoLevelQuickPick(placeholder,
+ {
+ label: '$(plus) ' + nls.localize('TaskService.noEntryToRun', 'Configure a Task'),
+ task: null
+ }, typeof filter === 'string' ? filter : undefined).
+ then(pickThen);
+ }
}
});
}
@@ -3055,7 +3141,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
}
}
- private _getTaskIdentifier(arg?: any): string | KeyedTaskIdentifier | undefined {
+ private _getTaskIdentifier(arg?: string | ITaskIdentifier): string | KeyedTaskIdentifier | undefined {
let result: string | KeyedTaskIdentifier | undefined = undefined;
if (Types.isString(arg)) {
result = arg;
diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts
index 00c85294b7b..9b5df9014e5 100644
--- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts
+++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts
@@ -21,7 +21,7 @@ import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatus
import { IOutputChannelRegistry, Extensions as OutputExt } from 'vs/workbench/services/output/common/output';
import { ITaskEvent, TaskEventKind, TaskGroup, TaskSettingId, TASKS_CATEGORY, TASK_RUNNING_STATE } from 'vs/workbench/contrib/tasks/common/tasks';
-import { ITaskService, ProcessExecutionSupportedContext, ShellExecutionSupportedContext } from 'vs/workbench/contrib/tasks/common/taskService';
+import { ITaskService, ProcessExecutionSupportedContext, ShellExecutionSupportedContext, TaskCommandsRegistered } from 'vs/workbench/contrib/tasks/common/taskService';
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { RunAutomaticTasks, ManageAutomaticTaskRunning } from 'vs/workbench/contrib/tasks/browser/runAutomaticTasks';
@@ -40,7 +40,7 @@ import { TaskDefinitionRegistry } from 'vs/workbench/contrib/tasks/common/taskDe
import { TerminalMenuBarGroup } from 'vs/workbench/contrib/terminal/browser/terminalMenus';
import { isString } from 'vs/base/common/types';
-const SHOW_TASKS_COMMANDS_CONTEXT = ContextKeyExpr.or(ShellExecutionSupportedContext, ProcessExecutionSupportedContext);
+const SHOW_TASKS_COMMANDS_CONTEXT = ContextKeyExpr.and(ShellExecutionSupportedContext, ProcessExecutionSupportedContext);
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
workbenchRegistry.registerWorkbenchContribution(RunAutomaticTasks, LifecyclePhase.Eventually);
@@ -359,7 +359,7 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
KeybindingsRegistry.registerKeybindingRule({
id: 'workbench.action.tasks.build',
weight: KeybindingWeight.WorkbenchContrib,
- when: undefined,
+ when: TaskCommandsRegistered,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyB
});
diff --git a/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts b/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts
index a2cc123e381..bf48327fe5d 100644
--- a/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts
+++ b/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts
@@ -218,12 +218,15 @@ export class TaskQuickPick extends Disposable {
return undefined;
}
- public async show(placeHolder: string, defaultEntry?: ITaskQuickPickEntry, startAtType?: string): Promise<Task | undefined | null> {
+ public async show(placeHolder: string, defaultEntry?: ITaskQuickPickEntry, startAtType?: string, filter?: string): Promise<Task | undefined | null> {
const picker: IQuickPick<ITaskTwoLevelQuickPickEntry> = this._quickInputService.createQuickPick();
picker.placeholder = placeHolder;
picker.matchOnDescription = true;
picker.ignoreFocusOut = false;
picker.show();
+ if (filter) {
+ picker.value = filter;
+ }
picker.onDidTriggerItemButton(async (context) => {
const task = context.item.task;
@@ -268,7 +271,7 @@ export class TaskQuickPick extends Disposable {
do {
if (Types.isString(firstLevelTask)) {
// Proceed to second level of quick pick
- const selectedEntry = await this._doPickerSecondLevel(picker, firstLevelTask);
+ const selectedEntry = await this.doPickerSecondLevel(picker, firstLevelTask);
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);
@@ -302,7 +305,7 @@ export class TaskQuickPick extends Disposable {
return firstLevelPickerResult?.task;
}
- private async _doPickerSecondLevel(picker: IQuickPick<ITaskTwoLevelQuickPickEntry>, type: string) {
+ public async doPickerSecondLevel(picker: IQuickPick<ITaskTwoLevelQuickPickEntry>, type: string) {
picker.busy = true;
if (type === SHOW_ALL) {
const items = (await this._taskService.tasks()).filter(t => !t.configurationProperties.hide).sort((a, b) => this._sorter.compare(a, b)).map(task => this._createTaskEntry(task));
@@ -312,13 +315,13 @@ export class TaskQuickPick extends Disposable {
picker.value = '';
picker.items = await this._getEntriesForProvider(type);
}
+ picker.show();
picker.busy = false;
const secondLevelPickerResult = await new Promise<ITaskTwoLevelQuickPickEntry | undefined | null>(resolve => {
Event.once(picker.onDidAccept)(async () => {
resolve(picker.selectedItems ? picker.selectedItems[0] : undefined);
});
});
-
return secondLevelPickerResult;
}
@@ -398,8 +401,8 @@ export class TaskQuickPick extends Disposable {
static async show(taskService: ITaskService, configurationService: IConfigurationService,
quickInputService: IQuickInputService, notificationService: INotificationService,
- dialogService: IDialogService, themeService: IThemeService, placeHolder: string, defaultEntry?: ITaskQuickPickEntry) {
+ dialogService: IDialogService, themeService: IThemeService, placeHolder: string, defaultEntry?: ITaskQuickPickEntry, filter?: string) {
const taskQuickPick = new TaskQuickPick(taskService, configurationService, quickInputService, notificationService, themeService, dialogService);
- return taskQuickPick.show(placeHolder, defaultEntry);
+ return taskQuickPick.show(placeHolder, defaultEntry, undefined, filter);
}
}
diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts
index 7cfab363b6d..22c53792466 100644
--- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts
+++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts
@@ -3,58 +3,52 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import * as path from 'vs/base/common/path';
-import * as nls from 'vs/nls';
-import * as Objects from 'vs/base/common/objects';
-import * as Types from 'vs/base/common/types';
-import * as Platform from 'vs/base/common/platform';
import * as Async from 'vs/base/common/async';
-import * as resources from 'vs/base/common/resources';
import { IStringDictionary } from 'vs/base/common/collections';
+import { Emitter, Event } from 'vs/base/common/event';
+import { isUNC } from 'vs/base/common/extpath';
+import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { LinkedMap, Touch } from 'vs/base/common/map';
+import * as Objects from 'vs/base/common/objects';
+import * as path from 'vs/base/common/path';
+import * as Platform from 'vs/base/common/platform';
+import * as resources from 'vs/base/common/resources';
import Severity from 'vs/base/common/severity';
-import { Event, Emitter } from 'vs/base/common/event';
-import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
-import { isUNC } from 'vs/base/common/extpath';
+import * as Types from 'vs/base/common/types';
+import * as nls from 'vs/nls';
+import { IModelService } from 'vs/editor/common/services/model';
import { IFileService } from 'vs/platform/files/common/files';
import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers';
-import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
-import { IModelService } from 'vs/editor/common/services/model';
-import { ProblemMatcher, ProblemMatcherRegistry /*, ProblemPattern, getResource */ } from 'vs/workbench/contrib/tasks/common/problemMatcher';
+import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { Markers } from 'vs/workbench/contrib/markers/common/markers';
+import { ProblemMatcher, ProblemMatcherRegistry /*, ProblemPattern, getResource */ } from 'vs/workbench/contrib/tasks/common/problemMatcher';
-import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
-import { ITerminalProfileResolverService, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal';
-import { ITerminalService, ITerminalInstance, ITerminalGroupService } from 'vs/workbench/contrib/terminal/browser/terminal';
-import { IOutputService } from 'vs/workbench/services/output/common/output';
-import { StartStopProblemCollector, WatchingProblemCollector, ProblemCollectorEventKind, ProblemHandlingStrategy } from 'vs/workbench/contrib/tasks/common/problemCollectors';
-import {
- Task, CustomTask, ContributedTask, RevealKind, CommandOptions, IShellConfiguration, RuntimeType, PanelKind,
- TaskEvent, TaskEventKind, IShellQuotingOptions, ShellQuoting, CommandString, ICommandConfiguration, IExtensionTaskSource, TaskScope, RevealProblemKind, DependsOrder, TaskSourceKind, InMemoryTask, ITaskEvent, TaskSettingId
-} from 'vs/workbench/contrib/tasks/common/tasks';
-import {
- ITaskSystem, ITaskSummary, ITaskExecuteResult, TaskExecuteKind, TaskError, TaskErrors, ITaskResolver,
- Triggers, ITaskTerminateResponse, ITaskSystemInfoResolver, ITaskSystemInfo, IResolveSet, IResolvedVariables
-} from 'vs/workbench/contrib/tasks/common/taskSystem';
-import { URI } from 'vs/base/common/uri';
-import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
+import { Codicon } from 'vs/base/common/codicons';
import { Schemas } from 'vs/base/common/network';
-import { IPathService } from 'vs/workbench/services/path/common/pathService';
-import { IViewsService, IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views';
-import { ILogService } from 'vs/platform/log/common/log';
+import { URI } from 'vs/base/common/uri';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
-import { IShellLaunchConfig, TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
-import { TerminalProcessExtHostProxy } from 'vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy';
-import { TaskTerminalStatus } from 'vs/workbench/contrib/tasks/browser/taskTerminalStatus';
-import { ITaskService } from 'vs/workbench/contrib/tasks/common/taskService';
-import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite';
+import { ILogService } from 'vs/platform/log/common/log';
import { INotificationService } from 'vs/platform/notification/common/notification';
-import { ThemeIcon } from 'vs/platform/theme/common/themeService';
+import { IShellLaunchConfig, TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
import { formatMessageForTerminal } from 'vs/platform/terminal/common/terminalStrings';
+import { ThemeIcon } from 'vs/platform/theme/common/themeService';
+import { IViewDescriptorService, IViewsService, ViewContainerLocation } from 'vs/workbench/common/views';
+import { TaskTerminalStatus } from 'vs/workbench/contrib/tasks/browser/taskTerminalStatus';
+import { ProblemCollectorEventKind, ProblemHandlingStrategy, StartStopProblemCollector, WatchingProblemCollector } from 'vs/workbench/contrib/tasks/common/problemCollectors';
import { GroupKind } from 'vs/workbench/contrib/tasks/common/taskConfiguration';
-import { Codicon } from 'vs/base/common/codicons';
+import { CommandOptions, CommandString, ContributedTask, CustomTask, DependsOrder, ICommandConfiguration, IExtensionTaskSource, InMemoryTask, IShellConfiguration, IShellQuotingOptions, ITaskEvent, PanelKind, RevealKind, RevealProblemKind, RuntimeType, ShellQuoting, Task, TaskEvent, TaskEventKind, TaskScope, TaskSettingId, TaskSourceKind } from 'vs/workbench/contrib/tasks/common/tasks';
+import { ITaskService } from 'vs/workbench/contrib/tasks/common/taskService';
+import { IResolvedVariables, IResolveSet, ITaskExecuteResult, ITaskResolver, ITaskSummary, ITaskSystem, ITaskSystemInfo, ITaskSystemInfoResolver, ITaskTerminateResponse, TaskError, TaskErrors, TaskExecuteKind, Triggers } from 'vs/workbench/contrib/tasks/common/taskSystem';
+import { ITerminalGroupService, ITerminalInstance, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { VSCodeOscProperty, VSCodeOscPt, VSCodeSequence } from 'vs/workbench/contrib/terminal/browser/terminalEscapeSequences';
+import { TerminalProcessExtHostProxy } from 'vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy';
+import { ITerminalProfileResolverService, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal';
+import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
+import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
+import { IOutputService } from 'vs/workbench/services/output/common/output';
+import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite';
+import { IPathService } from 'vs/workbench/services/path/common/pathService';
interface ITerminalData {
terminal: ITerminalInstance;
@@ -69,6 +63,8 @@ interface IActiveTerminalData {
state?: TaskEventKind;
}
+const ReconnectionType = 'Task';
+
class InstanceManager {
private _currentInstances: number = 0;
private _counter: number = 0;
@@ -196,6 +192,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
private _terminals: IStringDictionary<ITerminalData>;
private _idleTaskTerminals: LinkedMap<string, string>;
private _sameTaskTerminals: IStringDictionary<string>;
+ private _terminalForTask: ITerminalInstance | undefined;
private _taskSystemInfoResolver: ITaskSystemInfoResolver;
private _lastTask: VerifiedTask | undefined;
// Should always be set in run
@@ -205,7 +202,8 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
private _previousTerminalInstance: ITerminalInstance | undefined;
private _terminalStatusManager: TaskTerminalStatus;
private _terminalCreationQueue: Promise<ITerminalInstance | void> = Promise.resolve();
-
+ private _hasReconnected: boolean = false;
+ private _tasksToReconnect: string[] = [];
private readonly _onDidStateChange: Emitter<ITaskEvent>;
get taskShellIntegrationStartSequence(): string {
@@ -245,12 +243,24 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
this._terminals = Object.create(null);
this._idleTaskTerminals = new LinkedMap<string, string>();
this._sameTaskTerminals = Object.create(null);
-
this._onDidStateChange = new Emitter();
this._taskSystemInfoResolver = taskSystemInfoResolver;
this._register(this._terminalStatusManager = new TaskTerminalStatus(taskService));
}
+ private _reconnectToTerminals(terminals: ITerminalInstance[]): void {
+ for (const terminal of terminals) {
+ const taskForTerminal = terminal.shellLaunchConfig.attachPersistentProcess?.task;
+ if (taskForTerminal?.id && taskForTerminal?.lastTask) {
+ this._tasksToReconnect.push(taskForTerminal.id);
+ this._terminals[terminal.instanceId] = { terminal, lastTask: taskForTerminal.lastTask, group: taskForTerminal.group };
+ } else {
+ this._logService.trace(`Could not reconnect to terminal ${terminal.instanceId} with process details ${terminal.shellLaunchConfig.attachPersistentProcess}`);
+ }
+ }
+ this._hasReconnected = true;
+ }
+
public get onDidStateChange(): Event<ITaskEvent> {
return this._onDidStateChange.event;
}
@@ -263,6 +273,22 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
this._outputService.showChannel(this._outputChannelId, true);
}
+ public reconnect(task: Task, resolver: ITaskResolver, trigger: string = Triggers.command): ITaskExecuteResult | undefined {
+ const terminals = this._terminalService.getReconnectedTerminals(ReconnectionType);
+ if (!terminals || terminals?.length === 0) {
+ return;
+ }
+ if (!this._hasReconnected && terminals && terminals.length > 0) {
+ this._reviveTerminals();
+ this._reconnectToTerminals(terminals);
+ }
+ if (this._tasksToReconnect.includes(task._id)) {
+ this._terminalForTask = terminals.find(t => t.shellLaunchConfig.attachPersistentProcess?.task?.id === task._id);
+ this.run(task, resolver, trigger);
+ }
+ return undefined;
+ }
+
public run(task: Task, resolver: ITaskResolver, trigger: string = Triggers.command): ITaskExecuteResult {
task = task.clone(); // A small amount of task state is stored in the task (instance) and tasks passed in to run may have that set already.
const recentTaskKey = task.getRecentlyUsedKey() ?? '';
@@ -280,7 +306,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
}
try {
- const executeResult = { kind: TaskExecuteKind.Started, task, started: {}, promise: this._executeTask(task, resolver, trigger, new Set()) };
+ const executeResult = { kind: TaskExecuteKind.Started, task, started: {}, promise: this._executeTask(task, resolver, trigger, new Set(), undefined) };
executeResult.promise.then(summary => {
this._lastTask = this._currentTask;
});
@@ -430,12 +456,14 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
}
}
- private _removeFromActiveTasks(task: Task): void {
- if (!this._activeTasks[task.getMapKey()]) {
+ private _removeFromActiveTasks(task: Task | string): void {
+ const key = typeof task === 'string' ? task : task.getMapKey();
+ if (!this._activeTasks[key]) {
return;
}
- delete this._activeTasks[task.getMapKey()];
- this._removeInstances(task);
+ const taskToRemove = this._activeTasks[key];
+ delete this._activeTasks[key];
+ this._removeInstances(taskToRemove.task);
}
private _fireTaskEvent(event: ITaskEvent) {
@@ -853,7 +881,8 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
}));
watchingProblemMatcher.aboutToStart();
let delayer: Async.Delayer<any> | undefined = undefined;
- [terminal, error] = await this._createTerminal(task, resolver, workspaceFolder);
+ [terminal, error] = this._terminalForTask ? [this._terminalForTask, undefined] : await this._createTerminal(task, resolver, workspaceFolder);
+ this._terminalForTask = undefined;
if (error) {
return Promise.reject(new Error((<TaskError>error).message));
@@ -935,7 +964,8 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
});
});
} else {
- [terminal, error] = await this._createTerminal(task, resolver, workspaceFolder);
+ [terminal, error] = this._terminalForTask ? [this._terminalForTask, undefined] : await this._createTerminal(task, resolver, workspaceFolder);
+ this._terminalForTask = undefined;
if (error) {
return Promise.reject(new Error((<TaskError>error).message));
@@ -1040,7 +1070,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
const isShellCommand = task.command.runtime === RuntimeType.Shell;
const needsFolderQualification = this._contextService.getWorkbenchState() === WorkbenchState.WORKSPACE;
const terminalName = this._createTerminalName(task);
- const type = 'Task';
+ const type = ReconnectionType;
const originalCommand = task.command.name;
if (isShellCommand) {
let os: Platform.OperatingSystem;
@@ -1269,6 +1299,40 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
return createdTerminal;
}
+ private _reviveTerminals(): void {
+ if (Object.entries(this._terminals).length > 0) {
+ return;
+ }
+ const terminals = this._terminalService.getReconnectedTerminals(ReconnectionType)?.filter(t => !t.isDisposed);
+ if (!terminals?.length) {
+ return;
+ }
+ for (const terminal of terminals) {
+ const task = terminal.shellLaunchConfig.attachPersistentProcess?.task;
+ if (!task) {
+ continue;
+ }
+ const terminalData = { lastTask: task.lastTask, group: task.group, terminal };
+ this._terminals[terminal.instanceId] = terminalData;
+ terminal.onDisposed(() => this._deleteTaskAndTerminal(terminal, terminalData));
+ }
+ }
+
+ private _deleteTaskAndTerminal(terminal: ITerminalInstance, terminalData: ITerminalData): void {
+ delete this._terminals[terminal.instanceId];
+ delete this._sameTaskTerminals[terminalData.lastTask];
+ this._idleTaskTerminals.delete(terminalData.lastTask);
+ // Delete the task now as a work around for cases when the onExit isn't fired.
+ // This can happen if the terminal wasn't shutdown with an "immediate" flag and is expected.
+ // For correct terminal re-use, the task needs to be deleted immediately.
+ // Note that this shouldn't be a problem anymore since user initiated terminal kills are now immediate.
+ const mapKey = terminalData.lastTask;
+ this._removeFromActiveTasks(mapKey);
+ if (this._busyTasks[mapKey]) {
+ delete this._busyTasks[mapKey];
+ }
+ }
+
private async _createTerminal(task: CustomTask | ContributedTask, resolver: VariableResolver, workspaceFolder: IWorkspaceFolder | undefined): Promise<[ITerminalInstance | undefined, TaskError | undefined]> {
const platform = resolver.taskSystemInfo ? resolver.taskSystemInfo.platform : Platform.platform;
const options = await this._resolveOptions(resolver, task.command.options);
@@ -1308,7 +1372,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
}, 'Executing task: {0}', task._label), { excludeLeadingNewLine: true }) : undefined,
isFeatureTerminal: true,
icon: task.configurationProperties.icon?.id ? ThemeIcon.fromId(task.configurationProperties.icon.id) : undefined,
- color: task.configurationProperties.icon?.color || undefined,
+ color: task.configurationProperties.icon?.color || undefined
};
} else {
const resolvedResult: { command: CommandString; args: CommandString[] } = await this._resolveCommandAndArgs(resolver, task.command);
@@ -1368,28 +1432,14 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
}
this._terminalCreationQueue = this._terminalCreationQueue.then(() => this._doCreateTerminal(group, launchConfigs!));
- const result: ITerminalInstance = (await this._terminalCreationQueue)!;
-
- const terminalKey = result.instanceId.toString();
- result.onDisposed((terminal) => {
- const terminalData = this._terminals[terminalKey];
- if (terminalData) {
- delete this._terminals[terminalKey];
- delete this._sameTaskTerminals[terminalData.lastTask];
- this._idleTaskTerminals.delete(terminalData.lastTask);
- // Delete the task now as a work around for cases when the onExit isn't fired.
- // This can happen if the terminal wasn't shutdown with an "immediate" flag and is expected.
- // For correct terminal re-use, the task needs to be deleted immediately.
- // Note that this shouldn't be a problem anymore since user initiated terminal kills are now immediate.
- const mapKey = task.getMapKey();
- this._removeFromActiveTasks(task);
- if (this._busyTasks[mapKey]) {
- delete this._busyTasks[mapKey];
- }
- }
- });
- this._terminals[terminalKey] = { terminal: result, lastTask: taskKey, group };
- return [result, undefined];
+ const terminal: ITerminalInstance = (await this._terminalCreationQueue)!;
+ terminal.shellLaunchConfig.task = { lastTask: taskKey, group, label: task._label, id: task._id };
+ terminal.shellLaunchConfig.reconnectionOwner = ReconnectionType;
+ const terminalKey = terminal.instanceId.toString();
+ const terminalData = { terminal: terminal, lastTask: taskKey, group };
+ terminal.onDisposed(() => this._deleteTaskAndTerminal(terminal, terminalData));
+ this._terminals[terminalKey] = terminalData;
+ return [terminal, undefined];
}
private _buildShellCommandLine(platform: Platform.Platform, shellExecutable: string, shellOptions: IShellConfiguration | undefined, command: CommandString, originalCommand: CommandString | undefined, args: CommandString[]): string {
diff --git a/src/vs/workbench/contrib/tasks/common/taskService.ts b/src/vs/workbench/contrib/tasks/common/taskService.ts
index 86acc2a6a03..afc0ed385fa 100644
--- a/src/vs/workbench/contrib/tasks/common/taskService.ts
+++ b/src/vs/workbench/contrib/tasks/common/taskService.ts
@@ -19,6 +19,7 @@ export { ITaskSummary, Task, ITaskTerminateResponse as TaskTerminateResponse };
export const CustomExecutionSupportedContext = new RawContextKey<boolean>('customExecutionSupported', true, nls.localize('tasks.customExecutionSupported', "Whether CustomExecution tasks are supported. Consider using in the when clause of a \'taskDefinition\' contribution."));
export const ShellExecutionSupportedContext = new RawContextKey<boolean>('shellExecutionSupported', false, nls.localize('tasks.shellExecutionSupported', "Whether ShellExecution tasks are supported. Consider using in the when clause of a \'taskDefinition\' contribution."));
+export const TaskCommandsRegistered = new RawContextKey<boolean>('taskCommandsRegistered', false, nls.localize('tasks.taskCommandsRegistered', "Whether the task commands have been registered yet"));
export const ProcessExecutionSupportedContext = new RawContextKey<boolean>('processExecutionSupported', false, nls.localize('tasks.processExecutionSupported', "Whether ProcessExecution tasks are supported. Consider using in the when clause of a \'taskDefinition\' contribution."));
export const ITaskService = createDecorator<ITaskService>('taskService');
diff --git a/src/vs/workbench/contrib/tasks/common/taskSystem.ts b/src/vs/workbench/contrib/tasks/common/taskSystem.ts
index cd204106e8e..7b7b67f3a19 100644
--- a/src/vs/workbench/contrib/tasks/common/taskSystem.ts
+++ b/src/vs/workbench/contrib/tasks/common/taskSystem.ts
@@ -102,6 +102,7 @@ export interface ITaskSystemInfoResolver {
export interface ITaskSystem {
onDidStateChange: Event<ITaskEvent>;
run(task: Task, resolver: ITaskResolver): ITaskExecuteResult;
+ reconnect(task: Task, resolver: ITaskResolver): ITaskExecuteResult | undefined;
rerun(): ITaskExecuteResult | undefined;
isActive(): Promise<boolean>;
isActiveSync(): boolean;
diff --git a/src/vs/workbench/contrib/tasks/common/tasks.ts b/src/vs/workbench/contrib/tasks/common/tasks.ts
index 5877beb6437..2033ec632d9 100644
--- a/src/vs/workbench/contrib/tasks/common/tasks.ts
+++ b/src/vs/workbench/contrib/tasks/common/tasks.ts
@@ -1131,7 +1131,8 @@ export const enum TaskRunSource {
System,
User,
FolderOpen,
- ConfigurationChange
+ ConfigurationChange,
+ Reconnect
}
export namespace TaskEvent {
diff --git a/src/vs/workbench/contrib/tasks/electron-sandbox/taskService.ts b/src/vs/workbench/contrib/tasks/electron-sandbox/taskService.ts
index 0620c9bc2d5..050ef08efde 100644
--- a/src/vs/workbench/contrib/tasks/electron-sandbox/taskService.ts
+++ b/src/vs/workbench/contrib/tasks/electron-sandbox/taskService.ts
@@ -44,6 +44,7 @@ import { IWorkspaceTrustManagementService, IWorkspaceTrustRequestService } from
import { ITerminalProfileResolverService } from 'vs/workbench/contrib/terminal/common/terminal';
import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite';
import { IThemeService } from 'vs/platform/theme/common/themeService';
+import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
interface IWorkspaceFolderConfigurationResult {
workspaceFolder: IWorkspaceFolder;
@@ -85,7 +86,8 @@ export class TaskService extends AbstractTaskService {
@IWorkspaceTrustRequestService workspaceTrustRequestService: IWorkspaceTrustRequestService,
@IWorkspaceTrustManagementService workspaceTrustManagementService: IWorkspaceTrustManagementService,
@ILogService logService: ILogService,
- @IThemeService themeService: IThemeService) {
+ @IThemeService themeService: IThemeService,
+ @IInstantiationService instantiationService: IInstantiationService) {
super(configurationService,
markerService,
outputService,
@@ -118,7 +120,8 @@ export class TaskService extends AbstractTaskService {
workspaceTrustRequestService,
workspaceTrustManagementService,
logService,
- themeService);
+ themeService,
+ );
this._register(lifecycleService.onBeforeShutdown(event => event.veto(this.beforeShutdown(), 'veto.tasks')));
}
diff --git a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts
index dfcf34f405d..40346309f5e 100644
--- a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts
+++ b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts
@@ -36,11 +36,11 @@ type TelemetryData = {
};
type FileTelemetryDataFragment = {
- mimeType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
- ext: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
- path: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
- reason?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true };
- allowlistedjson?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
+ mimeType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The language type of the file (for example XML).' };
+ ext: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The file extension of the file (for example xml).' };
+ path: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The path of the file as a hash.' };
+ reason?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The reason why a file is read or written. Allows to e.g. distinguish auto save from normal save.' };
+ allowlistedjson?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The name of the file but only if it matches some well known file names such as package.json or tsconfig.json.' };
};
export class TelemetryContribution extends Disposable implements IWorkbenchContribution {
@@ -63,30 +63,33 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr
) {
super();
- const { filesToOpenOrCreate, filesToDiff } = environmentService;
+ const { filesToOpenOrCreate, filesToDiff, filesToMerge } = environmentService;
const activeViewlet = paneCompositeService.getActivePaneComposite(ViewContainerLocation.Sidebar);
type WindowSizeFragment = {
- innerHeight: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true };
- innerWidth: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true };
- outerHeight: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true };
- outerWidth: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true };
+ innerHeight: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The height of the current window.' };
+ innerWidth: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The width of the current window.' };
+ outerHeight: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The height of the current window with all decoration removed.' };
+ outerWidth: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The width of the current window with all decoration removed.' };
+ comment: 'The size of the window.';
};
type WorkspaceLoadClassification = {
owner: 'bpasero';
- userAgent: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
- emptyWorkbench: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true };
+ userAgent: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The user agent as reported by `navigator.userAgent` by Electron or the web browser.' };
+ emptyWorkbench: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Whether a folder or workspace is opened or not.' };
windowSize: WindowSizeFragment;
- 'workbench.filesToOpenOrCreate': { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true };
- 'workbench.filesToDiff': { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true };
+ 'workbench.filesToOpenOrCreate': { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Number of files that should open or be created.' };
+ 'workbench.filesToDiff': { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Number of files that should be compared.' };
+ 'workbench.filesToMerge': { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Number of files that should be merged.' };
customKeybindingsCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true };
- theme: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
- language: { classification: 'SystemMetaData'; purpose: 'BusinessInsight' };
- pinnedViewlets: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
- restoredViewlet?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
- restoredEditors: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true };
- startupKind: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true };
+ theme: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The current theme of the window.' };
+ language: { classification: 'SystemMetaData'; purpose: 'BusinessInsight'; comment: 'The display language of the window.' };
+ pinnedViewlets: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifiers of views that are pinned.' };
+ restoredViewlet?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier of the view that is restored.' };
+ restoredEditors: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of editors that restored.' };
+ startupKind: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'How the window was opened, e.g via reload or not.' };
+ comment: 'Metadata around the workspace that is being loaded into a window.';
};
type WorkspaceLoadEvent = {
@@ -95,6 +98,7 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr
emptyWorkbench: boolean;
'workbench.filesToOpenOrCreate': number;
'workbench.filesToDiff': number;
+ 'workbench.filesToMerge': number;
customKeybindingsCount: number;
theme: string;
language: string;
@@ -110,6 +114,7 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr
emptyWorkbench: contextService.getWorkbenchState() === WorkbenchState.EMPTY,
'workbench.filesToOpenOrCreate': filesToOpenOrCreate && filesToOpenOrCreate.length || 0,
'workbench.filesToDiff': filesToDiff && filesToDiff.length || 0,
+ 'workbench.filesToMerge': filesToMerge && filesToMerge.length || 0,
customKeybindingsCount: keybindingsService.customKeybindingsCount(),
theme: themeService.getColorTheme().id,
language,
@@ -138,13 +143,15 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr
if (settingsType) {
type SettingsReadClassification = {
owner: 'bpasero';
- settingsType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
+ settingsType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The type of the settings file that was read.' };
+ comment: 'Track when a settings file was read, for example from an editor.';
};
this.telemetryService.publicLog2<{ settingsType: string }, SettingsReadClassification>('settingsRead', { settingsType }); // Do not log read to user settings.json and .vscode folder as a fileGet event as it ruins our JSON usage data
} else {
type FileGetClassification = {
owner: 'bpasero';
+ comment: 'Track when a file was read, for example from an editor.';
} & FileTelemetryDataFragment;
this.telemetryService.publicLog2<TelemetryData, FileGetClassification>('fileGet', this.getTelemetryData(e.model.resource, e.reason));
@@ -156,12 +163,14 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr
if (settingsType) {
type SettingsWrittenClassification = {
owner: 'bpasero';
- settingsType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
+ settingsType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The type of the settings file that was written to.' };
+ comment: 'Track when a settings file was written to, for example from an editor.';
};
this.telemetryService.publicLog2<{ settingsType: string }, SettingsWrittenClassification>('settingsWritten', { settingsType }); // Do not log write to user settings.json and .vscode folder as a filePUT event as it ruins our JSON usage data
} else {
type FilePutClassfication = {
owner: 'bpasero';
+ comment: 'Track when a file was written to, for example from an editor.';
} & FileTelemetryDataFragment;
this.telemetryService.publicLog2<TelemetryData, FilePutClassfication>('filePUT', this.getTelemetryData(e.model.resource, e.reason));
}
diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts
index cec80cf90b1..dd4e9840004 100644
--- a/src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts
+++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts
@@ -53,7 +53,7 @@ export const lineAndColumnClause = [
'((\\S*)[\'"], line ((\\d+)( column (\\d+))?))', // "(file path)", line 45 [see #40468]
'((\\S*)[\'"],((\\d+)(:(\\d+))?))', // "(file path)",45 [see #78205]
'((\\S*) on line ((\\d+)(, column (\\d+))?))', // (file path) on line 8, column 13
- '((\\S*):\\s?line ((\\d+)(, col(umn)? (\\d+))?))', // (file path):line 8, column 13, (file path): line 8, col 13
+ '((\\S*):\\s?line ((\\d+)(, col(?:umn)? (\\d+))?))', // (file path):line 8, column 13, (file path): line 8, col 13
'(([^\\s\\(\\)]*)(\\s?[\\(\\[](\\d+)(,\\s?(\\d+))?)[\\)\\]])', // (file path)(45), (file path) (45), (file path)(45,18), (file path) (45,18), (file path)(45, 18), (file path) (45, 18), also with []
'(([^:\\s\\(\\)<>\'\"\\[\\]]*)(:(\\d+))?(:(\\d+))?)' // (file path):336, (file path):336:9
].join('|').replace(/ /g, `[${'\u00A0'} ]`);
diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css
index 17933a5776d..16275cb5114 100644
--- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css
+++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css
@@ -14,6 +14,10 @@
position: relative;
}
+.terminal-command-decoration.hide {
+ visibility: hidden;
+}
+
.monaco-workbench .pane-body.integrated-terminal .terminal-outer-container,
.monaco-workbench .pane-body.integrated-terminal .terminal-groups-container,
.monaco-workbench .pane-body.integrated-terminal .terminal-group,
diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts
index aa125ac3d78..45871689f60 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminal.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts
@@ -133,6 +133,7 @@ export interface ITerminalService extends ITerminalInstanceHost {
readonly connectionState: TerminalConnectionState;
readonly defaultLocation: TerminalLocation;
+
initializeTerminals(): Promise<void>;
onDidChangeActiveGroup: Event<ITerminalGroup | undefined>;
onDidDisposeGroup: Event<ITerminalGroup>;
@@ -163,6 +164,11 @@ export interface ITerminalService extends ITerminalInstanceHost {
getInstanceFromId(terminalId: number): ITerminalInstance | undefined;
getInstanceFromIndex(terminalIndex: number): ITerminalInstance;
+ /**
+ * An owner of terminals might be created after reconnection has occurred,
+ * so store them to be requested/adopted later
+ */
+ getReconnectedTerminals(reconnectionOwner: string): ITerminalInstance[] | undefined;
getActiveOrCreateInstance(): Promise<ITerminalInstance>;
moveToEditor(source: ITerminalInstance): void;
@@ -439,7 +445,7 @@ export interface ITerminalInstance {
readonly fixedRows?: number;
readonly icon?: TerminalIcon;
readonly color?: string;
-
+ readonly reconnectionOwner?: string;
readonly processName: string;
readonly sequence?: string;
readonly staticTitle?: string;
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts
index 55032336cd5..dd5684d1ad8 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts
@@ -100,7 +100,7 @@ export class TerminalEditorInput extends EditorInput implements IEditorCloseHand
return false;
}
- async confirm(terminals?: ReadonlyArray<IEditorIdentifier>): Promise<ConfirmResult> {
+ async confirm(terminals: ReadonlyArray<IEditorIdentifier>): Promise<ConfirmResult> {
const { choice } = await this._dialogService.show(
Severity.Warning,
localize('confirmDirtyTerminal.message', "Do you want to terminate running processes?"),
@@ -110,7 +110,7 @@ export class TerminalEditorInput extends EditorInput implements IEditorCloseHand
],
{
cancelId: 1,
- detail: terminals && terminals.length > 1 ?
+ detail: terminals.length > 1 ?
terminals.map(terminal => terminal.editor.getName()).join('\n') + '\n\n' + localize('confirmDirtyTerminals.detail', "Closing will terminate the running processes in the terminals.") :
localize('confirmDirtyTerminal.detail', "Closing will terminate the running processes in this terminal.")
}
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts
index 1f8a4970e50..bbfb4d97207 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts
@@ -276,6 +276,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
// TODO: Should this be an event as it can fire twice?
get processReady(): Promise<void> { return this._processManager.ptyProcessReady; }
get hasChildProcesses(): boolean { return this.shellLaunchConfig.attachPersistentProcess?.hasChildProcesses || this._processManager.hasChildProcesses; }
+ get reconnectionOwner(): string | undefined { return this.shellLaunchConfig.attachPersistentProcess?.reconnectionOwner || this.shellLaunchConfig.reconnectionOwner; }
get areLinksReady(): boolean { return this._areLinksReady; }
get initialDataEvents(): string[] | undefined { return this._initialDataEvents; }
get exitCode(): number | undefined { return this._exitCode; }
@@ -1236,7 +1237,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
}
private _setShellIntegrationContextKey(): void {
- console.log('set', this.xterm?.shellIntegration.status === ShellIntegrationStatus.VSCode);
if (this.xterm) {
this._terminalShellIntegrationEnabledContextKey.set(this.xterm.shellIntegration.status === ShellIntegrationStatus.VSCode);
}
@@ -1727,7 +1727,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
if (this._isExiting) {
return;
}
-
const parsedExitResult = parseExitResult(exitCodeOrError, this.shellLaunchConfig, this._processManager.processState, this._initialCwd);
if (this._usedShellIntegrationInjection && this._processManager.processState === ProcessState.KilledDuringLaunch && parsedExitResult?.code !== 0) {
@@ -2356,7 +2355,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
info.requiresAction &&
this._configHelper.config.environmentChangesRelaunch &&
!this._processManager.hasWrittenData &&
- !this._shellLaunchConfig.isFeatureTerminal &&
+ (this.reconnectionOwner || !this._shellLaunchConfig.isFeatureTerminal) &&
!this._shellLaunchConfig.customPtyImplementation
&& !this._shellLaunchConfig.isExtensionOwnedTerminal &&
!this._shellLaunchConfig.attachPersistentProcess
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts
index ba6540e654c..901505cb26a 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts
@@ -113,9 +113,10 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
readonly onRestoreCommands = this._onRestoreCommands.event;
get persistentProcessId(): number | undefined { return this._process?.id; }
- get shouldPersist(): boolean { return this._process ? this._process.shouldPersist : false; }
+ get shouldPersist(): boolean { return !!this.reconnectionOwner || (this._process ? this._process.shouldPersist : false); }
get hasWrittenData(): boolean { return this._hasWrittenData; }
get hasChildProcesses(): boolean { return this._hasChildProcesses; }
+ get reconnectionOwner(): string | undefined { return this._shellLaunchConfig?.attachPersistentProcess?.reconnectionOwner || this._shellLaunchConfig?.reconnectionOwner || undefined; }
constructor(
private readonly _instanceId: number,
@@ -245,7 +246,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
// this is a copy of what the merged environment collection is on the remote side
const env = await this._resolveEnvironment(backend, variableResolver, shellLaunchConfig);
- const shouldPersist = !shellLaunchConfig.isFeatureTerminal && this._configHelper.config.enablePersistentSessions && !shellLaunchConfig.isTransient;
+ const shouldPersist = (!!shellLaunchConfig.reconnectionOwner || !shellLaunchConfig.isFeatureTerminal) && this._configHelper.config.enablePersistentSessions && !shellLaunchConfig.isTransient;
if (shellLaunchConfig.attachPersistentProcess) {
const result = await backend.attachToProcess(shellLaunchConfig.attachPersistentProcess.id);
if (result) {
@@ -461,7 +462,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
windowsEnableConpty: this._configHelper.config.windowsEnableConpty && !isScreenReaderModeEnabled,
environmentVariableCollections: this._extEnvironmentVariableCollection ? serializeEnvironmentVariableCollections(this._extEnvironmentVariableCollection.collections) : undefined
};
- const shouldPersist = this._configHelper.config.enablePersistentSessions && !shellLaunchConfig.isFeatureTerminal;
+ const shouldPersist = this._configHelper.config.enablePersistentSessions && (!!this.reconnectionOwner || !shellLaunchConfig.isFeatureTerminal);
return await backend.createProcess(shellLaunchConfig, initialCwd, cols, rows, this._configHelper.config.unicodeVersion, env, options, shouldPersist);
}
@@ -494,7 +495,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
this._ptyResponsiveListener?.dispose();
this._ptyResponsiveListener = undefined;
if (this._shellLaunchConfig) {
- if (this._shellLaunchConfig.isFeatureTerminal) {
+ if (this._shellLaunchConfig.isFeatureTerminal && !this.reconnectionOwner) {
// Indicate the process is exited (and gone forever) only for feature terminals
// so they can react to the exit, this is particularly important for tasks so
// that it knows that the process is not still active. Note that this is not
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts
index 7ed18631566..f315f11cd30 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts
@@ -84,6 +84,12 @@ export class TerminalService implements ITerminalService {
return this._terminalGroupService.instances.concat(this._terminalEditorService.instances);
}
+
+ private _reconnectedTerminals: Map<string, ITerminalInstance[]> = new Map();
+ getReconnectedTerminals(reconnectionOwner: string): ITerminalInstance[] | undefined {
+ return this._reconnectedTerminals.get(reconnectionOwner);
+ }
+
get defaultLocation(): TerminalLocation { return this.configHelper.config.defaultLocation === TerminalLocationString.Editor ? TerminalLocation.Editor : TerminalLocation.Panel; }
private _activeInstance: ITerminalInstance | undefined;
@@ -1039,9 +1045,21 @@ export class TerminalService implements ITerminalService {
shellLaunchConfig.parentTerminalId = parent.instanceId;
instance = group.split(shellLaunchConfig);
}
+ this._addToReconnected(instance);
return instance;
}
+ private _addToReconnected(instance: ITerminalInstance): void {
+ if (instance.reconnectionOwner) {
+ const reconnectedTerminals = this._reconnectedTerminals.get(instance.reconnectionOwner);
+ if (reconnectedTerminals) {
+ reconnectedTerminals.push(instance);
+ } else {
+ this._reconnectedTerminals.set(instance.reconnectionOwner, [instance]);
+ }
+ }
+ }
+
private _createTerminal(shellLaunchConfig: IShellLaunchConfig, location: TerminalLocation, options?: ICreateTerminalOptions): ITerminalInstance {
let instance;
const editorOptions = this._getEditorOptions(options?.location);
@@ -1054,6 +1072,7 @@ export class TerminalService implements ITerminalService {
const group = this._terminalGroupService.createGroup(shellLaunchConfig);
instance = group.terminalInstances[0];
}
+ this._addToReconnected(instance);
return instance;
}
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts
index 9dc9017d20f..bb3b8225a5f 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts
@@ -46,6 +46,7 @@ import { getTerminalActionBarArgs } from 'vs/workbench/contrib/terminal/browser/
import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey';
import { getShellIntegrationTooltip } from 'vs/workbench/contrib/terminal/browser/terminalTooltip';
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
+import { TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities';
export class TerminalViewPane extends ViewPane {
private _actions: IAction[] | undefined;
@@ -65,7 +66,7 @@ export class TerminalViewPane extends ViewPane {
@IKeybindingService keybindingService: IKeybindingService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
- @IConfigurationService configurationService: IConfigurationService,
+ @IConfigurationService private readonly _configurationService: IConfigurationService,
@IContextMenuService private readonly _contextMenuService: IContextMenuService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@ITerminalService private readonly _terminalService: ITerminalService,
@@ -81,7 +82,7 @@ export class TerminalViewPane extends ViewPane {
@ITerminalProfileResolverService private readonly _terminalProfileResolverService: ITerminalProfileResolverService,
@IThemeService private readonly _themeService: IThemeService
) {
- super(options, keybindingService, _contextMenuService, configurationService, _contextKeyService, viewDescriptorService, _instantiationService, openerService, themeService, telemetryService);
+ super(options, keybindingService, _contextMenuService, _configurationService, _contextKeyService, viewDescriptorService, _instantiationService, openerService, themeService, telemetryService);
this._register(this._terminalService.onDidRegisterProcessSupport(() => {
if (this._actions) {
for (const action of this._actions) {
@@ -111,20 +112,34 @@ export class TerminalViewPane extends ViewPane {
this._terminalTabbedView?.rerenderTabs();
}
}));
- configurationService.onDidChangeConfiguration(e => {
- if ((e.affectsConfiguration(TerminalSettingId.ShellIntegrationDecorationsEnabled) && !configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationsEnabled)) ||
- (e.affectsConfiguration(TerminalSettingId.ShellIntegrationEnabled) && !configurationService.getValue(TerminalSettingId.ShellIntegrationEnabled))) {
- this._parentDomElement?.classList.remove('shell-integration');
- } else if (configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationsEnabled) && configurationService.getValue(TerminalSettingId.ShellIntegrationEnabled)) {
- this._parentDomElement?.classList.add('shell-integration');
+ _configurationService.onDidChangeConfiguration(e => {
+ if (e.affectsConfiguration(TerminalSettingId.ShellIntegrationDecorationsEnabled) || e.affectsConfiguration(TerminalSettingId.ShellIntegrationEnabled)) {
+ this._updateForShellIntegration();
}
});
+ this._register(this._terminalService.onDidCreateInstance((i) => {
+ i.capabilities.onDidAddCapability(c => {
+ if (c === TerminalCapability.CommandDetection && !this._gutterDecorationsEnabled()) {
+ this._parentDomElement?.classList.add('shell-integration');
+ }
+ });
+ }));
+ this._updateForShellIntegration();
+ }
- if (configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationsEnabled) && configurationService.getValue(TerminalSettingId.ShellIntegrationEnabled)) {
+ private _updateForShellIntegration() {
+ if (this._gutterDecorationsEnabled()) {
this._parentDomElement?.classList.add('shell-integration');
+ } else {
+ this._parentDomElement?.classList.remove('shell-integration');
}
}
+ private _gutterDecorationsEnabled(): boolean {
+ const decorationsEnabled = this._configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationsEnabled);
+ return (decorationsEnabled === 'both' || decorationsEnabled === 'gutter') && this._configurationService.getValue(TerminalSettingId.ShellIntegrationEnabled);
+ }
+
override renderBody(container: HTMLElement): void {
super.renderBody(container);
diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts
index 45b820a935c..feff72648ca 100644
--- a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts
+++ b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts
@@ -25,16 +25,19 @@ import { TERMINAL_COMMAND_DECORATION_DEFAULT_BACKGROUND_COLOR, TERMINAL_COMMAND_
import { Color } from 'vs/base/common/color';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IGenericMarkProperties } from 'vs/platform/terminal/common/terminalProcess';
+import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
+import { Codicon } from 'vs/base/common/codicons';
const enum DecorationSelector {
CommandDecoration = 'terminal-command-decoration',
+ Hide = 'hide',
ErrorColor = 'error',
DefaultColor = 'default-color',
Default = 'default',
Codicon = 'codicon',
XtermDecoration = 'xterm-decoration',
- OverviewRuler = 'xterm-decoration-overview-ruler',
- GenericMarkerIcon = 'codicon-circle-small-filled'
+ GenericMarkerIcon = 'codicon-circle-small-filled',
+ OverviewRuler = '.xterm-decoration-overview-ruler'
}
const enum DecorationStyles {
@@ -51,6 +54,8 @@ export class DecorationAddon extends Disposable implements ITerminalAddon {
private _contextMenuVisible: boolean = false;
private _decorations: Map<number, IDisposableDecoration> = new Map();
private _placeholderDecoration: IDecoration | undefined;
+ private _showGutterDecorations?: boolean;
+ private _showOverviewRulerDecorations?: boolean;
private readonly _onDidRequestRunCommand = this._register(new Emitter<{ command: ITerminalCommand; copyAsHtml?: boolean }>());
readonly onDidRequestRunCommand = this._onDidRequestRunCommand.event;
@@ -62,7 +67,8 @@ export class DecorationAddon extends Disposable implements ITerminalAddon {
@IHoverService private readonly _hoverService: IHoverService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@IThemeService private readonly _themeService: IThemeService,
- @IOpenerService private readonly _openerService: IOpenerService
+ @IOpenerService private readonly _openerService: IOpenerService,
+ @IQuickInputService private readonly _quickInputService: IQuickInputService
) {
super();
this._register(toDisposable(() => this._dispose()));
@@ -79,9 +85,67 @@ export class DecorationAddon extends Disposable implements ITerminalAddon {
this.refreshLayouts();
} else if (e.affectsConfiguration('workbench.colorCustomizations')) {
this._refreshStyles(true);
+ } else if (e.affectsConfiguration(TerminalSettingId.ShellIntegrationDecorationsEnabled)) {
+ if (this._commandDetectionListeners) {
+ dispose(this._commandDetectionListeners);
+ this._commandDetectionListeners = undefined;
+ }
+ this._updateDecorationVisibility();
}
});
this._themeService.onDidColorThemeChange(() => this._refreshStyles(true));
+ this._updateDecorationVisibility();
+ this._register(this._capabilities.onDidAddCapability(c => {
+ if (c === TerminalCapability.CommandDetection) {
+ this._addCommandDetectionListeners();
+ }
+ }));
+ this._register(this._capabilities.onDidRemoveCapability(c => {
+ if (c === TerminalCapability.CommandDetection) {
+ if (this._commandDetectionListeners) {
+ dispose(this._commandDetectionListeners);
+ this._commandDetectionListeners = undefined;
+ }
+ }
+ }));
+ }
+
+ private _updateDecorationVisibility(): void {
+ const showDecorations = this._configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationsEnabled);
+ this._showGutterDecorations = (showDecorations === 'both' || showDecorations === 'gutter');
+ this._showOverviewRulerDecorations = (showDecorations === 'both' || showDecorations === 'overviewRuler');
+ this._disposeAllDecorations();
+ if (this._showGutterDecorations || this._showOverviewRulerDecorations) {
+ this._attachToCommandCapability();
+ this._updateGutterDecorationVisibility();
+ }
+ const currentCommand = this._capabilities.get(TerminalCapability.CommandDetection)?.executingCommandObject;
+ if (currentCommand) {
+ this.registerCommandDecoration(currentCommand, true);
+ }
+ }
+
+ private _disposeAllDecorations(): void {
+ this._placeholderDecoration?.dispose();
+ for (const value of this._decorations.values()) {
+ value.decoration.dispose();
+ dispose(value.disposables);
+ }
+ }
+
+ private _updateGutterDecorationVisibility(): void {
+ const commandDecorationElements = document.querySelectorAll(DecorationSelector.CommandDecoration);
+ for (const commandDecorationElement of commandDecorationElements) {
+ this._updateCommandDecorationVisibility(commandDecorationElement);
+ }
+ }
+
+ private _updateCommandDecorationVisibility(commandDecorationElement: Element): void {
+ if (this._showGutterDecorations) {
+ commandDecorationElement.classList.remove(DecorationSelector.Hide);
+ } else {
+ commandDecorationElement.classList.add(DecorationSelector.Hide);
+ }
}
public refreshLayouts(): void {
@@ -128,31 +192,14 @@ export class DecorationAddon extends Disposable implements ITerminalAddon {
public clearDecorations(): void {
this._placeholderDecoration?.marker.dispose();
this._clearPlaceholder();
- for (const value of this._decorations.values()) {
- value.decoration.dispose();
- dispose(value.disposables);
- }
+ this._disposeAllDecorations();
this._decorations.clear();
}
private _attachToCommandCapability(): void {
if (this._capabilities.has(TerminalCapability.CommandDetection)) {
this._addCommandDetectionListeners();
- } else {
- this._register(this._capabilities.onDidAddCapability(c => {
- if (c === TerminalCapability.CommandDetection) {
- this._addCommandDetectionListeners();
- }
- }));
}
- this._register(this._capabilities.onDidRemoveCapability(c => {
- if (c === TerminalCapability.CommandDetection) {
- if (this._commandDetectionListeners) {
- dispose(this._commandDetectionListeners);
- this._commandDetectionListeners = undefined;
- }
- }
- }));
}
private _addCommandDetectionListeners(): void {
@@ -204,13 +251,12 @@ export class DecorationAddon extends Disposable implements ITerminalAddon {
}
registerCommandDecoration(command: ITerminalCommand, beforeCommandExecution?: boolean): IDecoration | undefined {
- if (!this._terminal || (beforeCommandExecution && command.genericMarkProperties)) {
+ if (!this._terminal || (beforeCommandExecution && command.genericMarkProperties) || (!this._showGutterDecorations && !this._showOverviewRulerDecorations)) {
return undefined;
}
if (!command.marker) {
throw new Error(`cannot add a decoration for a command ${JSON.stringify(command)} with no marker`);
}
-
this._clearPlaceholder();
let color = command.exitCode === undefined ? defaultColor : command.exitCode ? errorColor : successColor;
if (color && typeof color !== 'string') {
@@ -220,9 +266,9 @@ export class DecorationAddon extends Disposable implements ITerminalAddon {
}
const decoration = this._terminal.registerDecoration({
marker: command.marker,
- overviewRulerOptions: beforeCommandExecution
+ overviewRulerOptions: this._showOverviewRulerDecorations ? (beforeCommandExecution
? { color, position: 'left' }
- : { color, position: command.exitCode ? 'right' : 'left' }
+ : { color, position: command.exitCode ? 'right' : 'left' }) : undefined
});
if (!decoration) {
return undefined;
@@ -287,20 +333,25 @@ export class DecorationAddon extends Disposable implements ITerminalAddon {
element.classList.remove(classes);
}
element.classList.add(DecorationSelector.CommandDecoration, DecorationSelector.Codicon, DecorationSelector.XtermDecoration);
+
if (genericMarkProperties) {
element.classList.add(DecorationSelector.DefaultColor, DecorationSelector.GenericMarkerIcon);
if (!genericMarkProperties.hoverMessage) {
//disable the mouse pointer
element.classList.add(DecorationSelector.Default);
}
- } else if (exitCode === undefined) {
- element.classList.add(DecorationSelector.DefaultColor, DecorationSelector.Default);
- element.classList.add(`codicon-${this._configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationIcon)}`);
- } else if (exitCode) {
- element.classList.add(DecorationSelector.ErrorColor);
- element.classList.add(`codicon-${this._configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationIconError)}`);
} else {
- element.classList.add(`codicon-${this._configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationIconSuccess)}`);
+ // command decoration
+ this._updateCommandDecorationVisibility(element);
+ if (exitCode === undefined) {
+ element.classList.add(DecorationSelector.DefaultColor, DecorationSelector.Default);
+ element.classList.add(`codicon-${this._configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationIcon)}`);
+ } else if (exitCode) {
+ element.classList.add(DecorationSelector.ErrorColor);
+ element.classList.add(`codicon-${this._configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationIconError)}`);
+ } else {
+ element.classList.add(`codicon-${this._configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationIconSuccess)}`);
+ }
}
}
@@ -383,13 +434,102 @@ export class DecorationAddon extends Disposable implements ITerminalAddon {
if (actions.length > 0) {
actions.push(new Separator());
}
- const label = localize("terminal.learnShellIntegration", 'Learn About Shell Integration');
+ const labelConfigure = localize("terminal.configureCommandDecorations", 'Configure Command Decorations');
actions.push({
- class: undefined, tooltip: label, dispose: () => { }, id: 'terminal.learnShellIntegration', label, enabled: true,
+ class: undefined, tooltip: labelConfigure, dispose: () => { }, id: 'terminal.configureCommandDecorations', label: labelConfigure, enabled: true,
+ run: () => this._showConfigureCommandDecorationsQuickPick()
+ });
+ const labelAbout = localize("terminal.learnShellIntegration", 'Learn About Shell Integration');
+ actions.push({
+ class: undefined, tooltip: labelAbout, dispose: () => { }, id: 'terminal.learnShellIntegration', label: labelAbout, enabled: true,
run: () => this._openerService.open('https://code.visualstudio.com/docs/editor/integrated-terminal#_shell-integration')
});
return actions;
}
+
+ private async _showConfigureCommandDecorationsQuickPick() {
+ const quickPick = this._quickInputService.createQuickPick();
+ quickPick.items = [
+ { id: 'a', label: localize('changeDefaultIcon', 'Change default icon') },
+ { id: 'b', label: localize('changeSuccessIcon', 'Change success icon') },
+ { id: 'c', label: localize('changeErrorIcon', 'Change error icon') },
+ { type: 'separator' },
+ { id: 'd', label: localize('toggleVisibility', 'Toggle visibility') },
+ ];
+ quickPick.canSelectMany = false;
+ quickPick.onDidAccept(async e => {
+ quickPick.hide();
+ const result = quickPick.activeItems[0];
+ let iconSetting: string | undefined;
+ switch (result.id) {
+ case 'a': iconSetting = TerminalSettingId.ShellIntegrationDecorationIcon; break;
+ case 'b': iconSetting = TerminalSettingId.ShellIntegrationDecorationIconSuccess; break;
+ case 'c': iconSetting = TerminalSettingId.ShellIntegrationDecorationIconError; break;
+ case 'd': this._showToggleVisibilityQuickPick(); break;
+ }
+ if (iconSetting) {
+ this._showChangeIconQuickPick(iconSetting);
+ }
+ });
+ quickPick.show();
+ }
+
+ private async _showChangeIconQuickPick(iconSetting: string) {
+ type Item = IQuickPickItem & { icon: Codicon };
+ const items: Item[] = [];
+ for (const icon of Codicon.getAll()) {
+ items.push({ label: `$(${icon.id})`, description: `${icon.id}`, icon });
+ }
+ const result = await this._quickInputService.pick(items, {
+ matchOnDescription: true
+ });
+ if (result) {
+ this._configurationService.updateValue(iconSetting, result.icon.id);
+ this._showConfigureCommandDecorationsQuickPick();
+ }
+ }
+
+ private _showToggleVisibilityQuickPick() {
+ const quickPick = this._quickInputService.createQuickPick();
+ quickPick.hideInput = true;
+ quickPick.hideCheckAll = true;
+ quickPick.canSelectMany = true;
+ quickPick.title = localize('toggleVisibility', 'Toggle visibility');
+ const configValue = this._configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationsEnabled);
+ const gutterIcon: IQuickPickItem = {
+ label: localize('gutter', 'Gutter command decorations'),
+ picked: configValue !== 'never' && configValue !== 'overviewRuler'
+ };
+ const overviewRulerIcon: IQuickPickItem = {
+ label: localize('overviewRuler', 'Overview ruler command decorations'),
+ picked: configValue !== 'never' && configValue !== 'gutter'
+ };
+ quickPick.items = [gutterIcon, overviewRulerIcon];
+ const selectedItems: IQuickPickItem[] = [];
+ if (configValue !== 'never') {
+ if (configValue !== 'gutter') {
+ selectedItems.push(gutterIcon);
+ }
+ if (configValue !== 'overviewRuler') {
+ selectedItems.push(overviewRulerIcon);
+ }
+ }
+ quickPick.selectedItems = selectedItems;
+ quickPick.onDidChangeSelection(async e => {
+ let newValue: 'both' | 'gutter' | 'overviewRuler' | 'never' = 'never';
+ if (e.includes(gutterIcon)) {
+ if (e.includes(overviewRulerIcon)) {
+ newValue = 'both';
+ } else {
+ newValue = 'gutter';
+ }
+ } else if (e.includes(overviewRulerIcon)) {
+ newValue = 'overviewRuler';
+ }
+ await this._configurationService.updateValue(TerminalSettingId.ShellIntegrationDecorationsEnabled, newValue);
+ });
+ quickPick.show();
+ }
}
let successColor: string | Color | undefined;
let errorColor: string | Color | undefined;
diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts
index 3c5513e7c90..c5b6caee4af 100644
--- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts
+++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts
@@ -117,17 +117,17 @@ const terminalConfiguration: IConfigurationNode = {
[TerminalSettingId.ShellIntegrationDecorationIconSuccess]: {
type: 'string',
default: 'primitive-dot',
- markdownDescription: localize('terminal.integrated.shellIntegration.decorationIconSuccess', "Controls the icon that will be used for each command in terminals with shell integration enabled that do not have an associated exit code. Set to {0} to hide the icon or disable decorations with {1}.", '`\'\'`', '`#terminal.integrated.shellIntegration.decorationsEnabled#`')
+ markdownDescription: localize('terminal.integrated.shellIntegration.decorationIconSuccess', "Controls the icon that will be used for each command in terminals with shell integration enabled that do not have an associated exit code. Set to {0} to hide the icon or disable decorations with {1}.", '`\"\"`', '`#terminal.integrated.shellIntegration.decorationsEnabled#`')
},
[TerminalSettingId.ShellIntegrationDecorationIconError]: {
type: 'string',
default: 'error-small',
- markdownDescription: localize('terminal.integrated.shellIntegration.decorationIconError', "Controls the icon that will be used for each command in terminals with shell integration enabled that do have an associated exit code. Set to {0} to hide the icon or disable decorations with {1}.", '`\'\'`', '`#terminal.integrated.shellIntegration.decorationsEnabled#`')
+ markdownDescription: localize('terminal.integrated.shellIntegration.decorationIconError', "Controls the icon that will be used for each command in terminals with shell integration enabled that do have an associated exit code. Set to {0} to hide the icon or disable decorations with {1}.", '`\"\"`', '`#terminal.integrated.shellIntegration.decorationsEnabled#`')
},
[TerminalSettingId.ShellIntegrationDecorationIcon]: {
type: 'string',
default: 'circle-outline',
- markdownDescription: localize('terminal.integrated.shellIntegration.decorationIcon', "Controls the icon that will be used for skipped/empty commands. Set to {0} to hide the icon or disable decorations with {1}.", '`\'\'`', '`#terminal.integrated.shellIntegration.decorationsEnabled#`')
+ markdownDescription: localize('terminal.integrated.shellIntegration.decorationIcon', "Controls the icon that will be used for skipped/empty commands. Set to {0} to hide the icon or disable decorations with {1}.", '`\"\"`', '`#terminal.integrated.shellIntegration.decorationsEnabled#`')
},
[TerminalSettingId.TabsFocusMode]: {
type: 'string',
@@ -546,15 +546,22 @@ const terminalConfiguration: IConfigurationNode = {
},
[TerminalSettingId.ShellIntegrationEnabled]: {
restricted: true,
- markdownDescription: localize('terminal.integrated.shellIntegration.enabled', "Enable features like enhanced command tracking and current working directory detection. \n\nShell integration works by injecting the shell with a startup script. The script gives VS Code insight into what is happening within the terminal.\n\nSupported shells:\n\n- Linux/macOS: bash, pwsh, zsh\n - Windows: pwsh\n\nThis setting applies only when terminals are created, so you will need to restart your terminals for it to take effect.\n\n Note that the script injection may not work if you have custom arguments defined in the terminal profile, a [complex bash `PROMPT_COMMAND`](https://code.visualstudio.com/docs/editor/integrated-terminal#_complex-bash-promptcommand), or other unsupported setup."),
+ markdownDescription: localize('terminal.integrated.shellIntegration.enabled', "Determines whether or not shell integration is auto-injected to support features like enhanced command tracking and current working directory detection. \n\nShell integration works by injecting the shell with a startup script. The script gives VS Code insight into what is happening within the terminal.\n\nSupported shells:\n\n- Linux/macOS: bash, pwsh, zsh\n - Windows: pwsh\n\nThis setting applies only when terminals are created, so you will need to restart your terminals for it to take effect.\n\n Note that the script injection may not work if you have custom arguments defined in the terminal profile, a [complex bash `PROMPT_COMMAND`](https://code.visualstudio.com/docs/editor/integrated-terminal#_complex-bash-promptcommand), or other unsupported setup. To disable decorations, see {0}", '`#terminal.integrated.shellIntegrations.decorationsEnabled#`'),
type: 'boolean',
default: true
},
[TerminalSettingId.ShellIntegrationDecorationsEnabled]: {
restricted: true,
markdownDescription: localize('terminal.integrated.shellIntegration.decorationsEnabled', "When shell integration is enabled, adds a decoration for each command."),
- type: 'boolean',
- default: true
+ type: 'string',
+ enum: ['both', 'gutter', 'overviewRuler', 'never'],
+ enumDescriptions: [
+ localize('terminal.integrated.shellIntegration.decorationsEnabled.both', "Show decorations in the gutter (left) and overview ruler (right)"),
+ localize('terminal.integrated.shellIntegration.decorationsEnabled.gutter', "Show gutter decorations to the left of the terminal"),
+ localize('terminal.integrated.shellIntegration.decorationsEnabled.overviewRuler', "Show overview ruler decorations to the right of the terminal"),
+ localize('terminal.integrated.shellIntegration.decorationsEnabled.never', "Do not show decorations"),
+ ],
+ default: 'both'
},
[TerminalSettingId.ShellIntegrationCommandHistory]: {
restricted: true,
diff --git a/src/vs/workbench/contrib/terminal/test/browser/xterm/decorationAddon.test.ts b/src/vs/workbench/contrib/terminal/test/browser/xterm/decorationAddon.test.ts
index 3ea312e1e1e..ddcbd66e3b2 100644
--- a/src/vs/workbench/contrib/terminal/test/browser/xterm/decorationAddon.test.ts
+++ b/src/vs/workbench/contrib/terminal/test/browser/xterm/decorationAddon.test.ts
@@ -37,7 +37,14 @@ suite('DecorationAddon', () => {
const instantiationService = new TestInstantiationService();
const configurationService = new TestConfigurationService({
workbench: {
- hover: { delay: 5 }
+ hover: { delay: 5 },
+ },
+ terminal: {
+ integrated: {
+ shellIntegration: {
+ decorationsEnabled: 'both'
+ }
+ }
}
});
instantiationService.stub(IThemeService, new TestThemeService());
diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts
index 66ffbef2595..2281441286a 100644
--- a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts
+++ b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts
@@ -485,7 +485,6 @@ export class TestingExplorerViewModel extends Disposable {
instantiationService.createInstance(ErrorRenderer),
],
{
- simpleKeyboardNavigation: true,
identityProvider: instantiationService.createInstance(IdentityProvider),
hideTwistiesOfChildlessElements: false,
sorter: instantiationService.createInstance(TreeSorter, this),
diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts
index 9f113cfabab..32242173816 100644
--- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts
+++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts
@@ -60,6 +60,7 @@ import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/use
import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { ctxIsMergeEditor } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor';
+import { EditorResolution } from 'vs/platform/editor/common/editor';
const CONTEXT_CONFLICTS_SOURCES = new RawContextKey<string>('conflictsSources', '');
@@ -736,7 +737,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
{ title: localize('Theirs', 'Theirs'), description: remoteResourceName, detail: undefined, uri: conflict.remoteResource },
conflict.previewResource,
);
- await this.editorService.openEditor(input);
+ await this.editorService.openEditor(input, { override: EditorResolution.DISABLED });
}
}
diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts
index 497dfb1165c..8467fc4ed88 100644
--- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts
+++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts
@@ -1193,7 +1193,7 @@ export class GettingStartedPage extends EditorPane {
if (isCommand) {
const keybindingLabel = this.getKeybindingLabel(command);
if (keybindingLabel) {
- container.appendChild($('span.shortcut-message', {}, 'Tip: Use keyboard shortcut ', $('span.keybinding', {}, keybindingLabel)));
+ container.appendChild($('span.shortcut-message', {}, localize('gettingStarted.keyboardTip', 'Tip: Use keyboard shortcut '), $('span.keybinding', {}, keybindingLabel)));
}
}
diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts
index dcbddfc4097..210f171d0a8 100644
--- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts
+++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts
@@ -364,28 +364,14 @@ export class WalkthroughsService extends Disposable implements IWalkthroughsServ
};
}
- // Legacy media config (only in use by remote-wsl at the moment)
+ // Throw error for unknown walkthrough format
else {
- const legacyMedia = step.media as unknown as { path: string; altText: string };
- if (typeof legacyMedia.path === 'string' && legacyMedia.path.endsWith('.md')) {
- media = {
- type: 'markdown',
- path: convertExtensionPathToFileURI(legacyMedia.path),
- base: convertExtensionPathToFileURI(dirname(legacyMedia.path)),
- root: FileAccess.asFileUri(extension.extensionLocation),
- };
- }
- else {
- const altText = legacyMedia.altText;
- if (altText === undefined) {
- console.error('Walkthrough item:', fullyQualifiedID, 'is missing altText for its media element.');
- }
- media = { type: 'image', altText, path: convertExtensionRelativePathsToBrowserURIs(legacyMedia.path) };
- }
+ throw new Error('Unknown walkthrough format detected for ' + fullyQualifiedID);
}
return ({
- description, media,
+ description,
+ media,
completionEvents: step.completionEvents?.filter(x => typeof x === 'string') ?? [],
id: fullyQualifiedID,
title: step.title,
diff --git a/src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts b/src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts
index 8b87c52e229..9bbe2ed1d27 100644
--- a/src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts
+++ b/src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts
@@ -45,7 +45,7 @@ registerAction2(class extends Action2 {
}
});
-type NewFileItem = { commandID: string; title: string; from: string; group: string };
+type NewFileItem = { commandID: string; title: string; from: string; group: string; commandArgs?: any };
class NewFileTemplatesManager extends Disposable {
static Instance: NewFileTemplatesManager | undefined;
@@ -162,12 +162,27 @@ class NewFileTemplatesManager extends Disposable {
disposables.add(this.menu.onDidChange(() => refreshQp(this.allEntries())));
+ disposables.add(qp.onDidChangeValue((val: string) => {
+ if (val === '') {
+ refreshQp(entries);
+ return;
+ }
+ const currentTextEntry: NewFileItem = {
+ commandID: 'workbench.action.files.newUntitledFile',
+ commandArgs: { languageId: undefined, viewType: undefined, path: val },
+ title: localize('miNewFileWithName', "New File ({0})", val),
+ group: 'file',
+ from: builtInSource,
+ };
+ refreshQp([currentTextEntry, ...entries]);
+ }));
+
disposables.add(qp.onDidAccept(async e => {
const selected = qp.selectedItems[0] as (IQuickPickItem & NewFileItem);
resolveResult(!!selected);
qp.hide();
- if (selected) { await this.commandService.executeCommand(selected.commandID); }
+ if (selected) { await this.commandService.executeCommand(selected.commandID, selected.commandArgs); }
}));
disposables.add(qp.onDidHide(() => {
diff --git a/src/vs/workbench/electron-sandbox/desktop.contribution.ts b/src/vs/workbench/electron-sandbox/desktop.contribution.ts
index f105e0ce308..1dd0c84873b 100644
--- a/src/vs/workbench/electron-sandbox/desktop.contribution.ts
+++ b/src/vs/workbench/electron-sandbox/desktop.contribution.ts
@@ -310,10 +310,6 @@ import { ModifierKeyEmitter } from 'vs/base/browser/dom';
type: 'boolean',
description: localize('argv.disableHardwareAcceleration', 'Disables hardware acceleration. ONLY change this option if you encounter graphic issues.')
},
- 'disable-color-correct-rendering': {
- type: 'boolean',
- description: localize('argv.disableColorCorrectRendering', 'Resolves issues around color profile selection. ONLY change this option if you encounter graphic issues.')
- },
'force-color-profile': {
type: 'string',
markdownDescription: localize('argv.forceColorProfile', 'Allows to override the color profile to use. If you experience colors appear badly, try to set this to `srgb` and restart.')
diff --git a/src/vs/workbench/electron-sandbox/desktop.main.ts b/src/vs/workbench/electron-sandbox/desktop.main.ts
index 4986c87d42a..6d4f75c2f4b 100644
--- a/src/vs/workbench/electron-sandbox/desktop.main.ts
+++ b/src/vs/workbench/electron-sandbox/desktop.main.ts
@@ -91,7 +91,7 @@ export class DesktopMain extends Disposable {
// Files
const filesToWait = this.configuration.filesToWait;
const filesToWaitPaths = filesToWait?.paths;
- for (const paths of [filesToWaitPaths, this.configuration.filesToOpenOrCreate, this.configuration.filesToDiff]) {
+ for (const paths of [filesToWaitPaths, this.configuration.filesToOpenOrCreate, this.configuration.filesToDiff, this.configuration.filesToMerge]) {
if (Array.isArray(paths)) {
for (const path of paths) {
if (path.fileUri) {
diff --git a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts
index 7e3dd0559f3..4720f73a32a 100644
--- a/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts
+++ b/src/vs/workbench/electron-sandbox/parts/titlebar/titlebarPart.ts
@@ -11,7 +11,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { isMacintosh, isWindows, isLinux } from 'vs/base/common/platform';
-import { IMenuService } from 'vs/platform/actions/common/actions';
+import { IMenuService, MenuId } from 'vs/platform/actions/common/actions';
import { TitlebarPart as BrowserTitleBarPart } from 'vs/workbench/browser/parts/titlebar/titlebarPart';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IThemeService } from 'vs/platform/theme/common/themeService';
@@ -43,7 +43,8 @@ export class TitlebarPart extends BrowserTitleBarPart {
if (!isMacintosh) {
return super.minimumHeight;
}
- return (this.isCommandCenterVisible ? 35 : this.getMacTitlebarSize()) / getZoomFactor();
+
+ return (this.isCommandCenterVisible ? 35 : this.getMacTitlebarSize()) / (this.useCounterZoom ? getZoomFactor() : 1);
}
override get maximumHeight(): number { return this.minimumHeight; }
@@ -197,6 +198,19 @@ export class TitlebarPart extends BrowserTitleBarPart {
this.onDidChangeWindowMaximized(this.layoutService.isWindowMaximized());
}
+ // Window System Context Menu
+ // See https://github.com/electron/electron/issues/24893
+ if (isWindows && getTitleBarStyle(this.configurationService) === 'custom') {
+ this._register(this.nativeHostService.onDidTriggerSystemContextMenu(({ windowId, x, y }) => {
+ if (this.nativeHostService.windowId !== windowId) {
+ return;
+ }
+
+ const zoomFactor = getZoomFactor();
+ this.onContextMenu(new MouseEvent('mouseup', { clientX: x / zoomFactor, clientY: y / zoomFactor }), MenuId.TitleBarContext);
+ }));
+ }
+
return ret;
}
diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts
index af2e33d72d2..f044363676a 100644
--- a/src/vs/workbench/electron-sandbox/window.ts
+++ b/src/vs/workbench/electron-sandbox/window.ts
@@ -10,7 +10,7 @@ import { equals } from 'vs/base/common/objects';
import { EventType, EventHelper, addDisposableListener, scheduleAtNextAnimationFrame, ModifierKeyEmitter } from 'vs/base/browser/dom';
import { Separator, WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from 'vs/base/common/actions';
import { IFileService } from 'vs/platform/files/common/files';
-import { EditorResourceAccessor, IUntitledTextResourceEditorInput, SideBySideEditor, pathsToEditors, IResourceDiffEditorInput, IUntypedEditorInput, IEditorPane } from 'vs/workbench/common/editor';
+import { EditorResourceAccessor, IUntitledTextResourceEditorInput, SideBySideEditor, pathsToEditors, IResourceDiffEditorInput, IUntypedEditorInput, IEditorPane, isResourceEditorInput, IResourceMergeEditorInput } from 'vs/workbench/common/editor';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { WindowMinimumSize, IOpenFileRequest, IWindowsConfiguration, getTitleBarStyle, IAddFoldersRequest, INativeRunActionInWindowRequest, INativeRunKeybindingInWindowRequest, INativeOpenFileRequest } from 'vs/platform/window/common/window';
@@ -603,7 +603,7 @@ export class NativeWindow extends Disposable {
const commandId = `workbench.action.revealPathInFinder${i}`;
this.customTitleContextMenuDisposable.add(CommandsRegistry.registerCommand(commandId, () => this.nativeHostService.showItemInFolder(path.fsPath)));
- this.customTitleContextMenuDisposable.add(MenuRegistry.appendMenuItem(MenuId.TitleBarContext, { command: { id: commandId, title: label || posix.sep }, order: -i }));
+ this.customTitleContextMenuDisposable.add(MenuRegistry.appendMenuItem(MenuId.TitleBarTitleContext, { command: { id: commandId, title: label || posix.sep }, order: -i }));
}
}
@@ -819,19 +819,12 @@ export class NativeWindow extends Disposable {
}
private async onOpenFiles(request: INativeOpenFileRequest): Promise<void> {
- const inputs: Array<IResourceEditorInput | IUntitledTextResourceEditorInput> = [];
const diffMode = !!(request.filesToDiff && (request.filesToDiff.length === 2));
+ const mergeMode = !!(request.filesToMerge && (request.filesToMerge.length === 4));
- if (!diffMode && request.filesToOpenOrCreate) {
- inputs.push(...(await pathsToEditors(request.filesToOpenOrCreate, this.fileService)));
- }
-
- if (diffMode && request.filesToDiff) {
- inputs.push(...(await pathsToEditors(request.filesToDiff, this.fileService)));
- }
-
+ const inputs = await pathsToEditors(mergeMode ? request.filesToMerge : diffMode ? request.filesToDiff : request.filesToOpenOrCreate, this.fileService);
if (inputs.length) {
- const openedEditorPanes = await this.openResources(inputs, diffMode);
+ const openedEditorPanes = await this.openResources(inputs, diffMode, mergeMode);
if (request.filesToWait) {
@@ -860,11 +853,19 @@ export class NativeWindow extends Disposable {
await this.fileService.del(waitMarkerFile);
}
- private async openResources(resources: Array<IResourceEditorInput | IUntitledTextResourceEditorInput>, diffMode: boolean): Promise<readonly IEditorPane[]> {
+ private async openResources(resources: Array<IResourceEditorInput | IUntitledTextResourceEditorInput>, diffMode: boolean, mergeMode: boolean): Promise<readonly IEditorPane[]> {
const editors: IUntypedEditorInput[] = [];
- // In diffMode we open 2 resources as diff
- if (diffMode && resources.length === 2 && resources[0].resource && resources[1].resource) {
+ if (mergeMode && isResourceEditorInput(resources[0]) && isResourceEditorInput(resources[1]) && isResourceEditorInput(resources[2]) && isResourceEditorInput(resources[3])) {
+ const mergeEditor: IResourceMergeEditorInput = {
+ input1: { resource: resources[0].resource },
+ input2: { resource: resources[1].resource },
+ base: { resource: resources[2].resource },
+ result: { resource: resources[3].resource },
+ options: { pinned: true, override: 'mergeEditor.Input' } // TODO@bpasero remove the override once the resolver is ready
+ };
+ editors.push(mergeEditor);
+ } else if (diffMode && isResourceEditorInput(resources[0]) && isResourceEditorInput(resources[1])) {
const diffEditor: IResourceDiffEditorInput = {
original: { resource: resources[0].resource },
modified: { resource: resources[1].resource },
@@ -875,7 +876,6 @@ export class NativeWindow extends Disposable {
editors.push(...resources);
}
- // Open as editors
return this.editorService.openEditors(editors, undefined, { validateTrust: true });
}
}
diff --git a/src/vs/workbench/services/configuration/browser/configurationService.ts b/src/vs/workbench/services/configuration/browser/configurationService.ts
index bba9911a158..3dc4ea6419e 100644
--- a/src/vs/workbench/services/configuration/browser/configurationService.ts
+++ b/src/vs/workbench/services/configuration/browser/configurationService.ts
@@ -833,6 +833,10 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat
const defaultDelta = delta(defaultRestrictedSettings, this._restrictedSettings.default, (a, b) => a.localeCompare(b));
changed.push(...defaultDelta.added, ...defaultDelta.removed);
+ const application = (this.applicationConfiguration?.getRestrictedSettings() || []).sort((a, b) => a.localeCompare(b));
+ const applicationDelta = delta(application, this._restrictedSettings.application || [], (a, b) => a.localeCompare(b));
+ changed.push(...applicationDelta.added, ...applicationDelta.removed);
+
const userLocal = this.localUserConfiguration.getRestrictedSettings().sort((a, b) => a.localeCompare(b));
const userLocalDelta = delta(userLocal, this._restrictedSettings.userLocal || [], (a, b) => a.localeCompare(b));
changed.push(...userLocalDelta.added, ...userLocalDelta.removed);
@@ -861,6 +865,7 @@ export class WorkspaceService extends Disposable implements IWorkbenchConfigurat
if (changed.length) {
this._restrictedSettings = {
default: defaultRestrictedSettings,
+ application: application.length ? application : undefined,
userLocal: userLocal.length ? userLocal : undefined,
userRemote: userRemote.length ? userRemote : undefined,
workspace: workspace.length ? workspace : undefined,
diff --git a/src/vs/workbench/services/configuration/common/configuration.ts b/src/vs/workbench/services/configuration/common/configuration.ts
index 0b625da7565..26e00008c87 100644
--- a/src/vs/workbench/services/configuration/common/configuration.ts
+++ b/src/vs/workbench/services/configuration/common/configuration.ts
@@ -53,6 +53,7 @@ export interface IConfigurationCache {
export type RestrictedSettings = {
default: ReadonlyArray<string>;
+ application?: ReadonlyArray<string>;
userLocal?: ReadonlyArray<string>;
userRemote?: ReadonlyArray<string>;
workspace?: ReadonlyArray<string>;
diff --git a/src/vs/workbench/services/editor/browser/editorResolverService.ts b/src/vs/workbench/services/editor/browser/editorResolverService.ts
index 21ea8e0dcac..666798d3bea 100644
--- a/src/vs/workbench/services/editor/browser/editorResolverService.ts
+++ b/src/vs/workbench/services/editor/browser/editorResolverService.ts
@@ -10,11 +10,11 @@ import { basename, extname, isEqual } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { EditorActivation, EditorResolution, IEditorOptions } from 'vs/platform/editor/common/editor';
-import { DEFAULT_EDITOR_ASSOCIATION, EditorResourceAccessor, EditorInputWithOptions, IResourceSideBySideEditorInput, isEditorInputWithOptions, isEditorInputWithOptionsAndGroup, isResourceDiffEditorInput, isResourceSideBySideEditorInput, isUntitledResourceEditorInput, IUntypedEditorInput, SideBySideEditor } from 'vs/workbench/common/editor';
+import { DEFAULT_EDITOR_ASSOCIATION, EditorResourceAccessor, EditorInputWithOptions, IResourceSideBySideEditorInput, isEditorInputWithOptions, isEditorInputWithOptionsAndGroup, isResourceDiffEditorInput, isResourceSideBySideEditorInput, isUntitledResourceEditorInput, isResourceMergeEditorInput, IUntypedEditorInput, SideBySideEditor } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { Schemas } from 'vs/base/common/network';
-import { RegisteredEditorInfo, RegisteredEditorPriority, RegisteredEditorOptions, DiffEditorInputFactoryFunction, EditorAssociation, EditorAssociations, EditorInputFactoryFunction, editorsAssociationsSettingId, globMatchesResource, IEditorResolverService, priorityToRank, ResolvedEditor, ResolvedStatus, UntitledEditorInputFactoryFunction } from 'vs/workbench/services/editor/common/editorResolverService';
+import { RegisteredEditorInfo, RegisteredEditorPriority, RegisteredEditorOptions, DiffEditorInputFactoryFunction, EditorAssociation, EditorAssociations, EditorInputFactoryFunction, editorsAssociationsSettingId, globMatchesResource, IEditorResolverService, priorityToRank, ResolvedEditor, ResolvedStatus, UntitledEditorInputFactoryFunction, MergeEditorInputFactoryFunction } from 'vs/workbench/services/editor/common/editorResolverService';
import { IKeyMods, IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
import { localize } from 'vs/nls';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
@@ -34,8 +34,9 @@ interface RegisteredEditor {
editorInfo: RegisteredEditorInfo;
options?: RegisteredEditorOptions;
createEditorInput: EditorInputFactoryFunction;
- createUntitledEditorInput?: UntitledEditorInputFactoryFunction | undefined;
+ createUntitledEditorInput?: UntitledEditorInputFactoryFunction;
createDiffEditorInput?: DiffEditorInputFactoryFunction;
+ createMergeEditorInput?: MergeEditorInputFactoryFunction;
}
type RegisteredEditors = Array<RegisteredEditor>;
@@ -244,8 +245,9 @@ export class EditorResolverService extends Disposable implements IEditorResolver
editorInfo: RegisteredEditorInfo,
options: RegisteredEditorOptions,
createEditorInput: EditorInputFactoryFunction,
- createUntitledEditorInput?: UntitledEditorInputFactoryFunction | undefined,
- createDiffEditorInput?: DiffEditorInputFactoryFunction
+ createUntitledEditorInput?: UntitledEditorInputFactoryFunction,
+ createDiffEditorInput?: DiffEditorInputFactoryFunction,
+ createMergeEditorInput?: MergeEditorInputFactoryFunction
): IDisposable {
let registeredEditor = this._editors.get(globPattern);
if (registeredEditor === undefined) {
@@ -258,7 +260,8 @@ export class EditorResolverService extends Disposable implements IEditorResolver
options,
createEditorInput,
createUntitledEditorInput,
- createDiffEditorInput
+ createDiffEditorInput,
+ createMergeEditorInput
});
this._onDidChangeEditorRegistrations.fire();
return toDisposable(() => {
@@ -435,6 +438,15 @@ export class EditorResolverService extends Disposable implements IEditorResolver
options = { ...options, activation: options.preserveFocus ? EditorActivation.RESTORE : undefined };
}
+ // If it's a merge editor we trigger the create merge editor input
+ if (isResourceMergeEditorInput(editor)) {
+ if (!selectedEditor.createMergeEditorInput) {
+ return;
+ }
+ const inputWithOptions = await selectedEditor.createMergeEditorInput(editor, group);
+ return { editor: inputWithOptions.editor, options: inputWithOptions.options ?? options };
+ }
+
// If it's a diff editor we trigger the create diff editor input
if (isResourceDiffEditorInput(editor)) {
if (!selectedEditor.createDiffEditorInput) {
diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts
index c31eab43d77..4a2b8ea5e9d 100644
--- a/src/vs/workbench/services/editor/browser/editorService.ts
+++ b/src/vs/workbench/services/editor/browser/editorService.ts
@@ -5,10 +5,10 @@
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IResourceEditorInput, IEditorOptions, EditorActivation, EditorResolution, IResourceEditorInputIdentifier, ITextResourceEditorInput } from 'vs/platform/editor/common/editor';
-import { SideBySideEditor, IEditorPane, GroupIdentifier, IUntitledTextResourceEditorInput, IResourceDiffEditorInput, EditorInputWithOptions, isEditorInputWithOptions, IEditorIdentifier, IEditorCloseEvent, ITextDiffEditorPane, IRevertOptions, SaveReason, EditorsOrder, IWorkbenchEditorConfiguration, EditorResourceAccessor, IVisibleEditorPane, EditorInputCapabilities, isResourceDiffEditorInput, IUntypedEditorInput, isResourceEditorInput, isEditorInput, isEditorInputWithOptionsAndGroup, IFindEditorOptions } from 'vs/workbench/common/editor';
+import { SideBySideEditor, IEditorPane, GroupIdentifier, IUntitledTextResourceEditorInput, IResourceDiffEditorInput, EditorInputWithOptions, isEditorInputWithOptions, IEditorIdentifier, IEditorCloseEvent, ITextDiffEditorPane, IRevertOptions, SaveReason, EditorsOrder, IWorkbenchEditorConfiguration, EditorResourceAccessor, IVisibleEditorPane, EditorInputCapabilities, isResourceDiffEditorInput, IUntypedEditorInput, isResourceEditorInput, isEditorInput, isEditorInputWithOptionsAndGroup, IFindEditorOptions, isResourceMergeEditorInput } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput';
-import { ResourceMap } from 'vs/base/common/map';
+import { ResourceMap, ResourceSet } from 'vs/base/common/map';
import { IFileService, FileOperationEvent, FileOperation, FileChangesEvent, FileChangeType } from 'vs/platform/files/common/files';
import { Event, Emitter } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
@@ -176,7 +176,7 @@ export class EditorService extends Disposable implements EditorServiceImpl {
private readonly activeOutOfWorkspaceWatchers = new ResourceMap<IDisposable>();
private handleVisibleEditorsChange(): void {
- const visibleOutOfWorkspaceResources = new ResourceMap<URI>();
+ const visibleOutOfWorkspaceResources = new ResourceSet();
for (const editor of this.visibleEditors) {
const resources = distinct(coalesce([
@@ -186,14 +186,14 @@ export class EditorService extends Disposable implements EditorServiceImpl {
for (const resource of resources) {
if (this.fileService.hasProvider(resource) && !this.contextService.isInsideWorkspace(resource)) {
- visibleOutOfWorkspaceResources.set(resource, resource);
+ visibleOutOfWorkspaceResources.add(resource);
}
}
}
// Handle no longer visible out of workspace resources
for (const resource of this.activeOutOfWorkspaceWatchers.keys()) {
- if (!visibleOutOfWorkspaceResources.get(resource)) {
+ if (!visibleOutOfWorkspaceResources.has(resource)) {
dispose(this.activeOutOfWorkspaceWatchers.get(resource));
this.activeOutOfWorkspaceWatchers.delete(resource);
}
@@ -605,23 +605,24 @@ export class EditorService extends Disposable implements EditorServiceImpl {
}
private async handleWorkspaceTrust(editors: Array<EditorInputWithOptions | IUntypedEditorInput>): Promise<boolean> {
- const { resources, diffMode } = this.extractEditorResources(editors);
+ const { resources, diffMode, mergeMode } = this.extractEditorResources(editors);
const trustResult = await this.workspaceTrustRequestService.requestOpenFilesTrust(resources);
switch (trustResult) {
case WorkspaceTrustUriResponse.Open:
return true;
case WorkspaceTrustUriResponse.OpenInNewWindow:
- await this.hostService.openWindow(resources.map(resource => ({ fileUri: resource })), { forceNewWindow: true, diffMode });
+ await this.hostService.openWindow(resources.map(resource => ({ fileUri: resource })), { forceNewWindow: true, diffMode, mergeMode });
return false;
case WorkspaceTrustUriResponse.Cancel:
return false;
}
}
- private extractEditorResources(editors: Array<EditorInputWithOptions | IUntypedEditorInput>): { resources: URI[]; diffMode?: boolean } {
- const resources = new ResourceMap<boolean>();
+ private extractEditorResources(editors: Array<EditorInputWithOptions | IUntypedEditorInput>): { resources: URI[]; diffMode?: boolean; mergeMode?: boolean } {
+ const resources = new ResourceSet();
let diffMode = false;
+ let mergeMode = false;
for (const editor of editors) {
@@ -629,14 +630,14 @@ export class EditorService extends Disposable implements EditorServiceImpl {
if (isEditorInputWithOptions(editor)) {
const resource = EditorResourceAccessor.getOriginalUri(editor.editor, { supportSideBySide: SideBySideEditor.BOTH });
if (URI.isUri(resource)) {
- resources.set(resource, true);
+ resources.add(resource);
} else if (resource) {
if (resource.primary) {
- resources.set(resource.primary, true);
+ resources.add(resource.primary);
}
if (resource.secondary) {
- resources.set(resource.secondary, true);
+ resources.add(resource.secondary);
}
diffMode = editor.editor instanceof DiffEditorInput;
@@ -645,27 +646,44 @@ export class EditorService extends Disposable implements EditorServiceImpl {
// Untyped editor
else {
- if (isResourceDiffEditorInput(editor)) {
- const originalResourceEditor = editor.original;
- if (URI.isUri(originalResourceEditor.resource)) {
- resources.set(originalResourceEditor.resource, true);
+ if (isResourceMergeEditorInput(editor)) {
+ if (URI.isUri(editor.input1)) {
+ resources.add(editor.input1.resource);
}
- const modifiedResourceEditor = editor.modified;
- if (URI.isUri(modifiedResourceEditor.resource)) {
- resources.set(modifiedResourceEditor.resource, true);
+ if (URI.isUri(editor.input2)) {
+ resources.add(editor.input2.resource);
+ }
+
+ if (URI.isUri(editor.base)) {
+ resources.add(editor.base.resource);
+ }
+
+ if (URI.isUri(editor.result)) {
+ resources.add(editor.result.resource);
+ }
+
+ mergeMode = true;
+ } if (isResourceDiffEditorInput(editor)) {
+ if (URI.isUri(editor.original.resource)) {
+ resources.add(editor.original.resource);
+ }
+
+ if (URI.isUri(editor.modified.resource)) {
+ resources.add(editor.modified.resource);
}
diffMode = true;
} else if (isResourceEditorInput(editor)) {
- resources.set(editor.resource, true);
+ resources.add(editor.resource);
}
}
}
return {
resources: Array.from(resources.keys()),
- diffMode
+ diffMode,
+ mergeMode
};
}
diff --git a/src/vs/workbench/services/editor/common/editorResolverService.ts b/src/vs/workbench/services/editor/common/editorResolverService.ts
index 7cf047a1297..57f3e2c1508 100644
--- a/src/vs/workbench/services/editor/common/editorResolverService.ts
+++ b/src/vs/workbench/services/editor/common/editorResolverService.ts
@@ -16,7 +16,7 @@ import { Extensions as ConfigurationExtensions, IConfigurationNode, IConfigurati
import { IResourceEditorInput, ITextResourceEditorInput } from 'vs/platform/editor/common/editor';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Registry } from 'vs/platform/registry/common/platform';
-import { EditorInputWithOptions, EditorInputWithOptionsAndGroup, IResourceDiffEditorInput, IUntitledTextResourceEditorInput, IUntypedEditorInput } from 'vs/workbench/common/editor';
+import { EditorInputWithOptions, EditorInputWithOptionsAndGroup, IResourceDiffEditorInput, IResourceMergeEditorInput, IUntitledTextResourceEditorInput, IUntypedEditorInput } from 'vs/workbench/common/editor';
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
import { PreferredGroup } from 'vs/workbench/services/editor/common/editorService';
@@ -111,6 +111,8 @@ export type UntitledEditorInputFactoryFunction = (untitledEditorInput: IUntitled
export type DiffEditorInputFactoryFunction = (diffEditorInput: IResourceDiffEditorInput, group: IEditorGroup) => EditorInputFactoryResult;
+export type MergeEditorInputFactoryFunction = (mergeEditorInput: IResourceMergeEditorInput, group: IEditorGroup) => EditorInputFactoryResult;
+
export interface IEditorResolverService {
readonly _serviceBrand: undefined;
/**
@@ -144,8 +146,9 @@ export interface IEditorResolverService {
editorInfo: RegisteredEditorInfo,
options: RegisteredEditorOptions,
createEditorInput: EditorInputFactoryFunction,
- createUntitledEditorInput?: UntitledEditorInputFactoryFunction | undefined,
- createDiffEditorInput?: DiffEditorInputFactoryFunction
+ createUntitledEditorInput?: UntitledEditorInputFactoryFunction,
+ createDiffEditorInput?: DiffEditorInputFactoryFunction,
+ createMergeEditorInput?: MergeEditorInputFactoryFunction
): IDisposable;
/**
diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts
index 82a2541576a..122eba1dec7 100644
--- a/src/vs/workbench/services/environment/browser/environmentService.ts
+++ b/src/vs/workbench/services/environment/browser/environmentService.ts
@@ -332,6 +332,26 @@ export class BrowserWorkbenchEnvironmentService implements IBrowserWorkbenchEnvi
return undefined;
}
+
+ @memoize
+ get filesToMerge(): IPath[] | undefined {
+ if (this.payload) {
+ const fileToMerge1 = this.payload.get('mergeFile1');
+ const fileToMerge2 = this.payload.get('mergeFile2');
+ const fileToMergeBase = this.payload.get('mergeFileBase');
+ const fileToMergeResult = this.payload.get('mergeFileResult');
+ if (fileToMerge1 && fileToMerge2 && fileToMergeBase && fileToMergeResult) {
+ return [
+ { fileUri: URI.parse(fileToMerge1) },
+ { fileUri: URI.parse(fileToMerge2) },
+ { fileUri: URI.parse(fileToMergeBase) },
+ { fileUri: URI.parse(fileToMergeResult) }
+ ];
+ }
+ }
+
+ return undefined;
+ }
}
interface IExtensionHostDebugEnvironment {
diff --git a/src/vs/workbench/services/environment/common/environmentService.ts b/src/vs/workbench/services/environment/common/environmentService.ts
index d9e2e3ba65e..de37a985f17 100644
--- a/src/vs/workbench/services/environment/common/environmentService.ts
+++ b/src/vs/workbench/services/environment/common/environmentService.ts
@@ -44,6 +44,7 @@ export interface IWorkbenchEnvironmentService extends IEnvironmentService {
// --- Editors to open
readonly filesToOpenOrCreate?: IPath[] | undefined;
readonly filesToDiff?: IPath[] | undefined;
+ readonly filesToMerge?: IPath[] | undefined;
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// NOTE: KEEP THIS INTERFACE AS SMALL AS POSSIBLE. AS SUCH:
diff --git a/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts b/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts
index f675b6800ed..a18fa598d41 100644
--- a/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts
+++ b/src/vs/workbench/services/environment/electron-sandbox/environmentService.ts
@@ -128,6 +128,9 @@ export class NativeWorkbenchEnvironmentService extends AbstractNativeEnvironment
get filesToDiff(): IPath[] | undefined { return this.configuration.filesToDiff; }
@memoize
+ get filesToMerge(): IPath[] | undefined { return this.configuration.filesToMerge; }
+
+ @memoize
get filesToWait(): IPathsToWaitFor | undefined { return this.configuration.filesToWait; }
constructor(
diff --git a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts
index 41645b62f84..fa54d7d9ecd 100644
--- a/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts
+++ b/src/vs/workbench/services/extensions/browser/extensionUrlHandler.ts
@@ -83,7 +83,8 @@ export interface ExtensionUrlHandlerEvent {
export interface ExtensionUrlHandlerClassification extends GDPRClassification<ExtensionUrlHandlerEvent> {
owner: 'joaomoreno';
- readonly extensionId: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight' };
+ readonly extensionId: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The ID of the extension that should handle the URI' };
+ comment: 'This is used to understand the drop funnel of extension URI handling by the OS & VS Code.';
}
/**
diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts
index 61a3cfd5de2..24259163505 100644
--- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts
+++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts
@@ -1228,10 +1228,10 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
type ExtensionsMessageClassification = {
owner: 'alexdima';
comment: 'A validation message for an extension';
- type: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true };
- extensionId: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
- extensionPointId: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
- message: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
+ type: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Severity of problem.'; isMeasurement: true };
+ extensionId: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The identifier of the extension that has a problem.' };
+ extensionPointId: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The extension point that has a problem.' };
+ message: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The message of the problem.' };
};
type ExtensionsMessageEvent = {
type: Severity;
@@ -1304,8 +1304,8 @@ export abstract class AbstractExtensionService extends Disposable implements IEx
type ExtensionActivationErrorClassification = {
owner: 'alexdima';
comment: 'An extension failed to activate';
- extensionId: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
- error: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth' };
+ extensionId: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The identifier of the extension.' };
+ error: { classification: 'CallstackOrException'; purpose: 'PerformanceAndHealth'; comment: 'The error message.' };
};
type ExtensionActivationErrorEvent = {
extensionId: string;
diff --git a/src/vs/workbench/services/extensions/common/extensionHostManager.ts b/src/vs/workbench/services/extensions/common/extensionHostManager.ts
index 7366addd3dd..2aceff7689b 100644
--- a/src/vs/workbench/services/extensions/common/extensionHostManager.ts
+++ b/src/vs/workbench/services/extensions/common/extensionHostManager.ts
@@ -66,12 +66,12 @@ export function createExtensionHostManager(instantiationService: IInstantiationS
export type ExtensionHostStartupClassification = {
owner: 'alexdima';
comment: 'The startup state of the extension host';
- time: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
- action: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
- kind: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
- errorName?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
- errorMessage?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
- errorStack?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
+ time: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The time reported by Date.now().' };
+ action: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The action: starting, success or error.' };
+ kind: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The extension host kind: LocalProcess, LocalWebWorker or Remote.' };
+ errorName?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The error name.' };
+ errorMessage?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The error message.' };
+ errorStack?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The error stack.' };
};
export type ExtensionHostStartupEvent = {
diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts
index 17ab37e841f..ca5260d65af 100644
--- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts
+++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts
@@ -52,6 +52,7 @@ export const allApiProposals = Object.freeze({
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',
terminalDataWriteEvent: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalDataWriteEvent.d.ts',
@@ -59,7 +60,6 @@ export const allApiProposals = Object.freeze({
terminalExitReason: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalExitReason.d.ts',
testCoverage: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testCoverage.d.ts',
testObserver: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testObserver.d.ts',
- textEditorDrop: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textEditorDrop.d.ts',
textSearchProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.textSearchProvider.d.ts',
timeline: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.timeline.d.ts',
tokenInformation: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tokenInformation.d.ts',
diff --git a/src/vs/workbench/services/extensions/electron-sandbox/electronExtensionService.ts b/src/vs/workbench/services/extensions/electron-sandbox/electronExtensionService.ts
index 649e83699e8..34cb13522aa 100644
--- a/src/vs/workbench/services/extensions/electron-sandbox/electronExtensionService.ts
+++ b/src/vs/workbench/services/extensions/electron-sandbox/electronExtensionService.ts
@@ -304,9 +304,9 @@ export abstract class ElectronExtensionService extends AbstractExtensionService
type ExtensionHostCrashClassification = {
owner: 'alexdima';
comment: 'The extension host has terminated unexpectedly';
- code: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
- signal: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
- extensionIds: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
+ code: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The exit code of the extension host process.' };
+ signal: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The signal that caused the extension host process to exit.' };
+ extensionIds: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The list of loaded extensions.' };
};
type ExtensionHostCrashEvent = {
code: number;
@@ -323,9 +323,9 @@ export abstract class ElectronExtensionService extends AbstractExtensionService
type ExtensionHostCrashExtensionClassification = {
owner: 'alexdima';
comment: 'The extension host has terminated unexpectedly';
- code: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
- signal: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
- extensionId: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' };
+ code: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The exit code of the extension host process.' };
+ signal: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The signal that caused the extension host process to exit.' };
+ extensionId: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The identifier of the extension.' };
};
type ExtensionHostCrashExtensionEvent = {
code: number;
diff --git a/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts b/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts
index eaa0069f098..88c83a94c72 100644
--- a/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts
+++ b/src/vs/workbench/services/extensions/test/browser/extensionService.test.ts
@@ -29,12 +29,11 @@ import { ExtensionManifestPropertiesService, IExtensionManifestPropertiesService
import { ExtensionHostKind, ExtensionRunningLocation, IExtensionHost, IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
-import { TestEnvironmentService, TestFileService, TestLifecycleService, TestRemoteAgentService, TestWebExtensionsScannerService, TestWorkbenchExtensionEnablementService, TestWorkbenchExtensionManagementService } from 'vs/workbench/test/browser/workbenchTestServices';
+import { TestEnvironmentService, TestFileService, TestLifecycleService, TestRemoteAgentService, TestUserDataProfileService, TestWebExtensionsScannerService, TestWorkbenchExtensionEnablementService, TestWorkbenchExtensionManagementService } from 'vs/workbench/test/browser/workbenchTestServices';
import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices';
import { mock } from 'vs/base/test/common/mock';
import { IExtensionHostManager } from 'vs/workbench/services/extensions/common/extensionHostManager';
import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
-import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService';
import { IUserDataProfilesService, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
import { UriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentityService';
@@ -182,7 +181,7 @@ suite('ExtensionService', () => {
[IEnvironmentService, TestEnvironmentService],
[IWorkspaceTrustEnablementService, WorkspaceTrustEnablementService],
[IUserDataProfilesService, UserDataProfilesService],
- [IUserDataProfileService, UserDataProfileService],
+ [IUserDataProfileService, TestUserDataProfileService],
[IUriIdentityService, UriIdentityService],
]);
extService = <MyTestExtensionService>instantiationService.get(IExtensionService);
diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts
index 955ba20bddf..0aebd31d5b9 100644
--- a/src/vs/workbench/services/host/browser/browserHostService.ts
+++ b/src/vs/workbench/services/host/browser/browserHostService.ts
@@ -10,7 +10,7 @@ import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWindowSettings, IWindowOpenable, IOpenWindowOptions, isFolderToOpen, isWorkspaceToOpen, isFileToOpen, IOpenEmptyWindowOptions, IPathData, IFileToOpen, IWorkspaceToOpen, IFolderToOpen } from 'vs/platform/window/common/window';
-import { pathsToEditors } from 'vs/workbench/common/editor';
+import { isResourceEditorInput, pathsToEditors } from 'vs/workbench/common/editor';
import { whenEditorClosed } from 'vs/workbench/browser/editor';
import { IFileService } from 'vs/platform/files/common/files';
import { ILabelService } from 'vs/platform/label/common/label';
@@ -254,10 +254,40 @@ export class BrowserHostService extends Disposable implements IHostService {
this.withServices(async accessor => {
const editorService = accessor.get(IEditorService);
+ // Support mergeMode
+ if (options?.mergeMode && fileOpenables.length === 4) {
+ const editors = await pathsToEditors(fileOpenables, this.fileService);
+ if (editors.length !== 4 || !isResourceEditorInput(editors[0]) || !isResourceEditorInput(editors[1]) || !isResourceEditorInput(editors[2]) || !isResourceEditorInput(editors[3])) {
+ return; // invalid resources
+ }
+
+ // Same Window: open via editor service in current window
+ if (this.shouldReuse(options, true /* file */)) {
+ editorService.openEditor({
+ input1: { resource: editors[0].resource },
+ input2: { resource: editors[1].resource },
+ base: { resource: editors[2].resource },
+ result: { resource: editors[3].resource },
+ options: { pinned: true, override: 'mergeEditor.Input' } // TODO@bpasero remove the override once the resolver is ready
+ });
+ }
+
+ // New Window: open into empty window
+ else {
+ const environment = new Map<string, string>();
+ environment.set('mergeFile1', editors[0].resource.toString());
+ environment.set('mergeFile2', editors[1].resource.toString());
+ environment.set('mergeFileBase', editors[2].resource.toString());
+ environment.set('mergeFileResult', editors[3].resource.toString());
+
+ this.doOpen(undefined, { payload: Array.from(environment.entries()) });
+ }
+ }
+
// Support diffMode
if (options?.diffMode && fileOpenables.length === 2) {
const editors = await pathsToEditors(fileOpenables, this.fileService);
- if (editors.length !== 2 || !editors[0].resource || !editors[1].resource) {
+ if (editors.length !== 2 || !isResourceEditorInput(editors[0]) || !isResourceEditorInput(editors[1])) {
return; // invalid resources
}
diff --git a/src/vs/workbench/services/request/browser/requestService.ts b/src/vs/workbench/services/request/browser/requestService.ts
index 1f1e7384bc6..14dc5332868 100644
--- a/src/vs/workbench/services/request/browser/requestService.ts
+++ b/src/vs/workbench/services/request/browser/requestService.ts
@@ -41,7 +41,7 @@ export class BrowserRequestService extends RequestService {
}
private _makeRemoteRequest(connection: IRemoteAgentConnection, options: IRequestOptions, token: CancellationToken): Promise<IRequestContext> {
- return connection.withChannel('request', channel => RequestChannelClient.request(channel, options, token));
+ return connection.withChannel('request', channel => new RequestChannelClient(channel).request(options, token));
}
}
diff --git a/src/vs/workbench/services/storage/test/browser/storageService.test.ts b/src/vs/workbench/services/storage/test/browser/storageService.test.ts
index 8491d3f9301..cbe4837bc8a 100644
--- a/src/vs/workbench/services/storage/test/browser/storageService.test.ts
+++ b/src/vs/workbench/services/storage/test/browser/storageService.test.ts
@@ -89,7 +89,7 @@ flakySuite('StorageService (browser specific)', () => {
disposables.clear();
});
- test('clear', () => {
+ test.skip('clear', () => { // slow test and also only ever being used as a developer action
return runWithFakedTimers({ useFakeTimers: true }, async () => {
storageService.store('bar', 'foo', StorageScope.APPLICATION, StorageTarget.MACHINE);
storageService.store('bar', 3, StorageScope.APPLICATION, StorageTarget.USER);
diff --git a/src/vs/workbench/services/textfile/common/textEditorService.ts b/src/vs/workbench/services/textfile/common/textEditorService.ts
index c8c9050875b..a4a51339630 100644
--- a/src/vs/workbench/services/textfile/common/textEditorService.ts
+++ b/src/vs/workbench/services/textfile/common/textEditorService.ts
@@ -7,7 +7,7 @@ import { Event } from 'vs/base/common/event';
import { Registry } from 'vs/platform/registry/common/platform';
import { ResourceMap } from 'vs/base/common/map';
import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
-import { IEditorFactoryRegistry, IFileEditorInput, IUntypedEditorInput, IUntypedFileEditorInput, EditorExtensions, isResourceDiffEditorInput, isResourceSideBySideEditorInput, IUntitledTextResourceEditorInput, DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor';
+import { IEditorFactoryRegistry, IFileEditorInput, IUntypedEditorInput, IUntypedFileEditorInput, EditorExtensions, isResourceDiffEditorInput, isResourceSideBySideEditorInput, IUntitledTextResourceEditorInput, DEFAULT_EDITOR_ASSOCIATION, isResourceMergeEditorInput } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { INewUntitledTextEditorOptions, IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
import { Schemas } from 'vs/base/common/network';
@@ -85,6 +85,11 @@ export class TextEditorService extends Disposable implements ITextEditorService
createTextEditor(input: IUntypedFileEditorInput): IFileEditorInput;
createTextEditor(input: IUntypedEditorInput | IUntypedFileEditorInput): EditorInput | IFileEditorInput {
+ // Merge Editor is Unsupported from here
+ if (isResourceMergeEditorInput(input)) {
+ throw new Error('Unsupported input');
+ }
+
// Diff Editor Support
if (isResourceDiffEditorInput(input)) {
const original = this.createTextEditor({ ...input.original });
diff --git a/src/vs/workbench/services/themes/browser/productIconThemeData.ts b/src/vs/workbench/services/themes/browser/productIconThemeData.ts
index 80a9e6d973b..63ca74a35cd 100644
--- a/src/vs/workbench/services/themes/browser/productIconThemeData.ts
+++ b/src/vs/workbench/services/themes/browser/productIconThemeData.ts
@@ -13,7 +13,7 @@ import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { DEFAULT_PRODUCT_ICON_THEME_SETTING_VALUE } from 'vs/workbench/services/themes/common/themeConfiguration';
import { fontIdRegex, fontWeightRegex, fontStyleRegex, fontFormatRegex } from 'vs/workbench/services/themes/common/productIconThemeSchema';
-import { isString } from 'vs/base/common/types';
+import { isObject, isString } from 'vs/base/common/types';
import { ILogService } from 'vs/platform/log/common/log';
import { IconDefinition, getIconRegistry, IconContribution, IconFontDefinition, IconFontSource } from 'vs/platform/theme/common/iconRegistry';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
@@ -132,6 +132,24 @@ export class ProductIconThemeData implements IWorkbenchProductIconTheme {
break;
}
}
+ const { iconDefinitions, iconFontDefinitions } = data;
+ if (Array.isArray(iconDefinitions) && isObject(iconFontDefinitions)) {
+ const restoredIconDefinitions = new Map<string, IconDefinition>();
+ for (const entry of iconDefinitions) {
+ const { id, fontCharacter, fontId } = entry;
+ if (isString(id) && isString(fontCharacter)) {
+ if (isString(fontId)) {
+ const iconFontDefinition = IconFontDefinition.fromJSONObject(iconFontDefinitions[fontId]);
+ if (iconFontDefinition) {
+ restoredIconDefinitions.set(id, { fontCharacter, font: { id: fontId, definition: iconFontDefinition } });
+ }
+ } else {
+ restoredIconDefinitions.set(id, { fontCharacter });
+ }
+ }
+ }
+ theme.iconThemeDocument = { iconDefinitions: restoredIconDefinitions };
+ }
return theme;
} catch (e) {
return undefined;
@@ -139,6 +157,15 @@ export class ProductIconThemeData implements IWorkbenchProductIconTheme {
}
toStorage(storageService: IStorageService) {
+ const iconDefinitions = [];
+ const iconFontDefinitions: { [id: string]: IconFontDefinition } = {};
+ for (const entry of this.iconThemeDocument.iconDefinitions.entries()) {
+ const font = entry[1].font;
+ iconDefinitions.push({ id: entry[0], fontCharacter: entry[1].fontCharacter, fontId: font?.id });
+ if (font && iconFontDefinitions[font.id] === undefined) {
+ iconFontDefinitions[font.id] = IconFontDefinition.toJSONObject(font.definition);
+ }
+ }
const data = JSON.stringify({
id: this.id,
label: this.label,
@@ -147,6 +174,8 @@ export class ProductIconThemeData implements IWorkbenchProductIconTheme {
styleSheetContent: this.styleSheetContent,
watch: this.watch,
extensionData: ExtensionData.toJSONObject(this.extensionData),
+ iconDefinitions,
+ iconFontDefinitions
});
storageService.store(ProductIconThemeData.STORAGE_KEY, data, StorageScope.PROFILE, StorageTarget.MACHINE);
}
diff --git a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts
index 3800c0fbd23..6745ad1bc49 100644
--- a/src/vs/workbench/services/themes/browser/workbenchThemeService.ts
+++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts
@@ -581,11 +581,12 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
if (!this.themeExtensionsActivated.get(key)) {
type ActivatePluginClassification = {
owner: 'aeschli';
- id: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight' };
- name: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight' };
- isBuiltin: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true };
- publisherDisplayName: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
- themeId: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight' };
+ comment: 'An event is fired when an color theme extension is first used as it provides the currently shown color theme.';
+ id: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The extension id.' };
+ name: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The extension name.' };
+ isBuiltin: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Whether the extension is a built-in extension.' };
+ publisherDisplayName: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The extension publisher id.' };
+ themeId: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The id of the theme that triggered the first extension use.' };
};
type ActivatePluginEvent = {
id: string;
diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyHistoryTracker.ts b/src/vs/workbench/services/workingCopy/common/workingCopyHistoryTracker.ts
index ed05ae2c5ff..7e3d7a7c2b4 100644
--- a/src/vs/workbench/services/workingCopy/common/workingCopyHistoryTracker.ts
+++ b/src/vs/workbench/services/workingCopy/common/workingCopyHistoryTracker.ts
@@ -193,7 +193,8 @@ export class WorkingCopyHistoryTracker extends Disposable implements IWorkbenchC
private shouldTrackHistory(resource: URI, stat: IFileStatWithMetadata): boolean {
if (
resource.scheme !== this.pathService.defaultUriScheme && // track history for all workspace resources
- resource.scheme !== Schemas.vscodeUserData // track history for all settings
+ resource.scheme !== Schemas.vscodeUserData && // track history for all settings
+ resource.scheme !== Schemas.inMemory // track history for tests that use in-memory
) {
return false; // do not support unknown resources
}
diff --git a/src/vs/workbench/services/workingCopy/test/browser/resourceWorkingCopy.test.ts b/src/vs/workbench/services/workingCopy/test/browser/resourceWorkingCopy.test.ts
index 758c611b6a5..b7f6501ab42 100644
--- a/src/vs/workbench/services/workingCopy/test/browser/resourceWorkingCopy.test.ts
+++ b/src/vs/workbench/services/workingCopy/test/browser/resourceWorkingCopy.test.ts
@@ -14,6 +14,7 @@ import { IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor';
import { ResourceWorkingCopy } from 'vs/workbench/services/workingCopy/common/resourceWorkingCopy';
import { WorkingCopyCapabilities, IWorkingCopyBackup } from 'vs/workbench/services/workingCopy/common/workingCopy';
import { DisposableStore } from 'vs/base/common/lifecycle';
+import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler';
suite('ResourceWorkingCopy', function () {
@@ -55,21 +56,23 @@ suite('ResourceWorkingCopy', function () {
});
test('orphaned tracking', async () => {
- assert.strictEqual(workingCopy.isOrphaned(), false);
+ runWithFakedTimers({}, async () => {
+ assert.strictEqual(workingCopy.isOrphaned(), false);
- let onDidChangeOrphanedPromise = Event.toPromise(workingCopy.onDidChangeOrphaned);
- accessor.fileService.notExistsSet.set(resource, true);
- accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource, type: FileChangeType.DELETED }], false));
+ let onDidChangeOrphanedPromise = Event.toPromise(workingCopy.onDidChangeOrphaned);
+ accessor.fileService.notExistsSet.set(resource, true);
+ accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource, type: FileChangeType.DELETED }], false));
- await onDidChangeOrphanedPromise;
- assert.strictEqual(workingCopy.isOrphaned(), true);
+ await onDidChangeOrphanedPromise;
+ assert.strictEqual(workingCopy.isOrphaned(), true);
- onDidChangeOrphanedPromise = Event.toPromise(workingCopy.onDidChangeOrphaned);
- accessor.fileService.notExistsSet.delete(resource);
- accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource, type: FileChangeType.ADDED }], false));
+ onDidChangeOrphanedPromise = Event.toPromise(workingCopy.onDidChangeOrphaned);
+ accessor.fileService.notExistsSet.delete(resource);
+ accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource, type: FileChangeType.ADDED }], false));
- await onDidChangeOrphanedPromise;
- assert.strictEqual(workingCopy.isOrphaned(), false);
+ await onDidChangeOrphanedPromise;
+ assert.strictEqual(workingCopy.isOrphaned(), false);
+ });
});
diff --git a/src/vs/workbench/services/workingCopy/test/browser/storedFileWorkingCopy.test.ts b/src/vs/workbench/services/workingCopy/test/browser/storedFileWorkingCopy.test.ts
index 0fce73b2ce7..4dc6c6e598b 100644
--- a/src/vs/workbench/services/workingCopy/test/browser/storedFileWorkingCopy.test.ts
+++ b/src/vs/workbench/services/workingCopy/test/browser/storedFileWorkingCopy.test.ts
@@ -17,6 +17,7 @@ import { FileChangesEvent, FileChangeType, FileOperationError, FileOperationResu
import { SaveReason, SaveSourceRegistry } from 'vs/workbench/common/editor';
import { Promises } from 'vs/base/common/async';
import { consumeReadable, consumeStream, isReadableStream } from 'vs/base/common/stream';
+import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler';
export class TestStoredFileWorkingCopyModel extends Disposable implements IStoredFileWorkingCopyModel {
@@ -126,21 +127,23 @@ suite('StoredFileWorkingCopy', function () {
});
test('orphaned tracking', async () => {
- assert.strictEqual(workingCopy.hasState(StoredFileWorkingCopyState.ORPHAN), false);
+ runWithFakedTimers({}, async () => {
+ assert.strictEqual(workingCopy.hasState(StoredFileWorkingCopyState.ORPHAN), false);
- let onDidChangeOrphanedPromise = Event.toPromise(workingCopy.onDidChangeOrphaned);
- accessor.fileService.notExistsSet.set(resource, true);
- accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource, type: FileChangeType.DELETED }], false));
+ let onDidChangeOrphanedPromise = Event.toPromise(workingCopy.onDidChangeOrphaned);
+ accessor.fileService.notExistsSet.set(resource, true);
+ accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource, type: FileChangeType.DELETED }], false));
- await onDidChangeOrphanedPromise;
- assert.strictEqual(workingCopy.hasState(StoredFileWorkingCopyState.ORPHAN), true);
+ await onDidChangeOrphanedPromise;
+ assert.strictEqual(workingCopy.hasState(StoredFileWorkingCopyState.ORPHAN), true);
- onDidChangeOrphanedPromise = Event.toPromise(workingCopy.onDidChangeOrphaned);
- accessor.fileService.notExistsSet.delete(resource);
- accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource, type: FileChangeType.ADDED }], false));
+ onDidChangeOrphanedPromise = Event.toPromise(workingCopy.onDidChangeOrphaned);
+ accessor.fileService.notExistsSet.delete(resource);
+ accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource, type: FileChangeType.ADDED }], false));
- await onDidChangeOrphanedPromise;
- assert.strictEqual(workingCopy.hasState(StoredFileWorkingCopyState.ORPHAN), false);
+ await onDidChangeOrphanedPromise;
+ assert.strictEqual(workingCopy.hasState(StoredFileWorkingCopyState.ORPHAN), false);
+ });
});
test('dirty', async () => {
@@ -294,56 +297,60 @@ suite('StoredFileWorkingCopy', function () {
});
test('resolve (with backup, preserves metadata and orphaned state)', async () => {
- await workingCopy.resolve({ contents: bufferToStream(VSBuffer.fromString('hello backup')) });
+ runWithFakedTimers({}, async () => {
+ await workingCopy.resolve({ contents: bufferToStream(VSBuffer.fromString('hello backup')) });
- const orphanedPromise = Event.toPromise(workingCopy.onDidChangeOrphaned);
+ const orphanedPromise = Event.toPromise(workingCopy.onDidChangeOrphaned);
- accessor.fileService.notExistsSet.set(resource, true);
- accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource, type: FileChangeType.DELETED }], false));
+ accessor.fileService.notExistsSet.set(resource, true);
+ accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource, type: FileChangeType.DELETED }], false));
- await orphanedPromise;
- assert.strictEqual(workingCopy.hasState(StoredFileWorkingCopyState.ORPHAN), true);
+ await orphanedPromise;
+ assert.strictEqual(workingCopy.hasState(StoredFileWorkingCopyState.ORPHAN), true);
- const backup = await workingCopy.backup(CancellationToken.None);
- await accessor.workingCopyBackupService.backup(workingCopy, backup.content, undefined, backup.meta);
+ const backup = await workingCopy.backup(CancellationToken.None);
+ await accessor.workingCopyBackupService.backup(workingCopy, backup.content, undefined, backup.meta);
- assert.strictEqual(accessor.workingCopyBackupService.hasBackupSync(workingCopy), true);
+ assert.strictEqual(accessor.workingCopyBackupService.hasBackupSync(workingCopy), true);
- workingCopy.dispose();
+ workingCopy.dispose();
- workingCopy = createWorkingCopy();
- await workingCopy.resolve();
+ workingCopy = createWorkingCopy();
+ await workingCopy.resolve();
- assert.strictEqual(workingCopy.hasState(StoredFileWorkingCopyState.ORPHAN), true);
+ assert.strictEqual(workingCopy.hasState(StoredFileWorkingCopyState.ORPHAN), true);
- const backup2 = await workingCopy.backup(CancellationToken.None);
- assert.deepStrictEqual(backup.meta, backup2.meta);
+ const backup2 = await workingCopy.backup(CancellationToken.None);
+ assert.deepStrictEqual(backup.meta, backup2.meta);
+ });
});
test('resolve (updates orphaned state accordingly)', async () => {
- await workingCopy.resolve();
-
- const orphanedPromise = Event.toPromise(workingCopy.onDidChangeOrphaned);
-
- accessor.fileService.notExistsSet.set(resource, true);
- accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource, type: FileChangeType.DELETED }], false));
+ runWithFakedTimers({}, async () => {
+ await workingCopy.resolve();
- await orphanedPromise;
- assert.strictEqual(workingCopy.hasState(StoredFileWorkingCopyState.ORPHAN), true);
+ const orphanedPromise = Event.toPromise(workingCopy.onDidChangeOrphaned);
- // resolving clears orphaned state when successful
- accessor.fileService.notExistsSet.delete(resource);
- await workingCopy.resolve({ forceReadFromFile: true });
- assert.strictEqual(workingCopy.hasState(StoredFileWorkingCopyState.ORPHAN), false);
+ accessor.fileService.notExistsSet.set(resource, true);
+ accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource, type: FileChangeType.DELETED }], false));
- // resolving adds orphaned state when fail to read
- try {
- accessor.fileService.readShouldThrowError = new FileOperationError('file not found', FileOperationResult.FILE_NOT_FOUND);
- await workingCopy.resolve();
+ await orphanedPromise;
assert.strictEqual(workingCopy.hasState(StoredFileWorkingCopyState.ORPHAN), true);
- } finally {
- accessor.fileService.readShouldThrowError = undefined;
- }
+
+ // resolving clears orphaned state when successful
+ accessor.fileService.notExistsSet.delete(resource);
+ await workingCopy.resolve({ forceReadFromFile: true });
+ assert.strictEqual(workingCopy.hasState(StoredFileWorkingCopyState.ORPHAN), false);
+
+ // resolving adds orphaned state when fail to read
+ try {
+ accessor.fileService.readShouldThrowError = new FileOperationError('file not found', FileOperationResult.FILE_NOT_FOUND);
+ await workingCopy.resolve();
+ assert.strictEqual(workingCopy.hasState(StoredFileWorkingCopyState.ORPHAN), true);
+ } finally {
+ accessor.fileService.readShouldThrowError = undefined;
+ }
+ });
});
test('resolve (FILE_NOT_MODIFIED_SINCE can be handled for resolved working copies)', async () => {
@@ -573,32 +580,34 @@ suite('StoredFileWorkingCopy', function () {
});
test('save (no errors) - save clears orphaned', async () => {
- let savedCounter = 0;
- workingCopy.onDidSave(e => {
- savedCounter++;
- });
+ runWithFakedTimers({}, async () => {
+ let savedCounter = 0;
+ workingCopy.onDidSave(e => {
+ savedCounter++;
+ });
- let saveErrorCounter = 0;
- workingCopy.onDidSaveError(() => {
- saveErrorCounter++;
- });
+ let saveErrorCounter = 0;
+ workingCopy.onDidSaveError(() => {
+ saveErrorCounter++;
+ });
- await workingCopy.resolve();
+ await workingCopy.resolve();
- // save clears orphaned
- const orphanedPromise = Event.toPromise(workingCopy.onDidChangeOrphaned);
+ // save clears orphaned
+ const orphanedPromise = Event.toPromise(workingCopy.onDidChangeOrphaned);
- accessor.fileService.notExistsSet.set(resource, true);
- accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource, type: FileChangeType.DELETED }], false));
+ accessor.fileService.notExistsSet.set(resource, true);
+ accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource, type: FileChangeType.DELETED }], false));
- await orphanedPromise;
- assert.strictEqual(workingCopy.hasState(StoredFileWorkingCopyState.ORPHAN), true);
+ await orphanedPromise;
+ assert.strictEqual(workingCopy.hasState(StoredFileWorkingCopyState.ORPHAN), true);
- await workingCopy.save({ force: true });
- assert.strictEqual(savedCounter, 1);
- assert.strictEqual(saveErrorCounter, 0);
- assert.strictEqual(workingCopy.isDirty(), false);
- assert.strictEqual(workingCopy.hasState(StoredFileWorkingCopyState.ORPHAN), false);
+ await workingCopy.save({ force: true });
+ assert.strictEqual(savedCounter, 1);
+ assert.strictEqual(saveErrorCounter, 0);
+ assert.strictEqual(workingCopy.isDirty(), false);
+ assert.strictEqual(workingCopy.hasState(StoredFileWorkingCopyState.ORPHAN), false);
+ });
});
test('save (errors)', async () => {
diff --git a/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyHistoryService.test.ts b/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyHistoryService.test.ts
index 6bf694104dd..169bbdfc8bf 100644
--- a/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyHistoryService.test.ts
+++ b/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyHistoryService.test.ts
@@ -30,12 +30,12 @@ import { firstOrDefault } from 'vs/base/common/arrays';
class TestWorkbenchEnvironmentService extends NativeWorkbenchEnvironmentService {
- constructor(private readonly testDir: string) {
- super({ ...TestNativeWindowConfiguration, 'user-data-dir': testDir }, TestProductService);
+ constructor(private readonly testDir: URI | string) {
+ super({ ...TestNativeWindowConfiguration, 'user-data-dir': URI.isUri(testDir) ? testDir.fsPath : testDir }, TestProductService);
}
override get localHistoryHome() {
- return joinPath(URI.file(this.testDir), 'History');
+ return joinPath(URI.isUri(this.testDir) ? this.testDir : URI.file(this.testDir), 'History');
}
}
@@ -45,7 +45,7 @@ export class TestWorkingCopyHistoryService extends NativeWorkingCopyHistoryServi
readonly _configurationService: TestConfigurationService;
readonly _lifecycleService: TestLifecycleService;
- constructor(testDir: string) {
+ constructor(testDir: URI | string) {
const environmentService = new TestWorkbenchEnvironmentService(testDir);
const logService = new NullLogService();
const fileService = new FileService(logService);
diff --git a/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyHistoryTracker.test.ts b/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyHistoryTracker.test.ts
index 945f8e4b6ab..931ac6cbaef 100644
--- a/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyHistoryTracker.test.ts
+++ b/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyHistoryTracker.test.ts
@@ -5,12 +5,10 @@
import * as assert from 'assert';
import { Event } from 'vs/base/common/event';
-import { flakySuite } from 'vs/base/test/common/testUtils';
import { TestContextService, TestWorkingCopy } from 'vs/workbench/test/common/workbenchTestServices';
-import { getRandomTestPath } from 'vs/base/test/node/testUtils';
+import { randomPath } from 'vs/base/common/extpath';
import { tmpdir } from 'os';
import { join } from 'vs/base/common/path';
-import { Promises } from 'vs/base/node/pfs';
import { URI } from 'vs/base/common/uri';
import { TestWorkingCopyHistoryService } from 'vs/workbench/services/workingCopy/test/electron-browser/workingCopyHistoryService.test';
import { WorkingCopyHistoryTracker } from 'vs/workbench/services/workingCopy/common/workingCopyHistoryTracker';
@@ -28,22 +26,26 @@ import { TestNotificationService } from 'vs/platform/notification/test/common/te
import { CancellationToken } from 'vs/base/common/cancellation';
import { IWorkingCopyHistoryEntry, IWorkingCopyHistoryEntryDescriptor } from 'vs/workbench/services/workingCopy/common/workingCopyHistory';
import { assertIsDefined } from 'vs/base/common/types';
+import { VSBuffer } from 'vs/base/common/buffer';
+import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider';
+import { IDisposable } from 'vs/base/common/lifecycle';
-flakySuite('WorkingCopyHistoryTracker', () => {
+suite('WorkingCopyHistoryTracker', () => {
- let testDir: string;
- let historyHome: string;
- let workHome: string;
+ let testDir: URI;
+ let historyHome: URI;
+ let workHome: URI;
let workingCopyHistoryService: TestWorkingCopyHistoryService;
let workingCopyService: WorkingCopyService;
let fileService: IFileService;
let configurationService: TestConfigurationService;
+ let inMemoryFileSystemDisposable: IDisposable;
let tracker: WorkingCopyHistoryTracker;
- let testFile1Path: string;
- let testFile2Path: string;
+ let testFile1Path: URI;
+ let testFile2Path: URI;
const testFile1PathContents = 'Hello Foo';
const testFile2PathContents = [
@@ -65,25 +67,27 @@ flakySuite('WorkingCopyHistoryTracker', () => {
}
setup(async () => {
- testDir = getRandomTestPath(tmpdir(), 'vsctests', 'workingcopyhistorytracker');
- historyHome = join(testDir, 'User', 'History');
- workHome = join(testDir, 'work');
+ testDir = URI.file(randomPath(join(tmpdir(), 'vsctests', 'workingcopyhistorytracker'))).with({ scheme: Schemas.inMemory });
+ historyHome = joinPath(testDir, 'User', 'History');
+ workHome = joinPath(testDir, 'work');
workingCopyHistoryService = new TestWorkingCopyHistoryService(testDir);
workingCopyService = new WorkingCopyService();
fileService = workingCopyHistoryService._fileService;
configurationService = workingCopyHistoryService._configurationService;
+ inMemoryFileSystemDisposable = fileService.registerProvider(Schemas.inMemory, new InMemoryFileSystemProvider());
+
tracker = createTracker();
- await Promises.mkdir(historyHome, { recursive: true });
- await Promises.mkdir(workHome, { recursive: true });
+ await fileService.createFolder(historyHome);
+ await fileService.createFolder(workHome);
- testFile1Path = join(workHome, 'foo.txt');
- testFile2Path = join(workHome, 'bar.txt');
+ testFile1Path = joinPath(workHome, 'foo.txt');
+ testFile2Path = joinPath(workHome, 'bar.txt');
- await Promises.writeFile(testFile1Path, testFile1PathContents);
- await Promises.writeFile(testFile2Path, testFile2PathContents);
+ await fileService.writeFile(testFile1Path, VSBuffer.fromString(testFile1PathContents));
+ await fileService.writeFile(testFile2Path, VSBuffer.fromString(testFile2PathContents));
});
function createTracker() {
@@ -99,17 +103,19 @@ flakySuite('WorkingCopyHistoryTracker', () => {
);
}
- teardown(() => {
+ teardown(async () => {
workingCopyHistoryService.dispose();
workingCopyService.dispose();
tracker.dispose();
- return Promises.rm(testDir);
+ await fileService.del(testDir, { recursive: true });
+
+ inMemoryFileSystemDisposable.dispose();
});
test('history entry added on save', async () => {
- const workingCopy1 = new TestWorkingCopy(URI.file(testFile1Path));
- const workingCopy2 = new TestWorkingCopy(URI.file(testFile2Path));
+ const workingCopy1 = new TestWorkingCopy(testFile1Path);
+ const workingCopy2 = new TestWorkingCopy(testFile2Path);
const stat1 = await fileService.resolve(workingCopy1.resource, { resolveMetadata: true });
const stat2 = await fileService.resolve(workingCopy2.resource, { resolveMetadata: true });
@@ -136,7 +142,7 @@ flakySuite('WorkingCopyHistoryTracker', () => {
});
test('history entry skipped when setting disabled (globally)', async () => {
- configurationService.setUserConfiguration('workbench.localHistory.enabled', false, URI.file(testFile1Path));
+ configurationService.setUserConfiguration('workbench.localHistory.enabled', false, testFile1Path);
return assertNoLocalHistoryEntryAddedWithSettingsConfigured();
});
@@ -152,14 +158,14 @@ flakySuite('WorkingCopyHistoryTracker', () => {
});
test('history entry skipped when too large', async () => {
- configurationService.setUserConfiguration('workbench.localHistory.maxFileSize', 0, URI.file(testFile1Path));
+ configurationService.setUserConfiguration('workbench.localHistory.maxFileSize', 0, testFile1Path);
return assertNoLocalHistoryEntryAddedWithSettingsConfigured();
});
async function assertNoLocalHistoryEntryAddedWithSettingsConfigured(): Promise<void> {
- const workingCopy1 = new TestWorkingCopy(URI.file(testFile1Path));
- const workingCopy2 = new TestWorkingCopy(URI.file(testFile2Path));
+ const workingCopy1 = new TestWorkingCopy(testFile1Path);
+ const workingCopy2 = new TestWorkingCopy(testFile2Path);
const stat1 = await fileService.resolve(workingCopy1.resource, { resolveMetadata: true });
const stat2 = await fileService.resolve(workingCopy2.resource, { resolveMetadata: true });
@@ -187,7 +193,7 @@ flakySuite('WorkingCopyHistoryTracker', () => {
test('entries moved (file rename)', async () => {
const entriesMoved = Event.toPromise(workingCopyHistoryService.onDidMoveEntries);
- const workingCopy = new TestWorkingCopy(URI.file(testFile1Path));
+ const workingCopy = new TestWorkingCopy(testFile1Path);
const entry1 = await addEntry({ resource: workingCopy.resource, source: 'test-source' }, CancellationToken.None);
const entry2 = await addEntry({ resource: workingCopy.resource, source: 'test-source' }, CancellationToken.None);
@@ -233,8 +239,8 @@ flakySuite('WorkingCopyHistoryTracker', () => {
test('entries moved (folder rename)', async () => {
const entriesMoved = Event.toPromise(workingCopyHistoryService.onDidMoveEntries);
- const workingCopy1 = new TestWorkingCopy(URI.file(testFile1Path));
- const workingCopy2 = new TestWorkingCopy(URI.file(testFile2Path));
+ const workingCopy1 = new TestWorkingCopy(testFile1Path);
+ const workingCopy2 = new TestWorkingCopy(testFile2Path);
const entry1A = await addEntry({ resource: workingCopy1.resource, source: 'test-source' }, CancellationToken.None);
const entry2A = await addEntry({ resource: workingCopy1.resource, source: 'test-source' }, CancellationToken.None);
@@ -250,8 +256,8 @@ flakySuite('WorkingCopyHistoryTracker', () => {
entries = await workingCopyHistoryService.getEntries(workingCopy2.resource, CancellationToken.None);
assert.strictEqual(entries.length, 3);
- const renamedWorkHome = joinPath(dirname(URI.file(workHome)), 'renamed');
- await workingCopyHistoryService._fileService.move(URI.file(workHome), renamedWorkHome);
+ const renamedWorkHome = joinPath(dirname(testDir), 'renamed');
+ await workingCopyHistoryService._fileService.move(workHome, renamedWorkHome);
const renamedWorkingCopy1Resource = joinPath(renamedWorkHome, basename(workingCopy1.resource));
const renamedWorkingCopy2Resource = joinPath(renamedWorkHome, basename(workingCopy2.resource));
diff --git a/src/vs/workbench/services/workspaces/common/workspaceTrust.ts b/src/vs/workbench/services/workspaces/common/workspaceTrust.ts
index e464de637ee..16b1dff68d9 100644
--- a/src/vs/workbench/services/workspaces/common/workspaceTrust.ts
+++ b/src/vs/workbench/services/workspaces/common/workspaceTrust.ts
@@ -224,6 +224,10 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork
filesToOpen.push(...this.environmentService.filesToDiff);
}
+ if (this.environmentService.filesToMerge) {
+ filesToOpen.push(...this.environmentService.filesToMerge);
+ }
+
if (filesToOpen.length) {
const filesToOpenOrCreateUris = filesToOpen.filter(f => !!f.fileUri).map(f => f.fileUri!);
const canonicalFilesToOpen = await Promise.all(filesToOpenOrCreateUris.map(uri => this.getCanonicalUri(uri)));
diff --git a/src/vs/workbench/test/browser/parts/editor/editor.test.ts b/src/vs/workbench/test/browser/parts/editor/editor.test.ts
index 74e548d78f6..df4f76fb26e 100644
--- a/src/vs/workbench/test/browser/parts/editor/editor.test.ts
+++ b/src/vs/workbench/test/browser/parts/editor/editor.test.ts
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
-import { EditorResourceAccessor, SideBySideEditor, EditorInputWithPreferredResource, EditorInputCapabilities, isEditorIdentifier, IResourceDiffEditorInput, IUntitledTextResourceEditorInput, isResourceEditorInput, isUntitledResourceEditorInput, isResourceDiffEditorInput, isEditorInputWithOptionsAndGroup, EditorInputWithOptions, isEditorInputWithOptions, isEditorInput, EditorInputWithOptionsAndGroup, isResourceSideBySideEditorInput, IResourceSideBySideEditorInput, isTextEditorViewState } from 'vs/workbench/common/editor';
+import { EditorResourceAccessor, SideBySideEditor, EditorInputWithPreferredResource, EditorInputCapabilities, isEditorIdentifier, IResourceDiffEditorInput, IUntitledTextResourceEditorInput, isResourceEditorInput, isUntitledResourceEditorInput, isResourceDiffEditorInput, isEditorInputWithOptionsAndGroup, EditorInputWithOptions, isEditorInputWithOptions, isEditorInput, EditorInputWithOptionsAndGroup, isResourceSideBySideEditorInput, IResourceSideBySideEditorInput, isTextEditorViewState, isResourceMergeEditorInput, IResourceMergeEditorInput } from 'vs/workbench/common/editor';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
import { URI } from 'vs/base/common/uri';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -91,6 +91,11 @@ suite('Workbench editor utils', () => {
assert.ok(isResourceSideBySideEditorInput({ primary: { resource: URI.file('/') }, secondary: { resource: URI.file('/') } }));
assert.ok(!isResourceSideBySideEditorInput({ original: { resource: URI.file('/') }, modified: { resource: URI.file('/') } }));
assert.ok(!isResourceSideBySideEditorInput({ primary: { resource: URI.file('/') }, secondary: { resource: URI.file('/') }, original: { resource: URI.file('/') }, modified: { resource: URI.file('/') } }));
+
+ assert.ok(!isResourceMergeEditorInput(undefined));
+ assert.ok(!isResourceMergeEditorInput({}));
+ assert.ok(!isResourceMergeEditorInput({ resource: URI.file('/') }));
+ assert.ok(isResourceMergeEditorInput({ input1: { resource: URI.file('/') }, input2: { resource: URI.file('/') }, base: { resource: URI.file('/') }, result: { resource: URI.file('/') } }));
});
test('EditorInputCapabilities', () => {
@@ -115,7 +120,7 @@ suite('Workbench editor utils', () => {
testInput2.capabilities = EditorInputCapabilities.None;
const sideBySideInput = instantiationService.createInstance(SideBySideEditorInput, 'name', undefined, testInput1, testInput2);
- assert.strictEqual(sideBySideInput.hasCapability(EditorInputCapabilities.None), true);
+ assert.strictEqual(sideBySideInput.hasCapability(EditorInputCapabilities.MultipleEditors), true);
assert.strictEqual(sideBySideInput.hasCapability(EditorInputCapabilities.Readonly), false);
assert.strictEqual(sideBySideInput.hasCapability(EditorInputCapabilities.Untitled), false);
assert.strictEqual(sideBySideInput.hasCapability(EditorInputCapabilities.RequiresTrust), false);
@@ -154,42 +159,42 @@ suite('Workbench editor utils', () => {
const untitled = instantiationService.createInstance(UntitledTextEditorInput, service.create());
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untitled)!.toString(), untitled.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untitled, { supportSideBySide: SideBySideEditor.PRIMARY })!.toString(), untitled.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untitled, { supportSideBySide: SideBySideEditor.ANY })!.toString(), untitled.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untitled, { supportSideBySide: SideBySideEditor.SECONDARY })!.toString(), untitled.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untitled, { supportSideBySide: SideBySideEditor.BOTH })!.toString(), untitled.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untitled, { filterByScheme: Schemas.untitled })!.toString(), untitled.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untitled, { filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), untitled.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untitled)?.toString(), untitled.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untitled, { supportSideBySide: SideBySideEditor.PRIMARY })?.toString(), untitled.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untitled, { supportSideBySide: SideBySideEditor.ANY })?.toString(), untitled.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untitled, { supportSideBySide: SideBySideEditor.SECONDARY })?.toString(), untitled.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untitled, { supportSideBySide: SideBySideEditor.BOTH })?.toString(), untitled.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untitled, { filterByScheme: Schemas.untitled })?.toString(), untitled.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untitled, { filterByScheme: [Schemas.file, Schemas.untitled] })?.toString(), untitled.resource.toString());
assert.ok(!EditorResourceAccessor.getCanonicalUri(untitled, { filterByScheme: Schemas.file }));
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(untitled)!.toString(), untitled.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(untitled, { supportSideBySide: SideBySideEditor.PRIMARY })!.toString(), untitled.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(untitled, { supportSideBySide: SideBySideEditor.ANY })!.toString(), untitled.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(untitled, { supportSideBySide: SideBySideEditor.SECONDARY })!.toString(), untitled.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(untitled, { supportSideBySide: SideBySideEditor.BOTH })!.toString(), untitled.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(untitled, { filterByScheme: Schemas.untitled })!.toString(), untitled.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(untitled, { filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), untitled.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(untitled)?.toString(), untitled.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(untitled, { supportSideBySide: SideBySideEditor.PRIMARY })?.toString(), untitled.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(untitled, { supportSideBySide: SideBySideEditor.ANY })?.toString(), untitled.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(untitled, { supportSideBySide: SideBySideEditor.SECONDARY })?.toString(), untitled.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(untitled, { supportSideBySide: SideBySideEditor.BOTH })?.toString(), untitled.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(untitled, { filterByScheme: Schemas.untitled })?.toString(), untitled.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(untitled, { filterByScheme: [Schemas.file, Schemas.untitled] })?.toString(), untitled.resource.toString());
assert.ok(!EditorResourceAccessor.getOriginalUri(untitled, { filterByScheme: Schemas.file }));
const file = new TestEditorInput(URI.file('/some/path.txt'), 'editorResourceFileTest');
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(file)!.toString(), file.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(file, { supportSideBySide: SideBySideEditor.PRIMARY })!.toString(), file.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(file, { supportSideBySide: SideBySideEditor.ANY })!.toString(), file.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(file, { supportSideBySide: SideBySideEditor.SECONDARY })!.toString(), file.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(file, { supportSideBySide: SideBySideEditor.BOTH })!.toString(), file.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(file, { filterByScheme: Schemas.file })!.toString(), file.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(file, { filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(file)?.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(file, { supportSideBySide: SideBySideEditor.PRIMARY })?.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(file, { supportSideBySide: SideBySideEditor.ANY })?.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(file, { supportSideBySide: SideBySideEditor.SECONDARY })?.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(file, { supportSideBySide: SideBySideEditor.BOTH })?.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(file, { filterByScheme: Schemas.file })?.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(file, { filterByScheme: [Schemas.file, Schemas.untitled] })?.toString(), file.resource.toString());
assert.ok(!EditorResourceAccessor.getCanonicalUri(file, { filterByScheme: Schemas.untitled }));
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(file)!.toString(), file.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(file, { supportSideBySide: SideBySideEditor.PRIMARY })!.toString(), file.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(file, { supportSideBySide: SideBySideEditor.ANY })!.toString(), file.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(file, { supportSideBySide: SideBySideEditor.SECONDARY })!.toString(), file.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(file, { supportSideBySide: SideBySideEditor.BOTH })!.toString(), file.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(file, { filterByScheme: Schemas.file })!.toString(), file.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(file, { filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(file)?.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(file, { supportSideBySide: SideBySideEditor.PRIMARY })?.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(file, { supportSideBySide: SideBySideEditor.ANY })?.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(file, { supportSideBySide: SideBySideEditor.SECONDARY })?.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(file, { supportSideBySide: SideBySideEditor.BOTH })?.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(file, { filterByScheme: Schemas.file })?.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(file, { filterByScheme: [Schemas.file, Schemas.untitled] })?.toString(), file.resource.toString());
assert.ok(!EditorResourceAccessor.getOriginalUri(file, { filterByScheme: Schemas.untitled }));
const diffInput = instantiationService.createInstance(DiffEditorInput, 'name', 'description', untitled, file, undefined);
@@ -198,13 +203,13 @@ suite('Workbench editor utils', () => {
assert.ok(!EditorResourceAccessor.getCanonicalUri(input));
assert.ok(!EditorResourceAccessor.getCanonicalUri(input, { filterByScheme: Schemas.file }));
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(input, { supportSideBySide: SideBySideEditor.PRIMARY })!.toString(), file.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(input, { supportSideBySide: SideBySideEditor.PRIMARY, filterByScheme: Schemas.file })!.toString(), file.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(input, { supportSideBySide: SideBySideEditor.PRIMARY, filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(input, { supportSideBySide: SideBySideEditor.PRIMARY })?.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(input, { supportSideBySide: SideBySideEditor.PRIMARY, filterByScheme: Schemas.file })?.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(input, { supportSideBySide: SideBySideEditor.PRIMARY, filterByScheme: [Schemas.file, Schemas.untitled] })?.toString(), file.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(input, { supportSideBySide: SideBySideEditor.SECONDARY })!.toString(), untitled.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(input, { supportSideBySide: SideBySideEditor.SECONDARY, filterByScheme: Schemas.untitled })!.toString(), untitled.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(input, { supportSideBySide: SideBySideEditor.SECONDARY, filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), untitled.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(input, { supportSideBySide: SideBySideEditor.SECONDARY })?.toString(), untitled.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(input, { supportSideBySide: SideBySideEditor.SECONDARY, filterByScheme: Schemas.untitled })?.toString(), untitled.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(input, { supportSideBySide: SideBySideEditor.SECONDARY, filterByScheme: [Schemas.file, Schemas.untitled] })?.toString(), untitled.resource.toString());
assert.strictEqual((EditorResourceAccessor.getCanonicalUri(input, { supportSideBySide: SideBySideEditor.BOTH }) as { primary: URI; secondary: URI }).primary.toString(), file.resource.toString());
assert.strictEqual((EditorResourceAccessor.getCanonicalUri(input, { supportSideBySide: SideBySideEditor.BOTH, filterByScheme: Schemas.file }) as { primary: URI; secondary: URI }).primary.toString(), file.resource.toString());
@@ -217,13 +222,13 @@ suite('Workbench editor utils', () => {
assert.ok(!EditorResourceAccessor.getOriginalUri(input));
assert.ok(!EditorResourceAccessor.getOriginalUri(input, { filterByScheme: Schemas.file }));
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(input, { supportSideBySide: SideBySideEditor.PRIMARY })!.toString(), file.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(input, { supportSideBySide: SideBySideEditor.PRIMARY, filterByScheme: Schemas.file })!.toString(), file.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(input, { supportSideBySide: SideBySideEditor.PRIMARY, filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(input, { supportSideBySide: SideBySideEditor.PRIMARY })?.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(input, { supportSideBySide: SideBySideEditor.PRIMARY, filterByScheme: Schemas.file })?.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(input, { supportSideBySide: SideBySideEditor.PRIMARY, filterByScheme: [Schemas.file, Schemas.untitled] })?.toString(), file.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(input, { supportSideBySide: SideBySideEditor.SECONDARY })!.toString(), untitled.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(input, { supportSideBySide: SideBySideEditor.SECONDARY, filterByScheme: Schemas.untitled })!.toString(), untitled.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(input, { supportSideBySide: SideBySideEditor.SECONDARY, filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), untitled.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(input, { supportSideBySide: SideBySideEditor.SECONDARY })?.toString(), untitled.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(input, { supportSideBySide: SideBySideEditor.SECONDARY, filterByScheme: Schemas.untitled })?.toString(), untitled.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(input, { supportSideBySide: SideBySideEditor.SECONDARY, filterByScheme: [Schemas.file, Schemas.untitled] })?.toString(), untitled.resource.toString());
assert.strictEqual((EditorResourceAccessor.getOriginalUri(input, { supportSideBySide: SideBySideEditor.BOTH }) as { primary: URI; secondary: URI }).primary.toString(), file.resource.toString());
assert.strictEqual((EditorResourceAccessor.getOriginalUri(input, { supportSideBySide: SideBySideEditor.BOTH, filterByScheme: Schemas.file }) as { primary: URI; secondary: URI }).primary.toString(), file.resource.toString());
@@ -256,44 +261,44 @@ suite('Workbench editor utils', () => {
resource: untitledURI
};
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untitled)!.toString(), untitled.resource?.toString());
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untitled, { supportSideBySide: SideBySideEditor.PRIMARY })!.toString(), untitled.resource?.toString());
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untitled, { supportSideBySide: SideBySideEditor.ANY })!.toString(), untitled.resource?.toString());
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untitled, { supportSideBySide: SideBySideEditor.SECONDARY })!.toString(), untitled.resource?.toString());
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untitled, { supportSideBySide: SideBySideEditor.BOTH })!.toString(), untitled.resource?.toString());
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untitled, { filterByScheme: Schemas.untitled })!.toString(), untitled.resource?.toString());
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untitled, { filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), untitled.resource?.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untitled)?.toString(), untitled.resource?.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untitled, { supportSideBySide: SideBySideEditor.PRIMARY })?.toString(), untitled.resource?.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untitled, { supportSideBySide: SideBySideEditor.ANY })?.toString(), untitled.resource?.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untitled, { supportSideBySide: SideBySideEditor.SECONDARY })?.toString(), untitled.resource?.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untitled, { supportSideBySide: SideBySideEditor.BOTH })?.toString(), untitled.resource?.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untitled, { filterByScheme: Schemas.untitled })?.toString(), untitled.resource?.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untitled, { filterByScheme: [Schemas.file, Schemas.untitled] })?.toString(), untitled.resource?.toString());
assert.ok(!EditorResourceAccessor.getCanonicalUri(untitled, { filterByScheme: Schemas.file }));
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(untitled)!.toString(), untitled.resource?.toString());
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(untitled, { supportSideBySide: SideBySideEditor.PRIMARY })!.toString(), untitled.resource?.toString());
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(untitled, { supportSideBySide: SideBySideEditor.ANY })!.toString(), untitled.resource?.toString());
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(untitled, { supportSideBySide: SideBySideEditor.SECONDARY })!.toString(), untitled.resource?.toString());
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(untitled, { supportSideBySide: SideBySideEditor.BOTH })!.toString(), untitled.resource?.toString());
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(untitled, { filterByScheme: Schemas.untitled })!.toString(), untitled.resource?.toString());
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(untitled, { filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), untitled.resource?.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(untitled)?.toString(), untitled.resource?.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(untitled, { supportSideBySide: SideBySideEditor.PRIMARY })?.toString(), untitled.resource?.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(untitled, { supportSideBySide: SideBySideEditor.ANY })?.toString(), untitled.resource?.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(untitled, { supportSideBySide: SideBySideEditor.SECONDARY })?.toString(), untitled.resource?.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(untitled, { supportSideBySide: SideBySideEditor.BOTH })?.toString(), untitled.resource?.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(untitled, { filterByScheme: Schemas.untitled })?.toString(), untitled.resource?.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(untitled, { filterByScheme: [Schemas.file, Schemas.untitled] })?.toString(), untitled.resource?.toString());
assert.ok(!EditorResourceAccessor.getOriginalUri(untitled, { filterByScheme: Schemas.file }));
const file: IResourceEditorInput = {
resource: URI.file('/some/path.txt')
};
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(file)!.toString(), file.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(file, { supportSideBySide: SideBySideEditor.PRIMARY })!.toString(), file.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(file, { supportSideBySide: SideBySideEditor.ANY })!.toString(), file.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(file, { supportSideBySide: SideBySideEditor.SECONDARY })!.toString(), file.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(file, { supportSideBySide: SideBySideEditor.BOTH })!.toString(), file.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(file, { filterByScheme: Schemas.file })!.toString(), file.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(file, { filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(file)?.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(file, { supportSideBySide: SideBySideEditor.PRIMARY })?.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(file, { supportSideBySide: SideBySideEditor.ANY })?.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(file, { supportSideBySide: SideBySideEditor.SECONDARY })?.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(file, { supportSideBySide: SideBySideEditor.BOTH })?.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(file, { filterByScheme: Schemas.file })?.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(file, { filterByScheme: [Schemas.file, Schemas.untitled] })?.toString(), file.resource.toString());
assert.ok(!EditorResourceAccessor.getCanonicalUri(file, { filterByScheme: Schemas.untitled }));
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(file)!.toString(), file.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(file, { supportSideBySide: SideBySideEditor.PRIMARY })!.toString(), file.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(file, { supportSideBySide: SideBySideEditor.ANY })!.toString(), file.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(file, { supportSideBySide: SideBySideEditor.SECONDARY })!.toString(), file.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(file, { supportSideBySide: SideBySideEditor.BOTH })!.toString(), file.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(file, { filterByScheme: Schemas.file })!.toString(), file.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(file, { filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(file)?.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(file, { supportSideBySide: SideBySideEditor.PRIMARY })?.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(file, { supportSideBySide: SideBySideEditor.ANY })?.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(file, { supportSideBySide: SideBySideEditor.SECONDARY })?.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(file, { supportSideBySide: SideBySideEditor.BOTH })?.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(file, { filterByScheme: Schemas.file })?.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(file, { filterByScheme: [Schemas.file, Schemas.untitled] })?.toString(), file.resource.toString());
assert.ok(!EditorResourceAccessor.getOriginalUri(file, { filterByScheme: Schemas.untitled }));
const diffInput: IResourceDiffEditorInput = { original: untitled, modified: file };
@@ -302,13 +307,13 @@ suite('Workbench editor utils', () => {
assert.ok(!EditorResourceAccessor.getCanonicalUri(untypedInput));
assert.ok(!EditorResourceAccessor.getCanonicalUri(untypedInput, { filterByScheme: Schemas.file }));
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untypedInput, { supportSideBySide: SideBySideEditor.PRIMARY })!.toString(), file.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untypedInput, { supportSideBySide: SideBySideEditor.PRIMARY, filterByScheme: Schemas.file })!.toString(), file.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untypedInput, { supportSideBySide: SideBySideEditor.PRIMARY, filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untypedInput, { supportSideBySide: SideBySideEditor.PRIMARY })?.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untypedInput, { supportSideBySide: SideBySideEditor.PRIMARY, filterByScheme: Schemas.file })?.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untypedInput, { supportSideBySide: SideBySideEditor.PRIMARY, filterByScheme: [Schemas.file, Schemas.untitled] })?.toString(), file.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untypedInput, { supportSideBySide: SideBySideEditor.SECONDARY })!.toString(), untitled.resource?.toString());
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untypedInput, { supportSideBySide: SideBySideEditor.SECONDARY, filterByScheme: Schemas.untitled })!.toString(), untitled.resource?.toString());
- assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untypedInput, { supportSideBySide: SideBySideEditor.SECONDARY, filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), untitled.resource?.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untypedInput, { supportSideBySide: SideBySideEditor.SECONDARY })?.toString(), untitled.resource?.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untypedInput, { supportSideBySide: SideBySideEditor.SECONDARY, filterByScheme: Schemas.untitled })?.toString(), untitled.resource?.toString());
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(untypedInput, { supportSideBySide: SideBySideEditor.SECONDARY, filterByScheme: [Schemas.file, Schemas.untitled] })?.toString(), untitled.resource?.toString());
assert.strictEqual((EditorResourceAccessor.getCanonicalUri(untypedInput, { supportSideBySide: SideBySideEditor.BOTH }) as { primary: URI; secondary: URI }).primary.toString(), file.resource.toString());
assert.strictEqual((EditorResourceAccessor.getCanonicalUri(untypedInput, { supportSideBySide: SideBySideEditor.BOTH, filterByScheme: Schemas.file }) as { primary: URI; secondary: URI }).primary.toString(), file.resource.toString());
@@ -321,13 +326,13 @@ suite('Workbench editor utils', () => {
assert.ok(!EditorResourceAccessor.getOriginalUri(untypedInput));
assert.ok(!EditorResourceAccessor.getOriginalUri(untypedInput, { filterByScheme: Schemas.file }));
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(untypedInput, { supportSideBySide: SideBySideEditor.PRIMARY })!.toString(), file.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(untypedInput, { supportSideBySide: SideBySideEditor.PRIMARY, filterByScheme: Schemas.file })!.toString(), file.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(untypedInput, { supportSideBySide: SideBySideEditor.PRIMARY, filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(untypedInput, { supportSideBySide: SideBySideEditor.PRIMARY })?.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(untypedInput, { supportSideBySide: SideBySideEditor.PRIMARY, filterByScheme: Schemas.file })?.toString(), file.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(untypedInput, { supportSideBySide: SideBySideEditor.PRIMARY, filterByScheme: [Schemas.file, Schemas.untitled] })?.toString(), file.resource.toString());
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(untypedInput, { supportSideBySide: SideBySideEditor.SECONDARY })!.toString(), untitled.resource?.toString());
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(untypedInput, { supportSideBySide: SideBySideEditor.SECONDARY, filterByScheme: Schemas.untitled })!.toString(), untitled.resource?.toString());
- assert.strictEqual(EditorResourceAccessor.getOriginalUri(untypedInput, { supportSideBySide: SideBySideEditor.SECONDARY, filterByScheme: [Schemas.file, Schemas.untitled] })!.toString(), untitled.resource?.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(untypedInput, { supportSideBySide: SideBySideEditor.SECONDARY })?.toString(), untitled.resource?.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(untypedInput, { supportSideBySide: SideBySideEditor.SECONDARY, filterByScheme: Schemas.untitled })?.toString(), untitled.resource?.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(untypedInput, { supportSideBySide: SideBySideEditor.SECONDARY, filterByScheme: [Schemas.file, Schemas.untitled] })?.toString(), untitled.resource?.toString());
assert.strictEqual((EditorResourceAccessor.getOriginalUri(untypedInput, { supportSideBySide: SideBySideEditor.BOTH }) as { primary: URI; secondary: URI }).primary.toString(), file.resource.toString());
assert.strictEqual((EditorResourceAccessor.getOriginalUri(untypedInput, { supportSideBySide: SideBySideEditor.BOTH, filterByScheme: Schemas.file }) as { primary: URI; secondary: URI }).primary.toString(), file.resource.toString());
@@ -337,6 +342,16 @@ suite('Workbench editor utils', () => {
assert.strictEqual((EditorResourceAccessor.getOriginalUri(untypedInput, { supportSideBySide: SideBySideEditor.BOTH, filterByScheme: Schemas.untitled }) as { primary: URI; secondary: URI }).secondary.toString(), untitled.resource?.toString());
assert.strictEqual((EditorResourceAccessor.getOriginalUri(untypedInput, { supportSideBySide: SideBySideEditor.BOTH, filterByScheme: [Schemas.file, Schemas.untitled] }) as { primary: URI; secondary: URI }).secondary.toString(), untitled.resource?.toString());
}
+
+ const fileMerge: IResourceMergeEditorInput = {
+ input1: { resource: URI.file('/some/remote.txt') },
+ input2: { resource: URI.file('/some/local.txt') },
+ base: { resource: URI.file('/some/base.txt') },
+ result: { resource: URI.file('/some/merged.txt') }
+ };
+
+ assert.strictEqual(EditorResourceAccessor.getCanonicalUri(fileMerge)?.toString(), fileMerge.result.resource.toString());
+ assert.strictEqual(EditorResourceAccessor.getOriginalUri(fileMerge)?.toString(), fileMerge.result.resource.toString());
});
test('isEditorIdentifier', () => {
diff --git a/src/vs/workbench/test/browser/parts/editor/editorInput.test.ts b/src/vs/workbench/test/browser/parts/editor/editorInput.test.ts
index 9045a383ab0..e035dbde76f 100644
--- a/src/vs/workbench/test/browser/parts/editor/editorInput.test.ts
+++ b/src/vs/workbench/test/browser/parts/editor/editorInput.test.ts
@@ -5,7 +5,7 @@
import * as assert from 'assert';
import { URI } from 'vs/base/common/uri';
-import { isEditorInput, isResourceDiffEditorInput, isResourceEditorInput, isResourceSideBySideEditorInput, isUntitledResourceEditorInput } from 'vs/workbench/common/editor';
+import { isEditorInput, isResourceDiffEditorInput, isResourceEditorInput, isResourceMergeEditorInput, isResourceSideBySideEditorInput, isUntitledResourceEditorInput } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { TestEditorInput } from 'vs/workbench/test/browser/workbenchTestServices';
@@ -31,6 +31,7 @@ suite('EditorInput', () => {
assert.ok(!isResourceEditorInput(input));
assert.ok(!isUntitledResourceEditorInput(input));
assert.ok(!isResourceDiffEditorInput(input));
+ assert.ok(!isResourceMergeEditorInput(input));
assert.ok(!isResourceSideBySideEditorInput(input));
assert(input.matches(input));
diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts
index b910e0408c4..ff291f79a9f 100644
--- a/src/vs/workbench/test/browser/workbenchTestServices.ts
+++ b/src/vs/workbench/test/browser/workbenchTestServices.ts
@@ -160,7 +160,7 @@ import { ExtensionIdentifier, ExtensionType, IExtension, IExtensionDescription,
import { ISocketFactory } from 'vs/platform/remote/common/remoteAgentConnection';
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
import { ILayoutOffsetInfo } from 'vs/platform/layout/browser/layoutService';
-import { IUserDataProfilesService, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
+import { IUserDataProfilesService, toUserDataProfile, UserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
import { UserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfileService';
import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
import { EnablementState, IExtensionManagementServer, IScannedExtension, IWebExtensionsScannerService, IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
@@ -2006,6 +2006,14 @@ export class TestWorkbenchExtensionManagementService implements IWorkbenchExtens
async getTargetPlatform(): Promise<TargetPlatform> { return TargetPlatform.UNDEFINED; }
}
+export class TestUserDataProfileService implements IUserDataProfileService {
+
+ readonly _serviceBrand: undefined;
+ readonly onDidChangeCurrentProfile = Event.None;
+ readonly currentProfile = toUserDataProfile('test', URI.file('tests').with({ scheme: 'vscode-tests' }));
+ async updateCurrentProfile(): Promise<void> { }
+}
+
export class TestWebExtensionsScannerService implements IWebExtensionsScannerService {
_serviceBrand: undefined;
onDidChangeProfileExtensions = Event.None;
diff --git a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts
index f4fce1346e2..16a83da3eac 100644
--- a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts
+++ b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts
@@ -205,6 +205,7 @@ export class TestNativeHostService implements INativeHostService {
onDidResumeOS: Event<unknown> = Event.None;
onDidChangeColorScheme = Event.None;
onDidChangePassword = Event.None;
+ onDidTriggerSystemContextMenu: Event<{ windowId: number; x: number; y: number }> = Event.None;
onDidChangeDisplay = Event.None;
windowCount = Promise.resolve(1);
diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts
index f3deec51ef4..4800d7148ee 100644
--- a/src/vs/workbench/workbench.common.main.ts
+++ b/src/vs/workbench/workbench.common.main.ts
@@ -264,11 +264,6 @@ import 'vs/workbench/contrib/keybindings/browser/keybindings.contribution';
// Snippets
import 'vs/workbench/contrib/snippets/browser/snippets.contribution';
-import 'vs/workbench/contrib/snippets/browser/snippetsService';
-import 'vs/workbench/contrib/snippets/browser/insertSnippet';
-import 'vs/workbench/contrib/snippets/browser/surroundWithSnippet';
-import 'vs/workbench/contrib/snippets/browser/configureSnippets';
-import 'vs/workbench/contrib/snippets/browser/tabCompletion';
// Formatter Help
import 'vs/workbench/contrib/format/browser/format.contribution';
@@ -349,4 +344,7 @@ import 'vs/workbench/contrib/list/browser/list.contribution';
// Audio Cues
import 'vs/workbench/contrib/audioCues/browser/audioCues.contribution';
+// Deprecated Extension Migrator
+import 'vs/workbench/contrib/deprecatedExtensionMigrator/browser/deprecatedExtensionMigrator.contribution';
+
//#endregion