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 <johannes.rieken@gmail.com>2022-07-13 13:01:36 +0300
committerJohannes <johannes.rieken@gmail.com>2022-07-13 13:01:36 +0300
commit78693265be2fa394330df097c3fd99925dff21d4 (patch)
treeebbed0a9f40c67fbfb152a7730c9e61774616f01 /src/vs/workbench/contrib
parent268c941bf0fd8255d4dd7c106c22e9b911772916 (diff)
parent4404dc63561a286e13f9ba5a669e5403a367425a (diff)
Merge branch 'main' into joh/cellUrijoh/cellUri
Diffstat (limited to 'src/vs/workbench/contrib')
-rw-r--r--src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts2
-rw-r--r--src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts35
-rw-r--r--src/vs/workbench/contrib/audioCues/browser/audioCueService.ts10
-rw-r--r--src/vs/workbench/contrib/audioCues/browser/observable.ts690
-rw-r--r--src/vs/workbench/contrib/bulkEdit/browser/bulkCellEdits.ts26
-rw-r--r--src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts29
-rw-r--r--src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts5
-rw-r--r--src/vs/workbench/contrib/comments/browser/commentNode.ts2
-rw-r--r--src/vs/workbench/contrib/comments/browser/commentReply.ts8
-rw-r--r--src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts3
-rw-r--r--src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts14
-rw-r--r--src/vs/workbench/contrib/comments/browser/commentsView.ts2
-rw-r--r--src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts4
-rw-r--r--src/vs/workbench/contrib/debug/browser/callStackView.ts4
-rw-r--r--src/vs/workbench/contrib/debug/browser/debug.contribution.ts34
-rw-r--r--src/vs/workbench/contrib/debug/browser/debugCommands.ts183
-rw-r--r--src/vs/workbench/contrib/debug/browser/debugService.ts10
-rw-r--r--src/vs/workbench/contrib/debug/browser/debugSession.ts6
-rw-r--r--src/vs/workbench/contrib/debug/browser/debugToolBar.ts4
-rw-r--r--src/vs/workbench/contrib/debug/common/debug.ts6
-rw-r--r--src/vs/workbench/contrib/debug/common/debugModel.ts26
-rw-r--r--src/vs/workbench/contrib/debug/node/telemetryApp.ts4
-rw-r--r--src/vs/workbench/contrib/debug/test/browser/mockDebug.ts4
-rw-r--r--src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts (renamed from src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts)137
-rw-r--r--src/vs/workbench/contrib/editSessions/browser/editSessionsWorkbenchService.ts373
-rw-r--r--src/vs/workbench/contrib/editSessions/common/editSessions.ts67
-rw-r--r--src/vs/workbench/contrib/editSessions/common/editSessionsLogService.ts50
-rw-r--r--src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts (renamed from src/vs/workbench/contrib/sessionSync/test/browser/sessionSync.test.ts)26
-rw-r--r--src/vs/workbench/contrib/extensions/browser/extensionEditor.ts3
-rw-r--r--src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts15
-rw-r--r--src/vs/workbench/contrib/extensions/browser/extensionsActions.ts179
-rw-r--r--src/vs/workbench/contrib/extensions/browser/extensionsList.ts22
-rw-r--r--src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts7
-rw-r--r--src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts34
-rw-r--r--src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts10
-rw-r--r--src/vs/workbench/contrib/extensions/common/extensions.ts2
-rw-r--r--src/vs/workbench/contrib/files/browser/fileCommands.ts13
-rw-r--r--src/vs/workbench/contrib/files/browser/files.contribution.ts8
-rw-r--r--src/vs/workbench/contrib/files/browser/views/explorerView.ts78
-rw-r--r--src/vs/workbench/contrib/files/common/files.ts2
-rw-r--r--src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts10
-rw-r--r--src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts69
-rw-r--r--src/vs/workbench/contrib/interactive/browser/interactiveCommon.ts5
-rw-r--r--src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts19
-rw-r--r--src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts129
-rw-r--r--src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts2
-rw-r--r--src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts10
-rw-r--r--src/vs/workbench/contrib/localization/browser/localeService.ts51
-rw-r--r--src/vs/workbench/contrib/localization/browser/localizationsActions.ts39
-rw-r--r--src/vs/workbench/contrib/localization/common/locale.ts4
-rw-r--r--src/vs/workbench/contrib/localization/electron-sandbox/localeService.ts52
-rw-r--r--src/vs/workbench/contrib/logs/common/logConstants.ts1
-rw-r--r--src/vs/workbench/contrib/logs/common/logs.contribution.ts1
-rw-r--r--src/vs/workbench/contrib/markers/browser/markersTable.ts9
-rw-r--r--src/vs/workbench/contrib/markers/browser/media/markers.css11
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts142
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts25
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts12
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts140
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts50
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange.ts18
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts8
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/utils.ts9
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts54
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts38
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts272
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts17
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/view/media/mergeEditor.css22
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts125
-rw-r--r--src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts38
-rw-r--r--src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts134
-rw-r--r--src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts90
-rw-r--r--src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts2
-rw-r--r--src/vs/workbench/contrib/notebook/browser/contrib/gettingStarted/notebookGettingStarted.ts5
-rw-r--r--src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts16
-rw-r--r--src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts9
-rw-r--r--src/vs/workbench/contrib/notebook/browser/controller/editActions.ts3
-rw-r--r--src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts8
-rw-r--r--src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts20
-rw-r--r--src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts2
-rw-r--r--src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts2
-rw-r--r--src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts9
-rw-r--r--src/vs/workbench/contrib/notebook/browser/notebookEditor.ts1
-rw-r--r--src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts8
-rw-r--r--src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts25
-rw-r--r--src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts2
-rw-r--r--src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts4
-rw-r--r--src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts7
-rw-r--r--src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts6
-rw-r--r--src/vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar.ts2
-rw-r--r--src/vs/workbench/contrib/notebook/common/notebookCommon.ts13
-rw-r--r--src/vs/workbench/contrib/notebook/common/notebookOptions.ts4
-rw-r--r--src/vs/workbench/contrib/output/browser/outputServices.ts2
-rw-r--r--src/vs/workbench/contrib/output/common/outputChannelModel.ts28
-rw-r--r--src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css2
-rw-r--r--src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts2
-rw-r--r--src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts14
-rw-r--r--src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts10
-rw-r--r--src/vs/workbench/contrib/remote/common/remote.contribution.ts4
-rw-r--r--src/vs/workbench/contrib/scm/browser/scmViewPane.ts31
-rw-r--r--src/vs/workbench/contrib/search/browser/search.contribution.ts29
-rw-r--r--src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts40
-rw-r--r--src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts14
-rw-r--r--src/vs/workbench/contrib/snippets/browser/snippetsFile.ts11
-rw-r--r--src/vs/workbench/contrib/snippets/browser/snippetsService.ts7
-rw-r--r--src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts36
-rw-r--r--src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts65
-rw-r--r--src/vs/workbench/contrib/tasks/browser/task.contribution.ts32
-rw-r--r--src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts15
-rw-r--r--src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts38
-rw-r--r--src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts9
-rw-r--r--src/vs/workbench/contrib/tasks/common/taskConfiguration.ts17
-rw-r--r--src/vs/workbench/contrib/tasks/common/tasks.ts35
-rw-r--r--src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts30
-rw-r--r--src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts3
-rwxr-xr-xsrc/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh46
-rw-r--r--src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh24
-rw-r--r--src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps116
-rw-r--r--src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts14
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts27
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminal.ts29
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalActions.ts33
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts37
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts3
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalIcon.ts7
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalInstance.ts205
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts6
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalMenus.ts13
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts2
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts5
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts23
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts2
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts11
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalService.ts17
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts2
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalView.ts17
-rw-r--r--src/vs/workbench/contrib/terminal/browser/xterm/commandNavigationAddon.ts4
-rw-r--r--src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts35
-rw-r--r--src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts4
-rw-r--r--src/vs/workbench/contrib/terminal/common/terminal.ts7
-rw-r--r--src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts34
-rw-r--r--src/vs/workbench/contrib/terminal/common/terminalContextKey.ts8
-rw-r--r--src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts10
-rw-r--r--src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts55
-rw-r--r--src/vs/workbench/contrib/terminal/test/browser/links/terminalLocalLinkDetector.test.ts3
-rw-r--r--src/vs/workbench/contrib/terminal/test/browser/xterm/decorationAddon.test.ts8
-rw-r--r--src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts2
-rw-r--r--src/vs/workbench/contrib/testing/browser/explorerProjections/nodeHelper.ts6
-rw-r--r--src/vs/workbench/contrib/testing/browser/testExplorerActions.ts46
-rw-r--r--src/vs/workbench/contrib/testing/browser/testing.contribution.ts1
-rw-r--r--src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts22
-rw-r--r--src/vs/workbench/contrib/testing/browser/testingExplorerView.ts2
-rw-r--r--src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts8
-rw-r--r--src/vs/workbench/contrib/update/browser/update.contribution.ts12
-rw-r--r--src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts18
-rw-r--r--src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts39
-rw-r--r--src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html13
-rw-r--r--src/vs/workbench/contrib/webview/browser/pre/index.html15
-rw-r--r--src/vs/workbench/contrib/webview/browser/webviewElement.ts11
-rw-r--r--src/vs/workbench/contrib/webview/electron-sandbox/webviewElement.ts7
-rw-r--r--src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts5
-rw-r--r--src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts4
-rw-r--r--src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css7
163 files changed, 3314 insertions, 1976 deletions
diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts
index fc3727f804d..3b400d3fafc 100644
--- a/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts
+++ b/src/vs/workbench/contrib/audioCues/browser/audioCueDebuggerContribution.ts
@@ -4,9 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
+import { autorunWithStore } from 'vs/base/common/observable';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { AudioCue, IAudioCueService } from 'vs/workbench/contrib/audioCues/browser/audioCueService';
-import { autorunWithStore } from 'vs/workbench/contrib/audioCues/browser/observable';
import { IDebugService, IDebugSession } from 'vs/workbench/contrib/debug/common/debug';
export class AudioCueLineDebuggerContribution
diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts
index 3c348baa40a..490fab6c4c6 100644
--- a/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts
+++ b/src/vs/workbench/contrib/audioCues/browser/audioCueLineFeatureContribution.ts
@@ -12,21 +12,11 @@ import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/edito
import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers';
import { FoldingController } from 'vs/editor/contrib/folding/browser/folding';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
-import {
- autorun,
- autorunDelta,
- constObservable,
- derivedObservable,
- observableFromEvent,
- observableFromPromise,
- IObservable,
- wasEventTriggeredRecently,
- debouncedObservable,
-} from 'vs/workbench/contrib/audioCues/browser/observable';
import { ITextModel } from 'vs/editor/common/model';
import { GhostTextController } from 'vs/editor/contrib/inlineCompletions/browser/ghostTextController';
import { AudioCue, IAudioCueService } from 'vs/workbench/contrib/audioCues/browser/audioCueService';
import { CursorChangeReason } from 'vs/editor/common/cursorEvents';
+import { autorun, autorunDelta, constObservable, debouncedObservable, derived, IObservable, observableFromEvent, observableFromPromise, wasEventTriggeredRecently } from 'vs/base/common/observable';
export class AudioCueLineFeatureContribution
extends Disposable
@@ -48,7 +38,7 @@ export class AudioCueLineFeatureContribution
) {
super();
- const someAudioCueFeatureIsEnabled = derivedObservable(
+ const someAudioCueFeatureIsEnabled = derived(
'someAudioCueFeatureIsEnabled',
(reader) =>
this.features.some((feature) =>
@@ -73,7 +63,7 @@ export class AudioCueLineFeatureContribution
);
this._register(
- autorun((reader) => {
+ autorun('updateAudioCuesEnabled', (reader) => {
this.store.clear();
if (!someAudioCueFeatureIsEnabled.read(reader)) {
@@ -84,7 +74,7 @@ export class AudioCueLineFeatureContribution
if (activeEditor) {
this.registerAudioCuesForEditor(activeEditor.editor, activeEditor.model, this.store);
}
- }, 'updateAudioCuesEnabled')
+ })
);
}
@@ -96,6 +86,7 @@ export class AudioCueLineFeatureContribution
const curLineNumber = observableFromEvent(
editor.onDidChangeCursorPosition,
(args) => {
+ /** @description editor.onDidChangeCursorPosition (caused by user) */
if (
args &&
args.reason !== CursorChangeReason.Explicit &&
@@ -117,7 +108,7 @@ export class AudioCueLineFeatureContribution
const featureStates = this.features.map((feature) => {
const lineFeatureState = feature.getObservableState(editor, editorModel);
- const isFeaturePresent = derivedObservable(
+ const isFeaturePresent = derived(
`isPresentInLine:${feature.audioCue.name}`,
(reader) => {
if (!this.audioCueService.isEnabled(feature.audioCue).read(reader)) {
@@ -129,7 +120,7 @@ export class AudioCueLineFeatureContribution
: lineFeatureState.read(reader).isPresent(lineNumber);
}
);
- return derivedObservable(
+ return derived(
`typingDebouncedFeatureState:\n${feature.audioCue.name}`,
(reader) =>
feature.debounceWhileTyping && isTyping.read(reader)
@@ -138,7 +129,7 @@ export class AudioCueLineFeatureContribution
);
});
- const state = derivedObservable(
+ const state = derived(
'states',
(reader) => ({
lineNumber: debouncedLineNumber.read(reader),
@@ -193,7 +184,7 @@ class MarkerLineFeature implements LineFeature {
Event.filter(this.markerService.onMarkerChanged, (changedUris) =>
changedUris.some((u) => u.toString() === model.uri.toString())
),
- () => ({
+ () => /** @description this.markerService.onMarkerChanged */({
isPresent: (lineNumber) => {
const hasMarker = this.markerService
.read({ resource: model.uri })
@@ -245,7 +236,7 @@ class BreakpointLineFeature implements LineFeature {
getObservableState(editor: ICodeEditor, model: ITextModel): IObservable<LineFeatureState> {
return observableFromEvent<LineFeatureState>(
this.debugService.getModel().onDidChangeBreakpoints,
- () => ({
+ () => /** @description debugService.getModel().onDidChangeBreakpoints */({
isPresent: (lineNumber) => {
const breakpoints = this.debugService
.getModel()
@@ -271,17 +262,17 @@ class InlineCompletionLineFeature implements LineFeature {
const activeGhostText = observableFromEvent(
ghostTextController.onActiveModelDidChange,
- () => ghostTextController.activeModel
+ () => /** @description ghostTextController.onActiveModelDidChange */ ghostTextController.activeModel
).map((activeModel) => (
activeModel
? observableFromEvent(
activeModel.inlineCompletionsModel.onDidChange,
- () => activeModel.inlineCompletionsModel.ghostText
+ () => /** @description activeModel.inlineCompletionsModel.onDidChange */ activeModel.inlineCompletionsModel.ghostText
)
: undefined
));
- return derivedObservable<LineFeatureState>('ghostText', reader => {
+ return derived<LineFeatureState>('ghostText', reader => {
const ghostText = activeGhostText.read(reader)?.read(reader);
return {
isPresent(lineNumber) {
diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCueService.ts b/src/vs/workbench/contrib/audioCues/browser/audioCueService.ts
index dcac8bf037b..4e491840f5a 100644
--- a/src/vs/workbench/contrib/audioCues/browser/audioCueService.ts
+++ b/src/vs/workbench/contrib/audioCues/browser/audioCueService.ts
@@ -9,9 +9,9 @@ import { FileAccess } from 'vs/base/common/network';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
-import { observableFromEvent, IObservable, LazyDerived } from 'vs/workbench/contrib/audioCues/browser/observable';
import { Event } from 'vs/base/common/event';
import { localize } from 'vs/nls';
+import { IObservable, observableFromEvent, derived } from 'vs/base/common/observable';
export const IAudioCueService = createDecorator<IAudioCueService>('audioCue');
@@ -29,7 +29,7 @@ export class AudioCueService extends Disposable implements IAudioCueService {
private readonly screenReaderAttached = observableFromEvent(
this.accessibilityService.onDidChangeScreenReaderOptimized,
- () => this.accessibilityService.isScreenReaderOptimized()
+ () => /** @description accessibilityService.onDidChangeScreenReaderOptimized */ this.accessibilityService.isScreenReaderOptimized()
);
constructor(
@@ -85,7 +85,7 @@ export class AudioCueService extends Disposable implements IAudioCueService {
Event.filter(this.configurationService.onDidChangeConfiguration, (e) =>
e.affectsConfiguration('audioCues.enabled')
),
- () => this.configurationService.getValue<'on' | 'off' | 'auto'>('audioCues.enabled')
+ () => /** @description config: audioCues.enabled */ this.configurationService.getValue<'on' | 'off' | 'auto'>('audioCues.enabled')
);
private readonly isEnabledCache = new Cache((cue: AudioCue) => {
@@ -95,7 +95,7 @@ export class AudioCueService extends Disposable implements IAudioCueService {
),
() => this.configurationService.getValue<'on' | 'off' | 'auto'>(cue.settingsKey)
);
- return new LazyDerived(reader => {
+ return derived('audio cue enabled', reader => {
const setting = settingObservable.read(reader);
if (
setting === 'on' ||
@@ -113,7 +113,7 @@ export class AudioCueService extends Disposable implements IAudioCueService {
}
return false;
- }, 'audio cue enabled');
+ });
});
public isEnabled(cue: AudioCue): IObservable<boolean> {
diff --git a/src/vs/workbench/contrib/audioCues/browser/observable.ts b/src/vs/workbench/contrib/audioCues/browser/observable.ts
deleted file mode 100644
index 2ad62412411..00000000000
--- a/src/vs/workbench/contrib/audioCues/browser/observable.ts
+++ /dev/null
@@ -1,690 +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 { Event } from 'vs/base/common/event';
-import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
-
-export interface IObservable<T, TChange = void> {
- _change: TChange;
-
- /**
- * Reads the current value.
- *
- * This causes a recomputation if needed.
- * Calling this method forces changes to propagate to observers during update operations.
- * Must not be called from {@link IObserver.handleChange}.
- */
- get(): T;
-
- /**
- * Registers an observer.
- *
- * Calls {@link IObserver.handleChange} immediately after a change is noticed.
- * Might happen while someone calls {@link IObservable.get} or {@link IObservable.read}.
- */
- subscribe(observer: IObserver): void;
- unsubscribe(observer: IObserver): void;
-
- /**
- * Calls {@link IObservable.get} and then {@link IReader.handleBeforeReadObservable}.
- */
- read(reader: IReader): T;
-
- map<TNew>(fn: (value: T) => TNew): IObservable<TNew>;
-}
-
-export interface IReader {
- /**
- * Reports an observable that was read.
- *
- * Is called by `Observable.read`.
- */
- handleBeforeReadObservable<T>(observable: IObservable<T, any>): void;
-}
-
-export interface IObserver {
- /**
- * Indicates that an update operation is about to begin.
- *
- * During an update, invariants might not hold for subscribed observables and
- * change events might be delayed.
- * However, all changes must be reported before all update operations are over.
- */
- beginUpdate<T>(observable: IObservable<T>): void;
-
- /**
- * Is called by a subscribed observable immediately after it notices a change.
- *
- * When {@link IObservable.get} returns and no change has been reported,
- * there has been no change for that observable.
- *
- * Implementations must not call into other observables!
- * The change should be processed when {@link IObserver.endUpdate} is called.
- */
- handleChange<T, TChange>(observable: IObservable<T, TChange>, change: TChange): void;
-
- /**
- * Indicates that an update operation has completed.
- */
- endUpdate<T>(observable: IObservable<T>): void;
-}
-
-export interface ISettable<T, TChange = void> {
- set(value: T, transaction: ITransaction | undefined, change: TChange): void;
-}
-
-export interface ITransaction {
- /**
- * Calls `Observer.beginUpdate` immediately
- * and `Observer.endUpdate` when the transaction is complete.
- */
- updateObserver(
- observer: IObserver,
- observable: IObservable<any, any>
- ): void;
-}
-
-// === Base ===
-export abstract class ConvenientObservable<T, TChange> implements IObservable<T, TChange> {
- get _change(): TChange { return null!; }
-
- public abstract get(): T;
- public abstract subscribe(observer: IObserver): void;
- public abstract unsubscribe(observer: IObserver): void;
-
- public read(reader: IReader): T {
- reader.handleBeforeReadObservable(this);
- return this.get();
- }
-
- public map<TNew>(fn: (value: T) => TNew): IObservable<TNew> {
- return new LazyDerived((reader) => fn(this.read(reader)), '(mapped)');
- }
-}
-
-export abstract class BaseObservable<T, TChange = void> extends ConvenientObservable<T, TChange> {
- protected readonly observers = new Set<IObserver>();
-
- public subscribe(observer: IObserver): void {
- const len = this.observers.size;
- this.observers.add(observer);
- if (len === 0) {
- this.onFirstObserverSubscribed();
- }
- }
-
- public unsubscribe(observer: IObserver): void {
- const deleted = this.observers.delete(observer);
- if (deleted && this.observers.size === 0) {
- this.onLastObserverUnsubscribed();
- }
- }
-
- protected onFirstObserverSubscribed(): void { }
- protected onLastObserverUnsubscribed(): void { }
-}
-
-export function transaction(fn: (tx: ITransaction) => void) {
- const tx = new TransactionImpl();
- try {
- fn(tx);
- } finally {
- tx.finish();
- }
-}
-
-class TransactionImpl implements ITransaction {
- private readonly finishActions = new Array<() => void>();
-
- public updateObserver(
- observer: IObserver,
- observable: IObservable<any>
- ): void {
- this.finishActions.push(function () {
- observer.endUpdate(observable);
- });
- observer.beginUpdate(observable);
- }
-
- public finish(): void {
- for (const action of this.finishActions) {
- action();
- }
- }
-}
-
-export class ObservableValue<T, TChange = void>
- extends BaseObservable<T, TChange>
- implements ISettable<T, TChange>
-{
- private value: T;
-
- constructor(initialValue: T, public readonly name: string) {
- super();
- this.value = initialValue;
- }
-
- public get(): T {
- return this.value;
- }
-
- public set(value: T, tx: ITransaction | undefined, change: TChange): void {
- if (this.value === value) {
- return;
- }
-
- if (!tx) {
- transaction((tx) => {
- this.set(value, tx, change);
- });
- return;
- }
-
- this.value = value;
-
- for (const observer of this.observers) {
- tx.updateObserver(observer, this);
- observer.handleChange(this, change);
- }
- }
-}
-
-export function constObservable<T>(value: T): IObservable<T> {
- return new ConstObservable(value);
-}
-
-class ConstObservable<T> extends ConvenientObservable<T, void> {
- constructor(private readonly value: T) {
- super();
- }
-
- public get(): T {
- return this.value;
- }
- public subscribe(observer: IObserver): void {
- // NO OP
- }
- public unsubscribe(observer: IObserver): void {
- // NO OP
- }
-}
-
-// == autorun ==
-export function autorun(fn: (reader: IReader) => void, name: string): IDisposable {
- return new AutorunObserver(fn, name, undefined);
-}
-
-interface IChangeContext {
- readonly changedObservable: IObservable<any, any>;
- readonly change: unknown;
-
- didChange<T, TChange>(observable: IObservable<T, TChange>): this is { change: TChange };
-}
-
-export function autorunHandleChanges(
- name: string,
- options: {
- /**
- * Returns if this change should cause a re-run of the autorun.
- */
- handleChange: (context: IChangeContext) => boolean;
- },
- fn: (reader: IReader) => void
-): IDisposable {
- return new AutorunObserver(fn, name, options.handleChange);
-}
-
-export function autorunWithStore(
- fn: (reader: IReader, store: DisposableStore) => void,
- name: string
-): IDisposable {
- const store = new DisposableStore();
- const disposable = autorun(
- reader => {
- store.clear();
- fn(reader, store);
- },
- name
- );
- return toDisposable(() => {
- disposable.dispose();
- store.dispose();
- });
-}
-
-export class AutorunObserver implements IObserver, IReader, IDisposable {
- public needsToRun = true;
- private updateCount = 0;
-
- /**
- * The actual dependencies.
- */
- private _dependencies = new Set<IObservable<any>>();
- public get dependencies() {
- return this._dependencies;
- }
-
- /**
- * Dependencies that have to be removed when {@link runFn} ran through.
- */
- private staleDependencies = new Set<IObservable<any>>();
-
- constructor(
- private readonly runFn: (reader: IReader) => void,
- public readonly name: string,
- private readonly _handleChange: ((context: IChangeContext) => boolean) | undefined
- ) {
- this.runIfNeeded();
- }
-
- public handleBeforeReadObservable<T>(observable: IObservable<T>) {
- this._dependencies.add(observable);
- if (!this.staleDependencies.delete(observable)) {
- observable.subscribe(this);
- }
- }
-
- public handleChange<T, TChange>(observable: IObservable<T, TChange>, change: TChange): void {
- const shouldReact = this._handleChange ? this._handleChange({
- changedObservable: observable,
- change,
- didChange: o => o === observable as any,
- }) : true;
- this.needsToRun = this.needsToRun || shouldReact;
-
- if (this.updateCount === 0) {
- this.runIfNeeded();
- }
- }
-
- public beginUpdate() {
- this.updateCount++;
- }
-
- public endUpdate() {
- this.updateCount--;
- if (this.updateCount === 0) {
- this.runIfNeeded();
- }
- }
-
- private runIfNeeded(): void {
- if (!this.needsToRun) {
- return;
- }
- // Assert: this.staleDependencies is an empty set.
- const emptySet = this.staleDependencies;
- this.staleDependencies = this._dependencies;
- this._dependencies = emptySet;
-
- this.needsToRun = false;
-
- try {
- this.runFn(this);
- } finally {
- // We don't want our observed observables to think that they are (not even temporarily) not being observed.
- // Thus, we only unsubscribe from observables that are definitely not read anymore.
- for (const o of this.staleDependencies) {
- o.unsubscribe(this);
- }
- this.staleDependencies.clear();
- }
- }
-
- public dispose() {
- for (const o of this._dependencies) {
- o.unsubscribe(this);
- }
- this._dependencies.clear();
- }
-}
-
-export namespace autorun {
- export const Observer = AutorunObserver;
-}
-export function autorunDelta<T>(
- name: string,
- observable: IObservable<T>,
- handler: (args: { lastValue: T | undefined; newValue: T }) => void
-): IDisposable {
- let _lastValue: T | undefined;
- return autorun((reader) => {
- const newValue = observable.read(reader);
- const lastValue = _lastValue;
- _lastValue = newValue;
- handler({ lastValue, newValue });
- }, name);
-}
-
-
-// == Lazy Derived ==
-
-export function derivedObservable<T>(name: string, computeFn: (reader: IReader) => T): IObservable<T> {
- return new LazyDerived(computeFn, name);
-}
-export class LazyDerived<T> extends ConvenientObservable<T, void> {
- private readonly observer: LazyDerivedObserver<T>;
-
- constructor(computeFn: (reader: IReader) => T, name: string) {
- super();
- this.observer = new LazyDerivedObserver(computeFn, name, this);
- }
-
- public subscribe(observer: IObserver): void {
- this.observer.subscribe(observer);
- }
-
- public unsubscribe(observer: IObserver): void {
- this.observer.unsubscribe(observer);
- }
-
- public override read(reader: IReader): T {
- return this.observer.read(reader);
- }
-
- public get(): T {
- return this.observer.get();
- }
-}
-
-/**
- * @internal
- */
-class LazyDerivedObserver<T>
- extends BaseObservable<T, void>
- implements IReader, IObserver {
- private hadValue = false;
- private hasValue = false;
- private value: T | undefined = undefined;
- private updateCount = 0;
-
- private _dependencies = new Set<IObservable<any>>();
- public get dependencies(): ReadonlySet<IObservable<any>> {
- return this._dependencies;
- }
-
- /**
- * Dependencies that have to be removed when {@link runFn} ran through.
- */
- private staleDependencies = new Set<IObservable<any>>();
-
- constructor(
- private readonly computeFn: (reader: IReader) => T,
- public readonly name: string,
- private readonly actualObservable: LazyDerived<T>,
- ) {
- super();
- }
-
- protected override onLastObserverUnsubscribed(): void {
- /**
- * We are not tracking changes anymore, thus we have to assume
- * that our cache is invalid.
- */
- this.hasValue = false;
- this.hadValue = false;
- this.value = undefined;
- for (const d of this._dependencies) {
- d.unsubscribe(this);
- }
- this._dependencies.clear();
- }
-
- public handleBeforeReadObservable<T>(observable: IObservable<T>) {
- this._dependencies.add(observable);
- if (!this.staleDependencies.delete(observable)) {
- observable.subscribe(this);
- }
- }
-
- public handleChange() {
- if (this.hasValue) {
- this.hadValue = true;
- this.hasValue = false;
- }
-
- // Not in transaction: Recompute & inform observers immediately
- if (this.updateCount === 0 && this.observers.size > 0) {
- this.get();
- }
-
- // Otherwise, recompute in `endUpdate` or on demand.
- }
-
- public beginUpdate() {
- if (this.updateCount === 0) {
- for (const r of this.observers) {
- r.beginUpdate(this);
- }
- }
- this.updateCount++;
- }
-
- public endUpdate() {
- this.updateCount--;
- if (this.updateCount === 0) {
- if (this.observers.size > 0) {
- // Propagate invalidation
- this.get();
- }
-
- for (const r of this.observers) {
- r.endUpdate(this);
- }
- }
- }
-
- public get(): T {
- if (this.observers.size === 0) {
- // Cache is not valid and don't refresh the cache.
- // Observables should not be read in non-reactive contexts.
- return this.computeFn(this);
- }
-
- if (this.updateCount > 0 && this.hasValue) {
- // Refresh dependencies
- for (const d of this._dependencies) {
- // Maybe `.get()` triggers `handleChange`?
- d.get();
- if (!this.hasValue) {
- // The other dependencies will refresh on demand
- break;
- }
- }
- }
-
- if (!this.hasValue) {
- const emptySet = this.staleDependencies;
- this.staleDependencies = this._dependencies;
- this._dependencies = emptySet;
-
- const oldValue = this.value;
- try {
- this.value = this.computeFn(this);
- } finally {
- // We don't want our observed observables to think that they are (not even temporarily) not being observed.
- // Thus, we only unsubscribe from observables that are definitely not read anymore.
- for (const o of this.staleDependencies) {
- o.unsubscribe(this);
- }
- this.staleDependencies.clear();
- }
-
- this.hasValue = true;
- if (this.hadValue && oldValue !== this.value) {
- for (const r of this.observers) {
- r.handleChange(this.actualObservable, undefined);
- }
- }
- }
- return this.value!;
- }
-}
-
-export namespace LazyDerived {
- export const Observer = LazyDerivedObserver;
-}
-
-export function observableFromPromise<T>(promise: Promise<T>): IObservable<{ value?: T }> {
- const observable = new ObservableValue<{ value?: T }>({}, 'promiseValue');
- promise.then((value) => {
- observable.set({ value }, undefined);
- });
- return observable;
-}
-
-export function waitForState<T, TState extends T>(observable: IObservable<T>, predicate: (state: T) => state is TState): Promise<TState>;
-export function waitForState<T>(observable: IObservable<T>, predicate: (state: T) => boolean): Promise<T>;
-export function waitForState<T>(observable: IObservable<T>, predicate: (state: T) => boolean): Promise<T> {
- return new Promise(resolve => {
- const d = autorun(reader => {
- const currentState = observable.read(reader);
- if (predicate(currentState)) {
- d.dispose();
- resolve(currentState);
- }
- }, 'waitForState');
- });
-}
-
-export function observableFromEvent<T, TArgs = unknown>(
- event: Event<TArgs>,
- getValue: (args: TArgs | undefined) => T
-): IObservable<T> {
- return new FromEventObservable(event, getValue);
-}
-
-class FromEventObservable<TArgs, T> extends BaseObservable<T> {
- private value: T | undefined;
- private hasValue = false;
- private subscription: IDisposable | undefined;
-
- constructor(
- private readonly event: Event<TArgs>,
- private readonly getValue: (args: TArgs | undefined) => T
- ) {
- super();
- }
-
- protected override onFirstObserverSubscribed(): void {
- this.subscription = this.event(this.handleEvent);
- }
-
- private readonly handleEvent = (args: TArgs | undefined) => {
- const newValue = this.getValue(args);
- if (this.value !== newValue) {
- this.value = newValue;
-
- if (this.hasValue) {
- transaction(tx => {
- for (const o of this.observers) {
- tx.updateObserver(o, this);
- o.handleChange(this, undefined);
- }
- });
- }
- this.hasValue = true;
- }
- };
-
- protected override onLastObserverUnsubscribed(): void {
- this.subscription!.dispose();
- this.subscription = undefined;
- this.hasValue = false;
- this.value = undefined;
- }
-
- public get(): T {
- if (this.subscription) {
- if (!this.hasValue) {
- this.handleEvent(undefined);
- }
- return this.value!;
- } else {
- // no cache, as there are no subscribers to clean it up
- return this.getValue(undefined);
- }
- }
-}
-
-export namespace observableFromEvent {
- export const Observer = FromEventObservable;
-}
-
-export function debouncedObservable<T>(observable: IObservable<T>, debounceMs: number, disposableStore: DisposableStore): IObservable<T | undefined> {
- const debouncedObservable = new ObservableValue<T | undefined>(undefined, 'debounced');
-
- let timeout: any = undefined;
-
- disposableStore.add(autorun(reader => {
- const value = observable.read(reader);
-
- if (timeout) {
- clearTimeout(timeout);
- }
- timeout = setTimeout(() => {
- transaction(tx => {
- debouncedObservable.set(value, tx);
- });
- }, debounceMs);
-
- }, 'debounce'));
-
- return debouncedObservable;
-}
-
-export function wasEventTriggeredRecently(event: Event<any>, timeoutMs: number, disposableStore: DisposableStore): IObservable<boolean> {
- const observable = new ObservableValue(false, 'triggeredRecently');
-
- let timeout: any = undefined;
-
- disposableStore.add(event(() => {
- observable.set(true, undefined);
-
- if (timeout) {
- clearTimeout(timeout);
- }
- timeout = setTimeout(() => {
- observable.set(false, undefined);
- }, timeoutMs);
- }));
-
- return observable;
-}
-
-/**
- * This ensures the observable is kept up-to-date.
- * This is useful when the observables `get` method is used.
-*/
-export function keepAlive(observable: IObservable<any>): IDisposable {
- return autorun(reader => {
- observable.read(reader);
- }, 'keep-alive');
-}
-
-export function derivedObservableWithCache<T>(name: string, computeFn: (reader: IReader, lastValue: T | undefined) => T): IObservable<T> {
- let lastValue: T | undefined = undefined;
- const observable = derivedObservable(name, reader => {
- lastValue = computeFn(reader, lastValue);
- return lastValue;
- });
- return observable;
-}
-
-export function derivedObservableWithWritableCache<T>(name: string, computeFn: (reader: IReader, lastValue: T | undefined) => T): IObservable<T> & { clearCache(transaction: ITransaction): void } {
- let lastValue: T | undefined = undefined;
- const counter = new ObservableValue(0, 'counter');
- const observable = derivedObservable(name, reader => {
- counter.read(reader);
- lastValue = computeFn(reader, lastValue);
- return lastValue;
- });
- return Object.assign(observable, {
- clearCache: (transaction: ITransaction) => {
- lastValue = undefined;
- counter.set(counter.get() + 1, transaction);
- },
- });
-}
diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkCellEdits.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkCellEdits.ts
index 1f533a82ccc..0e870b7b639 100644
--- a/src/vs/workbench/contrib/bulkEdit/browser/bulkCellEdits.ts
+++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkCellEdits.ts
@@ -6,20 +6,36 @@
import { groupBy } from 'vs/base/common/arrays';
import { CancellationToken } from 'vs/base/common/cancellation';
import { compare } from 'vs/base/common/strings';
+import { isObject } from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
import { ResourceEdit } from 'vs/editor/browser/services/bulkEditService';
import { WorkspaceEditMetadata } from 'vs/editor/common/languages';
import { IProgress } from 'vs/platform/progress/common/progress';
import { UndoRedoGroup, UndoRedoSource } from 'vs/platform/undoRedo/common/undoRedo';
-import { ICellEditOperation } from 'vs/workbench/contrib/notebook/common/notebookCommon';
+import { ICellPartialMetadataEdit, ICellReplaceEdit, IDocumentMetadataEdit, IWorkspaceNotebookCellEdit } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService';
-export class ResourceNotebookCellEdit extends ResourceEdit {
+export class ResourceNotebookCellEdit extends ResourceEdit implements IWorkspaceNotebookCellEdit {
+
+ static is(candidate: any): candidate is IWorkspaceNotebookCellEdit {
+ if (candidate instanceof ResourceNotebookCellEdit) {
+ return true;
+ }
+ return URI.isUri((<IWorkspaceNotebookCellEdit>candidate).resource)
+ && isObject((<IWorkspaceNotebookCellEdit>candidate).cellEdit);
+ }
+
+ static lift(edit: IWorkspaceNotebookCellEdit): ResourceNotebookCellEdit {
+ if (edit instanceof ResourceNotebookCellEdit) {
+ return edit;
+ }
+ return new ResourceNotebookCellEdit(edit.resource, edit.cellEdit, edit.notebookVersionId, edit.metadata);
+ }
constructor(
readonly resource: URI,
- readonly cellEdit: ICellEditOperation,
- readonly versionId?: number,
+ readonly cellEdit: ICellPartialMetadataEdit | IDocumentMetadataEdit | ICellReplaceEdit,
+ readonly notebookVersionId: number | undefined = undefined,
metadata?: WorkspaceEditMetadata
) {
super(metadata);
@@ -49,7 +65,7 @@ export class BulkCellEdits {
const ref = await this._notebookModelService.resolve(first.resource);
// check state
- if (typeof first.versionId === 'number' && ref.object.notebook.versionId !== first.versionId) {
+ if (typeof first.notebookVersionId === 'number' && ref.object.notebook.versionId !== first.notebookVersionId) {
ref.dispose();
throw new Error(`Notebook '${first.resource}' has changed in the meantime`);
}
diff --git a/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts b/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts
index f69e60201f2..6906a0b4fb7 100644
--- a/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts
+++ b/src/vs/workbench/contrib/bulkEdit/browser/bulkTextEdits.ts
@@ -19,8 +19,9 @@ import { ResourceMap } from 'vs/base/common/map';
import { IModelService } from 'vs/editor/common/services/model';
import { ResourceTextEdit } from 'vs/editor/browser/services/bulkEditService';
import { CancellationToken } from 'vs/base/common/cancellation';
-import { performSnippetEdits } from 'vs/editor/contrib/snippet/browser/snippetController2';
+import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2';
import { SnippetParser } from 'vs/editor/contrib/snippet/browser/snippetParser';
+import { ISnippetEdit } from 'vs/editor/contrib/snippet/browser/snippetSession';
type ValidationResult = { canApply: true } | { canApply: false; reason: URI };
@@ -115,7 +116,7 @@ class ModelEditTask implements IDisposable {
if (!edit.text) {
return edit;
}
- const text = new SnippetParser().parse(edit.text, false, false).toString();
+ const text = new SnippetParser().text(edit.text);
return { ...edit, insertAsSnippet: false, text };
}
}
@@ -141,14 +142,24 @@ class EditorEditTask extends ModelEditTask {
super.apply();
return;
}
- if (this._edits.length > 0) {
- const insertAsSnippet = this._edits.every(edit => edit.insertAsSnippet);
- if (insertAsSnippet) {
- // todo@jrieken what ABOUT EOL?
- performSnippetEdits(this._editor, this._edits.map(edit => ({ range: Range.lift(edit.range!), snippet: edit.text! })));
+ if (this._edits.length > 0) {
+ const snippetCtrl = SnippetController2.get(this._editor);
+ if (snippetCtrl && this._edits.some(edit => edit.insertAsSnippet)) {
+ // some edit is a snippet edit -> use snippet controller and ISnippetEdits
+ const snippetEdits: ISnippetEdit[] = [];
+ for (const edit of this._edits) {
+ if (edit.range && edit.text !== null) {
+ snippetEdits.push({
+ range: Range.lift(edit.range),
+ template: edit.insertAsSnippet ? edit.text : SnippetParser.escape(edit.text)
+ });
+ }
+ }
+ snippetCtrl.apply(snippetEdits);
} else {
+ // normal edit
this._edits = this._edits
.map(this._transformSnippetStringToInsertText, this) // mixed edits (snippet and normal) -> no snippet mode
.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range));
@@ -222,13 +233,13 @@ export class BulkTextEdits {
let makeMinimal = false;
if (this._editor?.getModel()?.uri.toString() === ref.object.textEditorModel.uri.toString()) {
task = new EditorEditTask(ref, this._editor);
- makeMinimal = true && false; // todo@jrieken HACK
+ makeMinimal = true;
} else {
task = new ModelEditTask(ref);
}
for (const edit of value) {
- if (makeMinimal) {
+ if (makeMinimal && !edit.textEdit.insertAsSnippet) {
const newEdits = await this._editorWorker.computeMoreMinimalEdits(edit.resource, [edit.textEdit]);
if (!newEdits) {
task.addEdit(edit);
diff --git a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts
index 922bf591bf7..5f44dc67e26 100644
--- a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts
+++ b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsOutline.ts
@@ -403,8 +403,7 @@ class DocumentSymbolsOutlineCreator implements IOutlineCreator<IEditorPane, Docu
readonly dispose: () => void;
constructor(
- @IOutlineService outlineService: IOutlineService,
- @IInstantiationService private readonly _instantiationService: IInstantiationService,
+ @IOutlineService outlineService: IOutlineService
) {
const reg = outlineService.registerOutlineCreator(this);
this.dispose = () => reg.dispose();
@@ -427,7 +426,7 @@ class DocumentSymbolsOutlineCreator implements IOutlineCreator<IEditorPane, Docu
return undefined;
}
const firstLoadBarrier = new Barrier();
- const result = this._instantiationService.createInstance(DocumentSymbolsOutline, editor, target, firstLoadBarrier);
+ const result = editor.invokeWithinContext(accessor => accessor.get(IInstantiationService).createInstance(DocumentSymbolsOutline, editor!, target, firstLoadBarrier));
await firstLoadBarrier.wait();
return result;
}
diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts
index d1d118f5c15..450c76ae8ad 100644
--- a/src/vs/workbench/contrib/comments/browser/commentNode.ts
+++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts
@@ -382,7 +382,7 @@ export class CommentNode<T extends IRange | ICellRange> extends Disposable {
private createCommentEditor(editContainer: HTMLElement): void {
const container = dom.append(editContainer, dom.$('.edit-textarea'));
- this._commentEditor = this.instantiationService.createInstance(SimpleCommentEditor, container, SimpleCommentEditor.getEditorOptions(), this.parentThread);
+ this._commentEditor = this.instantiationService.createInstance(SimpleCommentEditor, container, SimpleCommentEditor.getEditorOptions(this.configurationService), this.parentThread);
const resource = URI.parse(`comment:commentinput-${this.comment.uniqueIdInThread}-${Date.now()}.md`);
this._commentEditorModel = this.modelService.createModel('', this.languageService.createByFilepathOrFirstLine(resource), resource, false);
diff --git a/src/vs/workbench/contrib/comments/browser/commentReply.ts b/src/vs/workbench/contrib/comments/browser/commentReply.ts
index 0905db307c3..68eda2e6e39 100644
--- a/src/vs/workbench/contrib/comments/browser/commentReply.ts
+++ b/src/vs/workbench/contrib/comments/browser/commentReply.ts
@@ -17,6 +17,7 @@ import { ILanguageService } from 'vs/editor/common/languages/language';
import { ITextModel } from 'vs/editor/common/model';
import { IModelService } from 'vs/editor/common/services/model';
import * as nls from 'vs/nls';
+import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { editorForeground, resolveColorValue } from 'vs/platform/theme/common/colorRegistry';
@@ -58,11 +59,12 @@ export class CommentReply<T extends IRange | ICellRange> extends Disposable {
@ILanguageService private languageService: ILanguageService,
@IModelService private modelService: IModelService,
@IThemeService private themeService: IThemeService,
+ @IConfigurationService configurationService: IConfigurationService
) {
super();
this.form = dom.append(container, dom.$('.comment-form'));
- this.commentEditor = this._register(this._scopedInstatiationService.createInstance(SimpleCommentEditor, this.form, SimpleCommentEditor.getEditorOptions(), this._parentThread));
+ this.commentEditor = this._register(this._scopedInstatiationService.createInstance(SimpleCommentEditor, this.form, SimpleCommentEditor.getEditorOptions(configurationService), this._parentThread));
this.commentEditorIsEmpty = CommentContextKeys.commentIsEmpty.bindTo(this._contextKeyService);
this.commentEditorIsEmpty.set(!this._pendingComment);
@@ -216,8 +218,8 @@ export class CommentReply<T extends IRange | ICellRange> extends Disposable {
this._commentThreadDisposables.push(this._commentThread.onDidChangeInput(input => {
const thread = this._commentThread;
-
- if (thread.input && thread.input.uri !== commentEditor.getModel()!.uri) {
+ const model = commentEditor.getModel();
+ if (thread.input && model && (thread.input.uri !== model.uri)) {
return;
}
if (!input) {
diff --git a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts
index a0e155d0bdd..41ed64d6d55 100644
--- a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts
+++ b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts
@@ -382,6 +382,7 @@ export class CommentController implements IEditorContribution {
this._commentingRangeDecorator.update(this.editor, []);
this._commentThreadRangeDecorator.update(this.editor, []);
dispose(this._commentWidgets);
+ this._commentWidgets = [];
}
}));
@@ -859,7 +860,7 @@ export class CommentController implements IEditorContribution {
}
const options = this.editor.getOptions();
- if (options.get(EditorOption.folding)) {
+ if (options.get(EditorOption.folding) && options.get(EditorOption.showFoldingControls) !== 'never') {
lineDecorationsWidth -= 16;
}
lineDecorationsWidth += 9;
diff --git a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts
index 3166fd47195..020ef7eb4d1 100644
--- a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts
+++ b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts
@@ -188,9 +188,21 @@ export class CommentNodeRenderer implements IListRenderer<ITreeNode<CommentNode>
return renderedComment;
}
+ private getIcon(commentCount: number, threadState?: CommentThreadState): Codicon {
+ if (threadState === CommentThreadState.Unresolved) {
+ return Codicon.commentUnresolved;
+ } else if (commentCount === 1) {
+ return Codicon.comment;
+ } else {
+ return Codicon.commentDiscussion;
+ }
+ }
+
renderElement(node: ITreeNode<CommentNode>, index: number, templateData: ICommentThreadTemplateData, height: number | undefined): void {
const commentCount = node.element.replies.length + 1;
- templateData.threadMetadata.icon?.classList.add(...ThemeIcon.asClassNameArray((commentCount === 1) ? Codicon.comment : Codicon.commentDiscussion));
+ templateData.threadMetadata.icon.classList.remove(...Array.from(templateData.threadMetadata.icon.classList.values())
+ .filter(value => value.startsWith('codicon')));
+ templateData.threadMetadata.icon.classList.add(...ThemeIcon.asClassNameArray(this.getIcon(commentCount, node.element.threadState)));
if (node.element.threadState !== undefined) {
const color = this.getCommentThreadWidgetStateColor(node.element.threadState, this.themeService.getColorTheme());
templateData.threadMetadata.icon.style.setProperty(commentViewThreadStateColorVar, `${color}`);
diff --git a/src/vs/workbench/contrib/comments/browser/commentsView.ts b/src/vs/workbench/contrib/comments/browser/commentsView.ts
index d6883919fa6..4c38db0a895 100644
--- a/src/vs/workbench/contrib/comments/browser/commentsView.ts
+++ b/src/vs/workbench/contrib/comments/browser/commentsView.ts
@@ -234,7 +234,7 @@ export class CommentsPanel extends ViewPane {
const commentToReveal = element instanceof ResourceWithCommentThreads ? element.commentThreads[0].comment.uniqueIdInThread : element.comment.uniqueIdInThread;
if (threadToReveal && isCodeEditor(editor)) {
const controller = CommentController.get(editor);
- controller?.revealCommentThread(threadToReveal, commentToReveal, false);
+ controller?.revealCommentThread(threadToReveal, commentToReveal, true);
}
return true;
diff --git a/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts b/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts
index 4a69955fd4a..c4ec1bbf5e3 100644
--- a/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts
+++ b/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts
@@ -24,6 +24,7 @@ import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/comme
import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/commentContextKeys';
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
+import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
export const ctxCommentEditorFocused = new RawContextKey<boolean>('commentEditorFocused', false);
@@ -79,7 +80,7 @@ export class SimpleCommentEditor extends CodeEditorWidget {
return EditorExtensionsRegistry.getEditorActions();
}
- public static getEditorOptions(): IEditorOptions {
+ public static getEditorOptions(configurationService: IConfigurationService): IEditorOptions {
return {
wordWrap: 'on',
glyphMargin: false,
@@ -103,6 +104,7 @@ export class SimpleCommentEditor extends CodeEditorWidget {
minimap: {
enabled: false
},
+ autoClosingBrackets: configurationService.getValue('editor.autoClosingBrackets'),
quickSuggestions: false
};
}
diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts
index 2eb3d65a818..b97d040674f 100644
--- a/src/vs/workbench/contrib/debug/browser/callStackView.ts
+++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts
@@ -21,7 +21,7 @@ import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'
import { posix } from 'vs/base/common/path';
import { commonSuffixLength } from 'vs/base/common/strings';
import { localize } from 'vs/nls';
-import { Icon } from 'vs/platform/action/common/action';
+import { ICommandActionTitle, Icon } from 'vs/platform/action/common/action';
import { createAndFillInActionBarActions, createAndFillInContextMenuActions, MenuEntryActionViewItem, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { IMenuService, MenuId, MenuItemAction, MenuRegistry, registerAction2, SubmenuItemAction } from 'vs/platform/actions/common/actions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@@ -1120,7 +1120,7 @@ registerAction2(class Collapse extends ViewAction<CallStackView> {
}
});
-function registerCallStackInlineMenuItem(id: string, title: string, icon: Icon, when: ContextKeyExpression, order: number, precondition?: ContextKeyExpression): void {
+function registerCallStackInlineMenuItem(id: string, title: string | ICommandActionTitle, icon: Icon, when: ContextKeyExpression, order: number, precondition?: ContextKeyExpression): void {
MenuRegistry.appendMenuItem(MenuId.DebugCallStackContext, {
group: 'inline',
order,
diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts
index 11584519c47..54d0f0a0e7c 100644
--- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts
+++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts
@@ -20,7 +20,7 @@ import {
} from 'vs/workbench/contrib/debug/common/debug';
import { DebugToolBar } from 'vs/workbench/contrib/debug/browser/debugToolBar';
import { DebugService } from 'vs/workbench/contrib/debug/browser/debugService';
-import { ADD_CONFIGURATION_ID, TOGGLE_INLINE_BREAKPOINT_ID, COPY_STACK_TRACE_ID, RESTART_SESSION_ID, TERMINATE_THREAD_ID, STEP_OVER_ID, STEP_INTO_ID, STEP_OUT_ID, PAUSE_ID, DISCONNECT_ID, STOP_ID, RESTART_FRAME_ID, CONTINUE_ID, FOCUS_REPL_ID, JUMP_TO_CURSOR_ID, RESTART_LABEL, STEP_INTO_LABEL, STEP_OVER_LABEL, STEP_OUT_LABEL, PAUSE_LABEL, DISCONNECT_LABEL, STOP_LABEL, CONTINUE_LABEL, DEBUG_START_LABEL, DEBUG_START_COMMAND_ID, DEBUG_RUN_LABEL, DEBUG_RUN_COMMAND_ID, EDIT_EXPRESSION_COMMAND_ID, REMOVE_EXPRESSION_COMMAND_ID, SELECT_AND_START_ID, SELECT_AND_START_LABEL, SET_EXPRESSION_COMMAND_ID, DISCONNECT_AND_SUSPEND_ID, DISCONNECT_AND_SUSPEND_LABEL, NEXT_DEBUG_CONSOLE_ID, NEXT_DEBUG_CONSOLE_LABEL, PREV_DEBUG_CONSOLE_ID, PREV_DEBUG_CONSOLE_LABEL, OPEN_LOADED_SCRIPTS_LABEL, SHOW_LOADED_SCRIPTS_ID, DEBUG_QUICK_ACCESS_PREFIX, DEBUG_CONSOLE_QUICK_ACCESS_PREFIX, SELECT_DEBUG_CONSOLE_ID, SELECT_DEBUG_CONSOLE_LABEL, STEP_INTO_TARGET_LABEL, STEP_INTO_TARGET_ID } from 'vs/workbench/contrib/debug/browser/debugCommands';
+import { ADD_CONFIGURATION_ID, TOGGLE_INLINE_BREAKPOINT_ID, COPY_STACK_TRACE_ID, RESTART_SESSION_ID, TERMINATE_THREAD_ID, STEP_OVER_ID, STEP_INTO_ID, STEP_OUT_ID, PAUSE_ID, DISCONNECT_ID, STOP_ID, RESTART_FRAME_ID, CONTINUE_ID, FOCUS_REPL_ID, JUMP_TO_CURSOR_ID, RESTART_LABEL, STEP_INTO_LABEL, STEP_OVER_LABEL, STEP_OUT_LABEL, PAUSE_LABEL, DISCONNECT_LABEL, STOP_LABEL, CONTINUE_LABEL, DEBUG_START_LABEL, DEBUG_START_COMMAND_ID, DEBUG_RUN_LABEL, DEBUG_RUN_COMMAND_ID, EDIT_EXPRESSION_COMMAND_ID, REMOVE_EXPRESSION_COMMAND_ID, SELECT_AND_START_ID, SELECT_AND_START_LABEL, SET_EXPRESSION_COMMAND_ID, DISCONNECT_AND_SUSPEND_ID, DISCONNECT_AND_SUSPEND_LABEL, NEXT_DEBUG_CONSOLE_ID, NEXT_DEBUG_CONSOLE_LABEL, PREV_DEBUG_CONSOLE_ID, PREV_DEBUG_CONSOLE_LABEL, OPEN_LOADED_SCRIPTS_LABEL, SHOW_LOADED_SCRIPTS_ID, DEBUG_QUICK_ACCESS_PREFIX, DEBUG_CONSOLE_QUICK_ACCESS_PREFIX, SELECT_DEBUG_CONSOLE_ID, SELECT_DEBUG_CONSOLE_LABEL, STEP_INTO_TARGET_LABEL, STEP_INTO_TARGET_ID, CALLSTACK_TOP_ID, CALLSTACK_TOP_LABEL, CALLSTACK_BOTTOM_LABEL, CALLSTACK_UP_LABEL, CALLSTACK_BOTTOM_ID, CALLSTACK_UP_ID, CALLSTACK_DOWN_ID, CALLSTACK_DOWN_LABEL, DEBUG_COMMAND_CATEGORY } from 'vs/workbench/contrib/debug/browser/debugCommands';
import { StatusBarColorProvider } from 'vs/workbench/contrib/debug/browser/statusbarColorProvider';
import { IViewsRegistry, Extensions as ViewExtensions, IViewContainersRegistry, ViewContainerLocation, ViewContainer } from 'vs/workbench/common/views';
import { isMacintosh, isWeb } from 'vs/base/common/platform';
@@ -55,7 +55,7 @@ import { DisassemblyView, DisassemblyViewContribution } from 'vs/workbench/contr
import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor';
import { DisassemblyViewInput } from 'vs/workbench/contrib/debug/common/disassemblyViewInput';
import { DebugLifecycle } from 'vs/workbench/contrib/debug/common/debugLifecycle';
-import { Icon } from 'vs/platform/action/common/action';
+import { ICommandActionTitle, Icon } from 'vs/platform/action/common/action';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { DebugConsoleQuickAccess } from 'vs/workbench/contrib/debug/browser/debugConsoleQuickAccess';
@@ -98,20 +98,21 @@ registerEditorContribution('editor.contrib.callStack', CallStackEditorContributi
registerEditorContribution(BREAKPOINT_EDITOR_CONTRIBUTION_ID, BreakpointEditorContribution);
registerEditorContribution(EDITOR_CONTRIBUTION_ID, DebugEditorContribution);
-const registerDebugCommandPaletteItem = (id: string, title: string, when?: ContextKeyExpression, precondition?: ContextKeyExpression) => {
+const registerDebugCommandPaletteItem = (id: string, title: ICommandActionTitle, when?: ContextKeyExpression, precondition?: ContextKeyExpression) => {
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
when: ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, when),
group: debugCategory,
command: {
id,
- title: `Debug: ${title}`,
+ title,
+ category: DEBUG_COMMAND_CATEGORY,
precondition
}
});
};
registerDebugCommandPaletteItem(RESTART_SESSION_ID, RESTART_LABEL);
-registerDebugCommandPaletteItem(TERMINATE_THREAD_ID, nls.localize('terminateThread', "Terminate Thread"), CONTEXT_IN_DEBUG_MODE);
+registerDebugCommandPaletteItem(TERMINATE_THREAD_ID, { value: nls.localize('terminateThread', "Terminate Thread"), original: 'Terminate Thread' }, CONTEXT_IN_DEBUG_MODE);
registerDebugCommandPaletteItem(STEP_OVER_ID, STEP_OVER_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped'));
registerDebugCommandPaletteItem(STEP_INTO_ID, STEP_INTO_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped'));
registerDebugCommandPaletteItem(STEP_INTO_TARGET_ID, STEP_INTO_TARGET_LABEL, CONTEXT_IN_DEBUG_MODE, ContextKeyExpr.and(CONTEXT_STEP_INTO_TARGETS_SUPPORTED, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped')));
@@ -121,13 +122,13 @@ registerDebugCommandPaletteItem(DISCONNECT_ID, DISCONNECT_LABEL, CONTEXT_IN_DEBU
registerDebugCommandPaletteItem(DISCONNECT_AND_SUSPEND_ID, DISCONNECT_AND_SUSPEND_LABEL, CONTEXT_IN_DEBUG_MODE, ContextKeyExpr.or(CONTEXT_FOCUSED_SESSION_IS_ATTACH, ContextKeyExpr.and(CONTEXT_SUSPEND_DEBUGGEE_SUPPORTED, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED)));
registerDebugCommandPaletteItem(STOP_ID, STOP_LABEL, CONTEXT_IN_DEBUG_MODE, ContextKeyExpr.or(CONTEXT_FOCUSED_SESSION_IS_ATTACH.toNegated(), CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED));
registerDebugCommandPaletteItem(CONTINUE_ID, CONTINUE_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped'));
-registerDebugCommandPaletteItem(FOCUS_REPL_ID, nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugFocusConsole' }, 'Focus on Debug Console View'));
-registerDebugCommandPaletteItem(JUMP_TO_CURSOR_ID, nls.localize('jumpToCursor', "Jump to Cursor"), CONTEXT_JUMP_TO_CURSOR_SUPPORTED);
-registerDebugCommandPaletteItem(JUMP_TO_CURSOR_ID, nls.localize('SetNextStatement', "Set Next Statement"), CONTEXT_JUMP_TO_CURSOR_SUPPORTED);
-registerDebugCommandPaletteItem(RunToCursorAction.ID, RunToCursorAction.LABEL, ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped')));
-registerDebugCommandPaletteItem(SelectionToReplAction.ID, SelectionToReplAction.LABEL, ContextKeyExpr.and(EditorContextKeys.hasNonEmptySelection, CONTEXT_IN_DEBUG_MODE));
-registerDebugCommandPaletteItem(SelectionToWatchExpressionsAction.ID, SelectionToWatchExpressionsAction.LABEL, ContextKeyExpr.and(EditorContextKeys.hasNonEmptySelection, CONTEXT_IN_DEBUG_MODE));
-registerDebugCommandPaletteItem(TOGGLE_INLINE_BREAKPOINT_ID, nls.localize('inlineBreakpoint', "Inline Breakpoint"));
+registerDebugCommandPaletteItem(FOCUS_REPL_ID, { value: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugFocusConsole' }, 'Focus on Debug Console View'), original: 'Focus on Debug Console View' });
+registerDebugCommandPaletteItem(JUMP_TO_CURSOR_ID, { value: nls.localize('jumpToCursor', "Jump to Cursor"), original: 'Jump to Cursor' }, CONTEXT_JUMP_TO_CURSOR_SUPPORTED);
+registerDebugCommandPaletteItem(JUMP_TO_CURSOR_ID, { value: nls.localize('SetNextStatement', "Set Next Statement"), original: 'Set Next Statement' }, CONTEXT_JUMP_TO_CURSOR_SUPPORTED);
+registerDebugCommandPaletteItem(RunToCursorAction.ID, { value: RunToCursorAction.LABEL, original: 'Run to Cursor' }, ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped')));
+registerDebugCommandPaletteItem(SelectionToReplAction.ID, { value: SelectionToReplAction.LABEL, original: 'Evaluate in Debug Console' }, ContextKeyExpr.and(EditorContextKeys.hasNonEmptySelection, CONTEXT_IN_DEBUG_MODE));
+registerDebugCommandPaletteItem(SelectionToWatchExpressionsAction.ID, { value: SelectionToWatchExpressionsAction.LABEL, original: 'Add to Watch' }, ContextKeyExpr.and(EditorContextKeys.hasNonEmptySelection, CONTEXT_IN_DEBUG_MODE));
+registerDebugCommandPaletteItem(TOGGLE_INLINE_BREAKPOINT_ID, { value: nls.localize('inlineBreakpoint', "Inline Breakpoint"), original: 'Inline Breakpoint' });
registerDebugCommandPaletteItem(DEBUG_START_COMMAND_ID, DEBUG_START_LABEL, ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE.notEqualsTo(getStateLabel(State.Initializing))));
registerDebugCommandPaletteItem(DEBUG_RUN_COMMAND_ID, DEBUG_RUN_LABEL, ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE.notEqualsTo(getStateLabel(State.Initializing))));
registerDebugCommandPaletteItem(SELECT_AND_START_ID, SELECT_AND_START_LABEL, ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE.notEqualsTo(getStateLabel(State.Initializing))));
@@ -135,10 +136,13 @@ registerDebugCommandPaletteItem(NEXT_DEBUG_CONSOLE_ID, NEXT_DEBUG_CONSOLE_LABEL)
registerDebugCommandPaletteItem(PREV_DEBUG_CONSOLE_ID, PREV_DEBUG_CONSOLE_LABEL);
registerDebugCommandPaletteItem(SHOW_LOADED_SCRIPTS_ID, OPEN_LOADED_SCRIPTS_LABEL, CONTEXT_IN_DEBUG_MODE);
registerDebugCommandPaletteItem(SELECT_DEBUG_CONSOLE_ID, SELECT_DEBUG_CONSOLE_LABEL);
-
+registerDebugCommandPaletteItem(CALLSTACK_TOP_ID, CALLSTACK_TOP_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped'));
+registerDebugCommandPaletteItem(CALLSTACK_BOTTOM_ID, CALLSTACK_BOTTOM_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped'));
+registerDebugCommandPaletteItem(CALLSTACK_UP_ID, CALLSTACK_UP_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped'));
+registerDebugCommandPaletteItem(CALLSTACK_DOWN_ID, CALLSTACK_DOWN_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped'));
// Debug callstack context menu
-const registerDebugViewMenuItem = (menuId: MenuId, id: string, title: string, order: number, when?: ContextKeyExpression, precondition?: ContextKeyExpression, group = 'navigation', icon?: Icon) => {
+const registerDebugViewMenuItem = (menuId: MenuId, id: string, title: string | ICommandActionTitle, order: number, when?: ContextKeyExpression, precondition?: ContextKeyExpression, group = 'navigation', icon?: Icon) => {
MenuRegistry.appendMenuItem(menuId, {
group,
when,
@@ -186,7 +190,7 @@ registerDebugViewMenuItem(MenuId.DebugWatchContext, REMOVE_WATCH_EXPRESSIONS_COM
// Touch Bar
if (isMacintosh) {
- const registerTouchBarEntry = (id: string, title: string, order: number, when: ContextKeyExpression | undefined, iconUri: URI) => {
+ const registerTouchBarEntry = (id: string, title: string | ICommandActionTitle, order: number, when: ContextKeyExpression | undefined, iconUri: URI) => {
MenuRegistry.appendMenuItem(MenuId.TouchBarContext, {
command: {
id,
diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts
index 49eb8197d7a..596bbd9c32d 100644
--- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts
+++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts
@@ -9,7 +9,7 @@ import { List } from 'vs/base/browser/ui/list/listWidget';
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IListService } from 'vs/platform/list/browser/listService';
import { IDebugService, IEnablement, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_VARIABLES_FOCUSED, EDITOR_CONTRIBUTION_ID, IDebugEditorContribution, CONTEXT_IN_DEBUG_MODE, CONTEXT_EXPRESSION_SELECTED, IConfig, IStackFrame, IThread, IDebugSession, CONTEXT_DEBUG_STATE, IDebugConfiguration, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, REPL_VIEW_ID, CONTEXT_DEBUGGERS_AVAILABLE, State, getStateLabel, CONTEXT_BREAKPOINT_INPUT_FOCUSED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, VIEWLET_ID, CONTEXT_DISASSEMBLY_VIEW_FOCUS, CONTEXT_IN_DEBUG_REPL, CONTEXT_STEP_INTO_TARGETS_SUPPORTED } from 'vs/workbench/contrib/debug/common/debug';
-import { Expression, Variable, Breakpoint, FunctionBreakpoint, DataBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel';
+import { Expression, Variable, Breakpoint, FunctionBreakpoint, DataBreakpoint, Thread } from 'vs/workbench/contrib/debug/common/debugModel';
import { IExtensionsViewPaneContainer, VIEWLET_ID as EXTENSIONS_VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions';
import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
@@ -64,27 +64,36 @@ export const REMOVE_EXPRESSION_COMMAND_ID = 'debug.removeWatchExpression';
export const NEXT_DEBUG_CONSOLE_ID = 'workbench.action.debug.nextConsole';
export const PREV_DEBUG_CONSOLE_ID = 'workbench.action.debug.prevConsole';
export const SHOW_LOADED_SCRIPTS_ID = 'workbench.action.debug.showLoadedScripts';
-
-export const RESTART_LABEL = nls.localize('restartDebug', "Restart");
-export const STEP_OVER_LABEL = nls.localize('stepOverDebug', "Step Over");
-export const STEP_INTO_LABEL = nls.localize('stepIntoDebug', "Step Into");
-export const STEP_INTO_TARGET_LABEL = nls.localize('stepIntoTargetDebug', "Step Into Target");
-export const STEP_OUT_LABEL = nls.localize('stepOutDebug', "Step Out");
-export const PAUSE_LABEL = nls.localize('pauseDebug', "Pause");
-export const DISCONNECT_LABEL = nls.localize('disconnect', "Disconnect");
-export const DISCONNECT_AND_SUSPEND_LABEL = nls.localize('disconnectSuspend', "Disconnect and Suspend");
-export const STOP_LABEL = nls.localize('stop', "Stop");
-export const CONTINUE_LABEL = nls.localize('continueDebug', "Continue");
-export const FOCUS_SESSION_LABEL = nls.localize('focusSession', "Focus Session");
-export const SELECT_AND_START_LABEL = nls.localize('selectAndStartDebugging', "Select and Start Debugging");
+export const CALLSTACK_TOP_ID = 'workbench.action.debug.callStackTop';
+export const CALLSTACK_BOTTOM_ID = 'workbench.action.debug.callStackBottom';
+export const CALLSTACK_UP_ID = 'workbench.action.debug.callStackUp';
+export const CALLSTACK_DOWN_ID = 'workbench.action.debug.callStackDown';
+
+export const DEBUG_COMMAND_CATEGORY = 'Debug';
+export const RESTART_LABEL = { value: nls.localize('restartDebug', "Restart"), original: 'Restart' };
+export const STEP_OVER_LABEL = { value: nls.localize('stepOverDebug', "Step Over"), original: 'Step Over' };
+export const STEP_INTO_LABEL = { value: nls.localize('stepIntoDebug', "Step Into"), original: 'Step Into' };
+export const STEP_INTO_TARGET_LABEL = { value: nls.localize('stepIntoTargetDebug', "Step Into Target"), original: 'Step Into Target' };
+export const STEP_OUT_LABEL = { value: nls.localize('stepOutDebug', "Step Out"), original: 'Step Out' };
+export const PAUSE_LABEL = { value: nls.localize('pauseDebug', "Pause"), original: 'Pause' };
+export const DISCONNECT_LABEL = { value: nls.localize('disconnect', "Disconnect"), original: 'Disconnect' };
+export const DISCONNECT_AND_SUSPEND_LABEL = { value: nls.localize('disconnectSuspend', "Disconnect and Suspend"), original: 'Disconnect and Suspend' };
+export const STOP_LABEL = { value: nls.localize('stop', "Stop"), original: 'Stop' };
+export const CONTINUE_LABEL = { value: nls.localize('continueDebug', "Continue"), original: 'Continue' };
+export const FOCUS_SESSION_LABEL = { value: nls.localize('focusSession', "Focus Session"), original: 'Focus Session' };
+export const SELECT_AND_START_LABEL = { value: nls.localize('selectAndStartDebugging', "Select and Start Debugging"), original: 'Select and Start Debugging' };
export const DEBUG_CONFIGURE_LABEL = nls.localize('openLaunchJson', "Open '{0}'", 'launch.json');
-export const DEBUG_START_LABEL = nls.localize('startDebug', "Start Debugging");
-export const DEBUG_RUN_LABEL = nls.localize('startWithoutDebugging', "Start Without Debugging");
-export const NEXT_DEBUG_CONSOLE_LABEL = nls.localize('nextDebugConsole', "Focus Next Debug Console");
-export const PREV_DEBUG_CONSOLE_LABEL = nls.localize('prevDebugConsole', "Focus Previous Debug Console");
-export const OPEN_LOADED_SCRIPTS_LABEL = nls.localize('openLoadedScript', "Open Loaded Script...");
-
-export const SELECT_DEBUG_CONSOLE_LABEL = nls.localize('selectDebugConsole', "Select Debug Console");
+export const DEBUG_START_LABEL = { value: nls.localize('startDebug', "Start Debugging"), original: 'Start Debugging' };
+export const DEBUG_RUN_LABEL = { value: nls.localize('startWithoutDebugging', "Start Without Debugging"), original: 'Start Without Debugging' };
+export const NEXT_DEBUG_CONSOLE_LABEL = { value: nls.localize('nextDebugConsole', "Focus Next Debug Console"), original: 'Focus Next Debug Console' };
+export const PREV_DEBUG_CONSOLE_LABEL = { value: nls.localize('prevDebugConsole', "Focus Previous Debug Console"), original: 'Focus Previous Debug Console' };
+export const OPEN_LOADED_SCRIPTS_LABEL = { value: nls.localize('openLoadedScript', "Open Loaded Script..."), original: 'Open Loaded Script...' };
+export const CALLSTACK_TOP_LABEL = { value: nls.localize('callStackTop', "Navigate to Top of Call Stack"), original: 'Navigate to Top of Call Stack' };
+export const CALLSTACK_BOTTOM_LABEL = { value: nls.localize('callStackBottom', "Navigate to Bottom of Call Stack"), original: 'Navigate to Bottom of Call Stack' };
+export const CALLSTACK_UP_LABEL = { value: nls.localize('callStackUp', "Navigate Up Call Stack"), original: 'Navigate Up Call Stack' };
+export const CALLSTACK_DOWN_LABEL = { value: nls.localize('callStackDown', "Navigate Down Call Stack"), original: 'Navigate Down Call Stack' };
+
+export const SELECT_DEBUG_CONSOLE_LABEL = { value: nls.localize('selectDebugConsole', "Select Debug Console"), original: 'Select Debug Console' };
export const DEBUG_QUICK_ACCESS_PREFIX = 'debug ';
export const DEBUG_CONSOLE_QUICK_ACCESS_PREFIX = 'debug consoles ';
@@ -179,6 +188,103 @@ async function changeDebugConsoleFocus(accessor: ServicesAccessor, next: boolean
}
}
+async function navigateCallStack(debugService: IDebugService, down: boolean) {
+ const frame = debugService.getViewModel().focusedStackFrame;
+ if (frame) {
+
+ let callStack = frame.thread.getCallStack();
+ let index = callStack.findIndex(elem => elem.frameId === frame.frameId);
+ let nextVisibleFrame;
+ if (down) {
+ if (index >= callStack.length - 1) {
+ if ((<Thread>frame.thread).reachedEndOfCallStack) {
+ goToTopOfCallStack(debugService);
+ return;
+ } else {
+ await debugService.getModel().fetchCallstack(frame.thread, 20);
+ callStack = frame.thread.getCallStack();
+ index = callStack.findIndex(elem => elem.frameId === frame.frameId);
+ }
+ }
+ nextVisibleFrame = findNextVisibleFrame(true, callStack, index);
+ } else {
+ if (index <= 0) {
+ goToBottomOfCallStack(debugService);
+ return;
+ }
+ nextVisibleFrame = findNextVisibleFrame(false, callStack, index);
+ }
+
+ if (nextVisibleFrame) {
+ debugService.focusStackFrame(nextVisibleFrame);
+ }
+ }
+}
+
+async function goToBottomOfCallStack(debugService: IDebugService) {
+ const thread = debugService.getViewModel().focusedThread;
+ if (thread) {
+ await debugService.getModel().fetchCallstack(thread);
+ const callStack = thread.getCallStack();
+ if (callStack.length > 0) {
+ const nextVisibleFrame = findNextVisibleFrame(false, callStack, 0); // must consider the next frame up first, which will be the last frame
+ if (nextVisibleFrame) {
+ debugService.focusStackFrame(nextVisibleFrame);
+ }
+ }
+ }
+}
+
+function goToTopOfCallStack(debugService: IDebugService) {
+ const thread = debugService.getViewModel().focusedThread;
+
+ if (thread) {
+ debugService.focusStackFrame(thread.getTopStackFrame());
+ }
+}
+
+/**
+ * Finds next frame that is not skipped by SkipFiles. Skips frame at index and starts searching at next.
+ * Must satisfy `0 <= startIndex <= callStack - 1`
+ * @param down specifies whether to search downwards if the current file is skipped.
+ * @param callStack the call stack to search
+ * @param startIndex the index to start the search at
+ */
+function findNextVisibleFrame(down: boolean, callStack: readonly IStackFrame[], startIndex: number) {
+
+ if (startIndex >= callStack.length) {
+ startIndex = callStack.length - 1;
+ } else if (startIndex < 0) {
+ startIndex = 0;
+ }
+
+ let index = startIndex;
+
+ let currFrame;
+ do {
+ if (down) {
+ if (index === callStack.length - 1) {
+ index = 0;
+ } else {
+ index++;
+ }
+ } else {
+ if (index === 0) {
+ index = callStack.length - 1;
+ } else {
+ index--;
+ }
+ }
+
+ currFrame = callStack[index];
+ if (!(currFrame.source.presentationHint === 'deemphasize' || currFrame.presentationHint === 'deemphasize')) {
+ return currFrame;
+ }
+ } while (index !== startIndex); // end loop when we've just checked the start index, since that should be the last one checked
+
+ return undefined;
+}
+
// These commands are used in call stack context menu, call stack inline actions, command palette, debug toolbar, mac native touch bar
// When the command is exectued in the context of a thread(context menu on a thread, inline call stack action) we pass the thread id
// Otherwise when it is executed "globaly"(using the touch bar, debug toolbar, command palette) we do not pass any id and just take whatever is the focussed thread
@@ -260,6 +366,39 @@ CommandsRegistry.registerCommand({
}
});
+
+CommandsRegistry.registerCommand({
+ id: CALLSTACK_TOP_ID,
+ handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
+ const debugService = accessor.get(IDebugService);
+ goToTopOfCallStack(debugService);
+ }
+});
+
+CommandsRegistry.registerCommand({
+ id: CALLSTACK_BOTTOM_ID,
+ handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
+ const debugService = accessor.get(IDebugService);
+ await goToBottomOfCallStack(debugService);
+ }
+});
+
+CommandsRegistry.registerCommand({
+ id: CALLSTACK_UP_ID,
+ handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
+ const debugService = accessor.get(IDebugService);
+ navigateCallStack(debugService, false);
+ }
+});
+
+CommandsRegistry.registerCommand({
+ id: CALLSTACK_DOWN_ID,
+ handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => {
+ const debugService = accessor.get(IDebugService);
+ navigateCallStack(debugService, true);
+ }
+});
+
MenuRegistry.appendMenuItem(MenuId.EditorContext, {
command: {
id: JUMP_TO_CURSOR_ID,
@@ -584,7 +723,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
const { launch, name, getConfig } = debugService.getConfigurationManager().selectedConfiguration;
const config = await getConfig();
const configOrName = config ? Object.assign(deepClone(config), debugStartOptions?.config) : name;
- await debugService.startDebugging(launch, configOrName, { noDebug: debugStartOptions?.noDebug, startedByUser: true }, false);
+ await debugService.startDebugging(launch, configOrName, { noDebug: debugStartOptions?.noDebug, startedByUser: true, saveBeforeStart: false });
}
});
diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts
index b308716a8bb..2474922a5ad 100644
--- a/src/vs/workbench/contrib/debug/browser/debugService.ts
+++ b/src/vs/workbench/contrib/debug/browser/debugService.ts
@@ -312,7 +312,10 @@ export class DebugService implements IDebugService {
* main entry point
* properly manages compounds, checks for errors and handles the initializing state.
*/
- async startDebugging(launch: ILaunch | undefined, configOrName?: IConfig | string, options?: IDebugSessionOptions, saveBeforeStart = !options?.parentSession): Promise<boolean> {
+ async startDebugging(launch: ILaunch | undefined, configOrName?: IConfig | string, options?: IDebugSessionOptions): Promise<boolean> {
+
+ const saveBeforeStart = options?.saveBeforeStart ?? !options?.parentSession;
+
const message = options && options.noDebug ? nls.localize('runTrust', "Running executes build tasks and program code from your workspace.") : nls.localize('debugTrust', "Debugging executes build tasks and program code from your workspace.");
const trust = await this.workspaceTrustRequestService.requestWorkspaceTrust({ message });
if (!trust) {
@@ -701,7 +704,10 @@ export class DebugService implements IDebugService {
}
async restartSession(session: IDebugSession, restartData?: any): Promise<any> {
- await this.editorService.saveAll();
+ if (session.saveBeforeStart) {
+ await saveAllBeforeDebugStart(this.configurationService, this.editorService);
+ }
+
const isAutoRestart = !!restartData;
const runTasks: () => Promise<TaskRunResult> = async () => {
diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts
index 4a53a4e0c93..29a568d478b 100644
--- a/src/vs/workbench/contrib/debug/browser/debugSession.ts
+++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts
@@ -164,6 +164,10 @@ export class DebugSession implements IDebugSession {
return !!this._options.compact;
}
+ get saveBeforeStart(): boolean {
+ return this._options.saveBeforeStart ?? !this._options?.parentSession;
+ }
+
get compoundRoot(): DebugCompoundRoot | undefined {
return this._options.compoundRoot;
}
@@ -955,7 +959,7 @@ export class DebugSession implements IDebugSession {
if (thread) {
// Call fetch call stack twice, the first only return the top stack frame.
// Second retrieves the rest of the call stack. For performance reasons #25605
- const promises = this.model.fetchCallStack(<Thread>thread);
+ const promises = this.model.refreshTopOfCallstack(<Thread>thread);
const focus = async () => {
if (focusedThreadDoesNotExist || (!event.body.preserveFocusHint && thread.getCallStack().length)) {
const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame;
diff --git a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts
index 1f7614bfee7..3fd1c0238b8 100644
--- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts
+++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts
@@ -16,7 +16,7 @@ import { URI } from 'vs/base/common/uri';
import 'vs/css!./media/debugToolBar';
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { localize } from 'vs/nls';
-import { ICommandAction } from 'vs/platform/action/common/action';
+import { ICommandAction, ICommandActionTitle } from 'vs/platform/action/common/action';
import { DropdownWithPrimaryActionViewItem } from 'vs/platform/actions/browser/dropdownWithPrimaryActionViewItem';
import { createActionViewItem, createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { IMenu, IMenuService, MenuId, MenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions';
@@ -296,7 +296,7 @@ export function createDisconnectMenuItemAction(action: MenuItemAction, disposabl
// Debug toolbar
const debugViewTitleItems: IDisposable[] = [];
-const registerDebugToolBarItem = (id: string, title: string, order: number, icon?: { light?: URI; dark?: URI } | ThemeIcon, when?: ContextKeyExpression, precondition?: ContextKeyExpression, alt?: ICommandAction) => {
+const registerDebugToolBarItem = (id: string, title: string | ICommandActionTitle, order: number, icon?: { light?: URI; dark?: URI } | ThemeIcon, when?: ContextKeyExpression, precondition?: ContextKeyExpression, alt?: ICommandAction) => {
MenuRegistry.appendMenuItem(MenuId.DebugToolBar, {
group: 'navigation',
when,
diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts
index baac9206484..21dc92d475c 100644
--- a/src/vs/workbench/contrib/debug/common/debug.ts
+++ b/src/vs/workbench/contrib/debug/common/debug.ts
@@ -206,6 +206,7 @@ export interface IDebugSessionOptions {
simple?: boolean;
};
startedByUser?: boolean;
+ saveBeforeStart?: boolean;
}
export interface IDataBreakpointInfoResponse {
@@ -296,6 +297,7 @@ export interface IDebugSession extends ITreeElement {
readonly subId: string | undefined;
readonly compact: boolean;
readonly compoundRoot: DebugCompoundRoot | undefined;
+ readonly saveBeforeStart: boolean;
readonly name: string;
readonly isSimpleUI: boolean;
readonly autoExpandLazyVariables: boolean;
@@ -602,6 +604,8 @@ export interface IDebugModel extends ITreeElement {
onDidChangeBreakpoints: Event<IBreakpointsChangeEvent | undefined>;
onDidChangeCallStack: Event<void>;
onDidChangeWatchExpressions: Event<IExpression | undefined>;
+
+ fetchCallstack(thread: IThread, levels?: number): Promise<void>;
}
/**
@@ -1086,7 +1090,7 @@ export interface IDebugService {
* Returns true if the start debugging was successful. For compound launches, all configurations have to start successfully for it to return success.
* On errors the startDebugging will throw an error, however some error and cancelations are handled and in that case will simply return false.
*/
- startDebugging(launch: ILaunch | undefined, configOrName?: IConfig | string, options?: IDebugSessionOptions, saveBeforeStart?: boolean): Promise<boolean>;
+ startDebugging(launch: ILaunch | undefined, configOrName?: IConfig | string, options?: IDebugSessionOptions): Promise<boolean>;
/**
* Restarts a session or creates a new one if there is no active session.
diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts
index 4b20e069cd2..1f50eb36368 100644
--- a/src/vs/workbench/contrib/debug/common/debugModel.ts
+++ b/src/vs/workbench/contrib/debug/common/debugModel.ts
@@ -1244,7 +1244,31 @@ export class DebugModel implements IDebugModel {
}
}
- fetchCallStack(thread: Thread): { topCallStack: Promise<void>; wholeCallStack: Promise<void> } {
+ /**
+ * Update the call stack and notify the call stack view that changes have occurred.
+ */
+ async fetchCallstack(thread: IThread, levels?: number): Promise<void> {
+
+ if ((<Thread>thread).reachedEndOfCallStack) {
+ return;
+ }
+
+ const totalFrames = thread.stoppedDetails?.totalFrames;
+ const remainingFrames = (typeof totalFrames === 'number') ? (totalFrames - thread.getCallStack().length) : undefined;
+
+ if (!levels || (remainingFrames && levels > remainingFrames)) {
+ levels = remainingFrames;
+ }
+
+ if (levels && levels > 0) {
+ await (<Thread>thread).fetchCallStack(levels);
+ this._onDidChangeCallStack.fire();
+ }
+
+ return;
+ }
+
+ refreshTopOfCallstack(thread: Thread): { topCallStack: Promise<void>; wholeCallStack: Promise<void> } {
if (thread.session.capabilities.supportsDelayedStackTraceLoading) {
// For improved performance load the first stack frame and then load the rest async.
let topCallStack = Promise.resolve();
diff --git a/src/vs/workbench/contrib/debug/node/telemetryApp.ts b/src/vs/workbench/contrib/debug/node/telemetryApp.ts
index ab6d37993ca..601ff4f9b1e 100644
--- a/src/vs/workbench/contrib/debug/node/telemetryApp.ts
+++ b/src/vs/workbench/contrib/debug/node/telemetryApp.ts
@@ -4,10 +4,10 @@
*--------------------------------------------------------------------------------------------*/
import { Server } from 'vs/base/parts/ipc/node/ipc.cp';
-import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender';
import { TelemetryAppenderChannel } from 'vs/platform/telemetry/common/telemetryIpc';
+import { OneDataSystemAppender } from 'vs/platform/telemetry/node/1dsAppender';
-const appender = new AppInsightsAppender(process.argv[2], JSON.parse(process.argv[3]), process.argv[4]);
+const appender = new OneDataSystemAppender(undefined, process.argv[2], JSON.parse(process.argv[3]), process.argv[4]);
process.once('exit', () => appender.flush());
const channel = new TelemetryAppenderChannel([appender]);
diff --git a/src/vs/workbench/contrib/debug/test/browser/mockDebug.ts b/src/vs/workbench/contrib/debug/test/browser/mockDebug.ts
index 1bebaa31adb..1596e346e31 100644
--- a/src/vs/workbench/contrib/debug/test/browser/mockDebug.ts
+++ b/src/vs/workbench/contrib/debug/test/browser/mockDebug.ts
@@ -190,6 +190,10 @@ export class MockSession implements IDebugSession {
return undefined;
}
+ get saveBeforeStart(): boolean {
+ return true;
+ }
+
get isSimpleUI(): boolean {
return false;
}
diff --git a/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts
index d075977cb45..12f620df779 100644
--- a/src/vs/workbench/contrib/sessionSync/browser/sessionSync.contribution.ts
+++ b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts
@@ -7,10 +7,10 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { Registry } from 'vs/platform/registry/common/platform';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
-import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
+import { Action2, IAction2Options, registerAction2 } from 'vs/platform/actions/common/actions';
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { localize } from 'vs/nls';
-import { ISessionSyncWorkbenchService, Change, ChangeType, Folder, EditSession, FileType, EDIT_SESSION_SYNC_TITLE, EditSessionSchemaVersion } from 'vs/workbench/services/sessionSync/common/sessionSync';
+import { IEditSessionsWorkbenchService, Change, ChangeType, Folder, EditSession, FileType, EDIT_SESSION_SYNC_CATEGORY, EditSessionSchemaVersion, IEditSessionsLogService } from 'vs/workbench/contrib/editSessions/common/editSessions';
import { ISCMRepository, ISCMService } from 'vs/workbench/contrib/scm/common/scm';
import { IFileService } from 'vs/platform/files/common/files';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
@@ -19,13 +19,12 @@ import { joinPath, relativePath } from 'vs/base/common/resources';
import { VSBuffer } from 'vs/base/common/buffer';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
-import { SessionSyncWorkbenchService } from 'vs/workbench/services/sessionSync/browser/sessionSyncWorkbenchService';
+import { EditSessionsWorkbenchService } from 'vs/workbench/contrib/editSessions/browser/editSessionsWorkbenchService';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { UserDataSyncErrorCode, UserDataSyncStoreError } from 'vs/platform/userDataSync/common/userDataSync';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
-import { ILogService } from 'vs/platform/log/common/log';
import { IProductService } from 'vs/platform/product/common/productService';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
@@ -39,34 +38,33 @@ import { getVirtualWorkspaceLocation } from 'vs/platform/workspace/common/virtua
import { Schemas } from 'vs/base/common/network';
import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys';
import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
+import { EditSessionsLogService } from 'vs/workbench/contrib/editSessions/common/editSessionsLogService';
-registerSingleton(ISessionSyncWorkbenchService, SessionSyncWorkbenchService);
+registerSingleton(IEditSessionsLogService, EditSessionsLogService);
+registerSingleton(IEditSessionsWorkbenchService, EditSessionsWorkbenchService);
-const resumeLatestCommand = {
- id: 'workbench.experimental.editSessions.actions.resumeLatest',
- title: { value: localize('resume latest', "{0}: Resume Latest Edit Session", EDIT_SESSION_SYNC_TITLE), original: 'Edit Sessions' },
-};
-const storeCurrentCommand = {
- id: 'workbench.experimental.editSessions.actions.storeCurrent',
- title: { value: localize('store current', "{0}: Store Current Edit Session", EDIT_SESSION_SYNC_TITLE), original: 'Edit Sessions' },
-};
-const continueEditSessionCommand = {
+const continueEditSessionCommand: IAction2Options = {
id: '_workbench.experimental.editSessions.actions.continueEditSession',
title: { value: localize('continue edit session', "Continue Edit Session..."), original: 'Continue Edit Session...' },
+ category: EDIT_SESSION_SYNC_CATEGORY,
+ f1: true
};
-const openLocalFolderCommand = {
+const openLocalFolderCommand: IAction2Options = {
id: '_workbench.experimental.editSessions.actions.continueEditSession.openLocalFolder',
- title: localize('continue edit session in local folder', "Open In Local Folder"),
+ title: { value: localize('continue edit session in local folder', "Open In Local Folder"), original: 'Open In Local Folder' },
+ category: EDIT_SESSION_SYNC_CATEGORY,
+ precondition: IsWebContext
};
const queryParamName = 'editSessionId';
+const experimentalSettingName = 'workbench.experimental.editSessions.enabled';
-export class SessionSyncContribution extends Disposable implements IWorkbenchContribution {
+export class EditSessionsContribution extends Disposable implements IWorkbenchContribution {
private registered = false;
private continueEditSessionOptions: ContinueEditSessionItem[] = [];
constructor(
- @ISessionSyncWorkbenchService private readonly sessionSyncWorkbenchService: ISessionSyncWorkbenchService,
+ @IEditSessionsWorkbenchService private readonly editSessionsWorkbenchService: IEditSessionsWorkbenchService,
@IFileService private readonly fileService: IFileService,
@IProgressService private readonly progressService: IProgressService,
@IOpenerService private readonly openerService: IOpenerService,
@@ -74,7 +72,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
@ISCMService private readonly scmService: ISCMService,
@INotificationService private readonly notificationService: INotificationService,
@IDialogService private readonly dialogService: IDialogService,
- @ILogService private readonly logService: ILogService,
+ @IEditSessionsLogService private readonly logService: IEditSessionsLogService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IProductService private readonly productService: IProductService,
@IConfigurationService private configurationService: IConfigurationService,
@@ -87,11 +85,11 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
super();
if (this.environmentService.editSessionId !== undefined) {
- void this.applyEditSession(this.environmentService.editSessionId).finally(() => this.environmentService.editSessionId = undefined);
+ void this.resumeEditSession(this.environmentService.editSessionId).finally(() => this.environmentService.editSessionId = undefined);
}
this.configurationService.onDidChangeConfiguration((e) => {
- if (e.affectsConfiguration('workbench.experimental.editSessions.enabled')) {
+ if (e.affectsConfiguration(experimentalSettingName)) {
this.registerActions();
}
});
@@ -127,13 +125,14 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
}
private registerActions() {
- if (this.registered || this.configurationService.getValue('workbench.experimental.editSessions.enabled') !== true) {
+ if (this.registered || this.configurationService.getValue(experimentalSettingName) !== true) {
+ this.logService.info(`Skipping registering edit sessions actions as edit sessions are currently disabled. Set ${experimentalSettingName} to enable edit sessions.`);
return;
}
this.registerContinueEditSessionAction();
- this.registerApplyLatestEditSessionAction();
+ this.registerResumeLatestEditSessionAction();
this.registerStoreLatestEditSessionAction();
this.registerContinueInLocalFolderAction();
@@ -145,11 +144,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
const that = this;
this._register(registerAction2(class ContinueEditSessionAction extends Action2 {
constructor() {
- super({
- id: continueEditSessionCommand.id,
- title: continueEditSessionCommand.title,
- f1: true
- });
+ super(continueEditSessionCommand);
}
async run(accessor: ServicesAccessor, workspaceUri: URI | undefined): Promise<void> {
@@ -166,34 +161,33 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
query: uri.query.length > 0 ? (uri + `&${queryParamName}=${encodedRef}`) : `${queryParamName}=${encodedRef}`
});
} else {
- that.logService.warn(`Edit Sessions: Failed to store edit session when invoking ${continueEditSessionCommand.id}.`);
+ that.logService.warn(`Failed to store edit session when invoking ${continueEditSessionCommand.id}.`);
}
// Open the URI
- that.logService.info(`Edit Sessions: opening ${uri.toString()}`);
+ that.logService.info(`Opening ${uri.toString()}`);
await that.openerService.open(uri, { openExternal: true });
}
}));
}
- private registerApplyLatestEditSessionAction(): void {
+ private registerResumeLatestEditSessionAction(): void {
const that = this;
- this._register(registerAction2(class ApplyLatestEditSessionAction extends Action2 {
+ this._register(registerAction2(class ResumeLatestEditSessionAction extends Action2 {
constructor() {
super({
- id: resumeLatestCommand.id,
- title: resumeLatestCommand.title,
- menu: {
- id: MenuId.CommandPalette,
- }
+ id: 'workbench.experimental.editSessions.actions.resumeLatest',
+ title: { value: localize('resume latest.v2', "Resume Latest Edit Session"), original: 'Resume Latest Edit Session' },
+ category: EDIT_SESSION_SYNC_CATEGORY,
+ f1: true,
});
}
async run(accessor: ServicesAccessor): Promise<void> {
await that.progressService.withProgress({
location: ProgressLocation.Notification,
- title: localize('applying edit session', 'Applying edit session...')
- }, async () => await that.applyEditSession());
+ title: localize('resuming edit session', 'Resuming edit session...')
+ }, async () => await that.resumeEditSession());
}
}));
}
@@ -203,11 +197,10 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
this._register(registerAction2(class StoreLatestEditSessionAction extends Action2 {
constructor() {
super({
- id: storeCurrentCommand.id,
- title: storeCurrentCommand.title,
- menu: {
- id: MenuId.CommandPalette,
- }
+ id: 'workbench.experimental.editSessions.actions.storeCurrent',
+ title: { value: localize('store current.v2', "Store Current Edit Session"), original: 'Store Current Edit Session' },
+ category: EDIT_SESSION_SYNC_CATEGORY,
+ f1: true,
});
}
@@ -220,26 +213,24 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
}));
}
- async applyEditSession(ref?: string): Promise<void> {
- if (ref !== undefined) {
- this.logService.info(`Edit Sessions: Applying edit session with ref ${ref}.`);
- }
+ async resumeEditSession(ref?: string): Promise<void> {
+ this.logService.info(ref !== undefined ? `Resuming edit session with ref ${ref}...` : 'Resuming edit session...');
- const data = await this.sessionSyncWorkbenchService.read(ref);
+ const data = await this.editSessionsWorkbenchService.read(ref);
if (!data) {
if (ref === undefined) {
- this.notificationService.info(localize('no edit session', 'There are no edit sessions to apply.'));
+ this.notificationService.info(localize('no edit session', 'There are no edit sessions to resume.'));
} else {
- this.notificationService.warn(localize('no edit session content for ref', 'Could not apply edit session contents for ID {0}.', ref));
+ this.notificationService.warn(localize('no edit session content for ref', 'Could not resume edit session contents for ID {0}.', ref));
}
- this.logService.info(`Edit Sessions: Aborting applying edit session as no edit session content is available to be applied from ref ${ref}.`);
+ this.logService.info(`Aborting resuming edit session as no edit session content is available to be applied from ref ${ref}.`);
return;
}
const editSession = data.editSession;
ref = data.ref;
if (editSession.version > EditSessionSchemaVersion) {
- this.notificationService.error(localize('client too old', "Please upgrade to a newer version of {0} to apply this edit session.", this.productService.nameLong));
+ this.notificationService.error(localize('client too old', "Please upgrade to a newer version of {0} to resume this edit session.", this.productService.nameLong));
return;
}
@@ -250,7 +241,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
for (const folder of editSession.folders) {
const folderRoot = this.contextService.getWorkspace().folders.find((f) => f.name === folder.name);
if (!folderRoot) {
- this.logService.info(`Edit Sessions: Skipping applying ${folder.workingChanges.length} changes from edit session with ref ${ref} as no corresponding workspace folder named ${folder.name} is currently open.`);
+ 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;
}
@@ -273,9 +264,9 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
if (hasLocalUncommittedChanges) {
// TODO@joyceerhl Provide the option to diff files which would be overwritten by edit session contents
const result = await this.dialogService.confirm({
- message: localize('apply edit session warning', 'Applying your edit session may overwrite your existing uncommitted changes. Do you want to proceed?'),
+ message: localize('resume edit session warning', 'Resuming your edit session may overwrite your existing uncommitted changes. Do you want to proceed?'),
type: 'warning',
- title: EDIT_SESSION_SYNC_TITLE
+ title: EDIT_SESSION_SYNC_CATEGORY.value
});
if (!result.confirmed) {
return;
@@ -290,12 +281,12 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
}
}
- this.logService.info(`Edit Sessions: Deleting edit session with ref ${ref} after successfully applying it to current workspace...`);
- await this.sessionSyncWorkbenchService.delete(ref);
- this.logService.info(`Edit Sessions: Deleted edit session with ref ${ref}.`);
+ this.logService.info(`Deleting edit session with ref ${ref} after successfully applying it to current workspace...`);
+ await this.editSessionsWorkbenchService.delete(ref);
+ this.logService.info(`Deleted edit session with ref ${ref}.`);
} catch (ex) {
- this.logService.error('Edit Sessions: Failed to apply edit session, reason: ', (ex as Error).toString());
- this.notificationService.error(localize('apply failed', "Failed to apply your edit session."));
+ this.logService.error('Failed to resume edit session, reason: ', (ex as Error).toString());
+ this.notificationService.error(localize('resume failed', "Failed to resume your edit session."));
}
}
@@ -313,7 +304,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
for (const uri of trackedUris) {
const workspaceFolder = this.contextService.getWorkspaceFolder(uri);
if (!workspaceFolder) {
- this.logService.info(`Edit Sessions: Skipping working change ${uri.toString()} as no associated workspace folder was found.`);
+ this.logService.info(`Skipping working change ${uri.toString()} as no associated workspace folder was found.`);
continue;
}
@@ -342,7 +333,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
}
if (!hasEdits) {
- this.logService.info('Edit Sessions: Skipping storing edit session as there are no edits to store.');
+ this.logService.info('Skipping storing edit session as there are no edits to store.');
if (fromStoreCommand) {
this.notificationService.info(localize('no edits to store', 'Skipped storing edit session as there are no edits to store.'));
}
@@ -352,12 +343,12 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
const data: EditSession = { folders, version: 1 };
try {
- this.logService.info(`Edit Sessions: Storing edit session...`);
- const ref = await this.sessionSyncWorkbenchService.write(data);
- this.logService.info(`Edit Sessions: Stored edit session with ref ${ref}.`);
+ this.logService.info(`Storing edit session...`);
+ const ref = await this.editSessionsWorkbenchService.write(data);
+ this.logService.info(`Stored edit session with ref ${ref}.`);
return ref;
} catch (ex) {
- this.logService.error(`Edit Sessions: Failed to store edit session, reason: `, (ex as Error).toString());
+ this.logService.error(`Failed to store edit session, reason: `, (ex as Error).toString());
type UploadFailedEvent = { reason: string };
type UploadFailedClassification = {
@@ -369,11 +360,11 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
switch (ex.code) {
case UserDataSyncErrorCode.TooLarge:
// Uploading a payload can fail due to server size limits
- this.telemetryService.publicLog2<UploadFailedEvent, UploadFailedClassification>('sessionSync.upload.failed', { reason: 'TooLarge' });
+ this.telemetryService.publicLog2<UploadFailedEvent, UploadFailedClassification>('editSessions.upload.failed', { reason: 'TooLarge' });
this.notificationService.error(localize('payload too large', 'Your edit session exceeds the size limit and cannot be stored.'));
break;
default:
- this.telemetryService.publicLog2<UploadFailedEvent, UploadFailedClassification>('sessionSync.upload.failed', { reason: 'unknown' });
+ this.telemetryService.publicLog2<UploadFailedEvent, UploadFailedClassification>('editSessions.upload.failed', { reason: 'unknown' });
this.notificationService.error(localize('payload failed', 'Your edit session cannot be stored.'));
break;
}
@@ -398,11 +389,7 @@ export class SessionSyncContribution extends Disposable implements IWorkbenchCon
const that = this;
this._register(registerAction2(class ContinueInLocalFolderAction extends Action2 {
constructor() {
- super({
- id: openLocalFolderCommand.id,
- title: openLocalFolderCommand.title,
- precondition: IsWebContext
- });
+ super(openLocalFolderCommand);
}
async run(accessor: ServicesAccessor): Promise<URI | undefined> {
@@ -513,7 +500,7 @@ const continueEditSessionExtPoint = ExtensionsRegistry.registerExtensionPoint<IC
//#endregion
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
-workbenchRegistry.registerWorkbenchContribution(SessionSyncContribution, LifecyclePhase.Restored);
+workbenchRegistry.registerWorkbenchContribution(EditSessionsContribution, LifecyclePhase.Restored);
Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerConfiguration({
...workbenchConfigurationNodeBase,
diff --git a/src/vs/workbench/contrib/editSessions/browser/editSessionsWorkbenchService.ts b/src/vs/workbench/contrib/editSessions/browser/editSessionsWorkbenchService.ts
new file mode 100644
index 00000000000..b63b550ab35
--- /dev/null
+++ b/src/vs/workbench/contrib/editSessions/browser/editSessionsWorkbenchService.ts
@@ -0,0 +1,373 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { Disposable } from 'vs/base/common/lifecycle';
+import { URI } from 'vs/base/common/uri';
+import { localize } from 'vs/nls';
+import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
+import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
+import { IEnvironmentService } from 'vs/platform/environment/common/environment';
+import { IFileService } from 'vs/platform/files/common/files';
+import { IProductService } from 'vs/platform/product/common/productService';
+import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
+import { IRequestService } from 'vs/platform/request/common/request';
+import { IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
+import { createSyncHeaders, IAuthenticationProvider } from 'vs/platform/userDataSync/common/userDataSync';
+import { UserDataSyncStoreClient } from 'vs/platform/userDataSync/common/userDataSyncStoreService';
+import { AuthenticationSession, AuthenticationSessionsChangeEvent, IAuthenticationService } from 'vs/workbench/services/authentication/common/authentication';
+import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
+import { EDIT_SESSIONS_SIGNED_IN, EditSession, EDIT_SESSION_SYNC_CATEGORY, IEditSessionsWorkbenchService, EDIT_SESSIONS_SIGNED_IN_KEY, IEditSessionsLogService } from 'vs/workbench/contrib/editSessions/common/editSessions';
+import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
+import { generateUuid } from 'vs/base/common/uuid';
+
+type ExistingSession = IQuickPickItem & { session: AuthenticationSession & { providerId: string } };
+type AuthenticationProviderOption = IQuickPickItem & { provider: IAuthenticationProvider };
+
+export class EditSessionsWorkbenchService extends Disposable implements IEditSessionsWorkbenchService {
+
+ _serviceBrand = undefined;
+
+ private serverConfiguration = this.productService['editSessions.store'];
+ private storeClient: UserDataSyncStoreClient | undefined;
+
+ #authenticationInfo: { sessionId: string; token: string; providerId: string } | undefined;
+ private static CACHED_SESSION_STORAGE_KEY = 'editSessionAccountPreference';
+
+ private initialized = false;
+ private readonly signedInContext: IContextKey<boolean>;
+
+ constructor(
+ @IFileService private readonly fileService: IFileService,
+ @IStorageService private readonly storageService: IStorageService,
+ @IQuickInputService private readonly quickInputService: IQuickInputService,
+ @IAuthenticationService private readonly authenticationService: IAuthenticationService,
+ @IExtensionService private readonly extensionService: IExtensionService,
+ @IEnvironmentService private readonly environmentService: IEnvironmentService,
+ @IEditSessionsLogService private readonly logService: IEditSessionsLogService,
+ @IProductService private readonly productService: IProductService,
+ @IContextKeyService private readonly contextKeyService: IContextKeyService,
+ @IRequestService private readonly requestService: IRequestService,
+ @IDialogService private readonly dialogService: IDialogService,
+ ) {
+ super();
+
+ // If the user signs out of the current session, reset our cached auth state in memory and on disk
+ this._register(this.authenticationService.onDidChangeSessions((e) => this.onDidChangeSessions(e.event)));
+
+ // If another window changes the preferred session storage, reset our cached auth state in memory
+ this._register(this.storageService.onDidChangeValue(e => this.onDidChangeStorage(e)));
+
+ this.registerResetAuthenticationAction();
+
+ this.signedInContext = EDIT_SESSIONS_SIGNED_IN.bindTo(this.contextKeyService);
+ this.signedInContext.set(this.existingSessionId !== undefined);
+ }
+
+ /**
+ *
+ * @param editSession An object representing edit session state to be restored.
+ * @returns The ref of the stored edit session state.
+ */
+ async write(editSession: EditSession): Promise<string> {
+ await this.initialize();
+ if (!this.initialized) {
+ throw new Error('Please sign in to store your edit session.');
+ }
+
+ return this.storeClient!.write('editSessions', JSON.stringify(editSession), null, createSyncHeaders(generateUuid()));
+ }
+
+ /**
+ * @param ref: A specific content ref to retrieve content for, if it exists.
+ * If undefined, this method will return the latest saved edit session, if any.
+ *
+ * @returns An object representing the requested or latest edit session state, if any.
+ */
+ async read(ref: string | undefined): Promise<{ ref: string; editSession: EditSession } | undefined> {
+ await this.initialize();
+ if (!this.initialized) {
+ throw new Error('Please sign in to apply your latest edit session.');
+ }
+
+ let content: string | undefined | null;
+ const headers = createSyncHeaders(generateUuid());
+ try {
+ if (ref !== undefined) {
+ content = await this.storeClient?.resolveContent('editSessions', ref, headers);
+ } else {
+ const result = await this.storeClient?.read('editSessions', null, headers);
+ content = result?.content;
+ ref = result?.ref;
+ }
+ } catch (ex) {
+ this.logService.error(ex);
+ }
+
+ // TODO@joyceerhl Validate session data, check schema version
+ return (content !== undefined && content !== null && ref !== undefined) ? { ref: ref, editSession: JSON.parse(content) } : undefined;
+ }
+
+ async delete(ref: string) {
+ await this.initialize();
+ if (!this.initialized) {
+ throw new Error(`Unable to delete edit session with ref ${ref}.`);
+ }
+
+ try {
+ await this.storeClient?.delete('editSessions', ref);
+ } catch (ex) {
+ this.logService.error(ex);
+ }
+ }
+
+ private async initialize() {
+ if (this.initialized) {
+ return;
+ }
+ this.initialized = await this.doInitialize();
+ this.signedInContext.set(this.initialized);
+ }
+
+ /**
+ *
+ * Ensures that the store client is initialized,
+ * meaning that authentication is configured and it
+ * can be used to communicate with the remote storage service
+ */
+ private async doInitialize(): Promise<boolean> {
+ // Wait for authentication extensions to be registered
+ await this.extensionService.whenInstalledExtensionsRegistered();
+
+ if (!this.serverConfiguration?.url) {
+ throw new Error('Unable to initialize sessions sync as session sync preference is not configured in product.json.');
+ }
+
+ if (!this.storeClient) {
+ this.storeClient = new UserDataSyncStoreClient(URI.parse(this.serverConfiguration.url), this.productService, this.requestService, this.logService, this.environmentService, this.fileService, this.storageService);
+ this._register(this.storeClient.onTokenFailed(() => {
+ this.logService.info('Clearing edit sessions authentication preference because of successive token failures.');
+ this.clearAuthenticationPreference();
+ }));
+ }
+
+ // If we already have an existing auth session in memory, use that
+ if (this.#authenticationInfo !== undefined) {
+ return true;
+ }
+
+ // If the user signed in previously and the session is still available, reuse that without prompting the user again
+ const existingSessionId = this.existingSessionId;
+ if (existingSessionId) {
+ this.logService.trace(`Searching for existing authentication session with ID ${existingSessionId}`);
+ const existing = await this.getExistingSession();
+ if (existing !== undefined) {
+ this.logService.trace(`Found existing authentication session with ID ${existingSessionId}`);
+ this.#authenticationInfo = { sessionId: existing.session.id, token: existing.session.idToken ?? existing.session.accessToken, providerId: existing.session.providerId };
+ this.storeClient.setAuthToken(this.#authenticationInfo.token, this.#authenticationInfo.providerId);
+ return true;
+ }
+ }
+
+ // Ask the user to pick a preferred account
+ const session = await this.getAccountPreference();
+ if (session !== undefined) {
+ this.#authenticationInfo = { sessionId: session.id, token: session.idToken ?? session.accessToken, providerId: session.providerId };
+ this.storeClient.setAuthToken(this.#authenticationInfo.token, this.#authenticationInfo.providerId);
+ this.existingSessionId = session.id;
+ this.logService.trace(`Saving authentication session preference for ID ${session.id}.`);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ *
+ * Prompts the user to pick an authentication option for storing and getting edit sessions.
+ */
+ private async getAccountPreference(): Promise<AuthenticationSession & { providerId: string } | undefined> {
+ const quickpick = this.quickInputService.createQuickPick<ExistingSession | AuthenticationProviderOption>();
+ quickpick.title = localize('account preference', 'Sign In to Use Edit Sessions');
+ quickpick.ok = false;
+ quickpick.placeholder = localize('choose account placeholder', "Select an account to sign in");
+ quickpick.ignoreFocusOut = true;
+ quickpick.items = await this.createQuickpickItems();
+
+ return new Promise((resolve, reject) => {
+ quickpick.onDidHide((e) => {
+ resolve(undefined);
+ quickpick.dispose();
+ });
+
+ quickpick.onDidAccept(async (e) => {
+ const selection = quickpick.selectedItems[0];
+ const session = 'provider' in selection ? { ...await this.authenticationService.createSession(selection.provider.id, selection.provider.scopes), providerId: selection.provider.id } : selection.session;
+ resolve(session);
+ quickpick.hide();
+ });
+
+ quickpick.show();
+ });
+ }
+
+ private async createQuickpickItems(): Promise<(ExistingSession | AuthenticationProviderOption | IQuickPickSeparator)[]> {
+ const options: (ExistingSession | AuthenticationProviderOption | IQuickPickSeparator)[] = [];
+
+ options.push({ type: 'separator', label: localize('signed in', "Signed In") });
+
+ const sessions = await this.getAllSessions();
+ options.push(...sessions);
+
+ options.push({ type: 'separator', label: localize('others', "Others") });
+
+ for (const authenticationProvider of (await this.getAuthenticationProviders())) {
+ const signedInForProvider = sessions.some(account => account.session.providerId === authenticationProvider.id);
+ if (!signedInForProvider || this.authenticationService.supportsMultipleAccounts(authenticationProvider.id)) {
+ const providerName = this.authenticationService.getLabel(authenticationProvider.id);
+ options.push({ label: localize('sign in using account', "Sign in with {0}", providerName), provider: authenticationProvider });
+ }
+ }
+
+ return options;
+ }
+
+ /**
+ *
+ * Returns all authentication sessions available from {@link getAuthenticationProviders}.
+ */
+ private async getAllSessions() {
+ const authenticationProviders = await this.getAuthenticationProviders();
+ const accounts = new Map<string, ExistingSession>();
+ let currentSession: ExistingSession | undefined;
+
+ for (const provider of authenticationProviders) {
+ const sessions = await this.authenticationService.getSessions(provider.id, provider.scopes);
+
+ for (const session of sessions) {
+ const item = {
+ label: session.account.label,
+ description: this.authenticationService.getLabel(provider.id),
+ session: { ...session, providerId: provider.id }
+ };
+ accounts.set(item.session.account.id, item);
+ if (this.existingSessionId === session.id) {
+ currentSession = item;
+ }
+ }
+ }
+
+ if (currentSession !== undefined) {
+ accounts.set(currentSession.session.account.id, currentSession);
+ }
+
+ return [...accounts.values()];
+ }
+
+ /**
+ *
+ * Returns all authentication providers which can be used to authenticate
+ * to the remote storage service, based on product.json configuration
+ * and registered authentication providers.
+ */
+ private async getAuthenticationProviders() {
+ if (!this.serverConfiguration) {
+ throw new Error('Unable to get configured authentication providers as session sync preference is not configured in product.json.');
+ }
+
+ // Get the list of authentication providers configured in product.json
+ const authenticationProviders = this.serverConfiguration.authenticationProviders;
+ const configuredAuthenticationProviders = Object.keys(authenticationProviders).reduce<IAuthenticationProvider[]>((result, id) => {
+ result.push({ id, scopes: authenticationProviders[id].scopes });
+ return result;
+ }, []);
+
+ // Filter out anything that isn't currently available through the authenticationService
+ const availableAuthenticationProviders = this.authenticationService.declaredProviders;
+
+ return configuredAuthenticationProviders.filter(({ id }) => availableAuthenticationProviders.some(provider => provider.id === id));
+ }
+
+ private get existingSessionId() {
+ return this.storageService.get(EditSessionsWorkbenchService.CACHED_SESSION_STORAGE_KEY, StorageScope.APPLICATION);
+ }
+
+ private set existingSessionId(sessionId: string | undefined) {
+ if (sessionId === undefined) {
+ this.storageService.remove(EditSessionsWorkbenchService.CACHED_SESSION_STORAGE_KEY, StorageScope.APPLICATION);
+ } else {
+ this.storageService.store(EditSessionsWorkbenchService.CACHED_SESSION_STORAGE_KEY, sessionId, StorageScope.APPLICATION, StorageTarget.MACHINE);
+ }
+ }
+
+ private async getExistingSession() {
+ const accounts = await this.getAllSessions();
+ return accounts.find((account) => account.session.id === this.existingSessionId);
+ }
+
+ private async onDidChangeStorage(e: IStorageValueChangeEvent): Promise<void> {
+ if (e.key === EditSessionsWorkbenchService.CACHED_SESSION_STORAGE_KEY
+ && e.scope === StorageScope.APPLICATION
+ ) {
+ const newSessionId = this.existingSessionId;
+ const previousSessionId = this.#authenticationInfo?.sessionId;
+
+ if (previousSessionId !== newSessionId) {
+ this.logService.trace(`Resetting authentication state because authentication session ID preference changed from ${previousSessionId} to ${newSessionId}.`);
+ this.#authenticationInfo = undefined;
+ this.initialized = false;
+ }
+ }
+ }
+
+ private clearAuthenticationPreference(): void {
+ this.#authenticationInfo = undefined;
+ this.initialized = false;
+ this.existingSessionId = undefined;
+ this.signedInContext.set(false);
+ }
+
+ private onDidChangeSessions(e: AuthenticationSessionsChangeEvent): void {
+ if (this.#authenticationInfo?.sessionId && e.removed.find(session => session.id === this.#authenticationInfo?.sessionId)) {
+ this.clearAuthenticationPreference();
+ }
+ }
+
+ private registerResetAuthenticationAction() {
+ const that = this;
+ this._register(registerAction2(class ResetEditSessionAuthenticationAction extends Action2 {
+ constructor() {
+ super({
+ id: 'workbench.editSessions.actions.resetAuth',
+ title: localize('reset auth', 'Sign Out'),
+ category: EDIT_SESSION_SYNC_CATEGORY,
+ precondition: ContextKeyExpr.equals(EDIT_SESSIONS_SIGNED_IN_KEY, true),
+ menu: [{
+ id: MenuId.CommandPalette,
+ },
+ {
+ id: MenuId.AccountsContext,
+ group: '2_editSessions',
+ when: ContextKeyExpr.equals(EDIT_SESSIONS_SIGNED_IN_KEY, true),
+ }]
+ });
+ }
+
+ async run() {
+ const result = await that.dialogService.confirm({
+ type: 'info',
+ message: localize('sign out of edit sessions clear data prompt', 'Do you want to sign out of edit sessions?'),
+ checkbox: { label: localize('delete all edit sessions', 'Delete all stored edit sessions from the cloud.') },
+ primaryButton: localize('clear data confirm', 'Yes'),
+ });
+ if (result.confirmed) {
+ if (result.checkboxChecked) {
+ that.storeClient?.delete('editSessions', null);
+ }
+ that.clearAuthenticationPreference();
+ }
+ }
+ }));
+ }
+}
diff --git a/src/vs/workbench/contrib/editSessions/common/editSessions.ts b/src/vs/workbench/contrib/editSessions/common/editSessions.ts
new file mode 100644
index 00000000000..789976a50e5
--- /dev/null
+++ b/src/vs/workbench/contrib/editSessions/common/editSessions.ts
@@ -0,0 +1,67 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { localize } from 'vs/nls';
+import { ILocalizedString } from 'vs/platform/action/common/action';
+import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
+import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
+import { ILogService } from 'vs/platform/log/common/log';
+
+export const EDIT_SESSION_SYNC_CATEGORY: ILocalizedString = {
+ original: 'Edit Sessions',
+ value: localize('session sync', 'Edit Sessions')
+};
+
+export const IEditSessionsWorkbenchService = createDecorator<IEditSessionsWorkbenchService>('IEditSessionsWorkbenchService');
+export interface IEditSessionsWorkbenchService {
+ _serviceBrand: undefined;
+
+ read(ref: string | undefined): Promise<{ ref: string; editSession: EditSession } | undefined>;
+ write(editSession: EditSession): Promise<string>;
+ delete(ref: string): Promise<void>;
+}
+
+export const IEditSessionsLogService = createDecorator<IEditSessionsLogService>('IEditSessionsLogService');
+export interface IEditSessionsLogService extends ILogService { }
+
+export enum ChangeType {
+ Addition = 1,
+ Deletion = 2,
+}
+
+export enum FileType {
+ File = 1,
+}
+
+interface Addition {
+ relativeFilePath: string;
+ fileType: FileType.File;
+ contents: string;
+ type: ChangeType.Addition;
+}
+
+interface Deletion {
+ relativeFilePath: string;
+ fileType: FileType.File;
+ contents: undefined;
+ type: ChangeType.Deletion;
+}
+
+export type Change = Addition | Deletion;
+
+export interface Folder {
+ name: string;
+ workingChanges: Change[];
+}
+
+export const EditSessionSchemaVersion = 1;
+
+export interface EditSession {
+ version: number;
+ folders: Folder[];
+}
+
+export const EDIT_SESSIONS_SIGNED_IN_KEY = 'editSessionsSignedIn';
+export const EDIT_SESSIONS_SIGNED_IN = new RawContextKey<boolean>(EDIT_SESSIONS_SIGNED_IN_KEY, false);
diff --git a/src/vs/workbench/contrib/editSessions/common/editSessionsLogService.ts b/src/vs/workbench/contrib/editSessions/common/editSessionsLogService.ts
new file mode 100644
index 00000000000..a18c9e29304
--- /dev/null
+++ b/src/vs/workbench/contrib/editSessions/common/editSessionsLogService.ts
@@ -0,0 +1,50 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { IEnvironmentService } from 'vs/platform/environment/common/environment';
+import { AbstractLogger, ILogger, ILoggerService } from 'vs/platform/log/common/log';
+import { IEditSessionsLogService } from 'vs/workbench/contrib/editSessions/common/editSessions';
+
+export class EditSessionsLogService extends AbstractLogger implements IEditSessionsLogService {
+
+ declare readonly _serviceBrand: undefined;
+ private readonly logger: ILogger;
+
+ constructor(
+ @ILoggerService loggerService: ILoggerService,
+ @IEnvironmentService environmentService: IEnvironmentService
+ ) {
+ super();
+ this.logger = this._register(loggerService.createLogger(environmentService.editSessionsLogResource, { name: 'editsessions' }));
+ }
+
+ trace(message: string, ...args: any[]): void {
+ this.logger.trace(message, ...args);
+ }
+
+ debug(message: string, ...args: any[]): void {
+ this.logger.debug(message, ...args);
+ }
+
+ info(message: string, ...args: any[]): void {
+ this.logger.info(message, ...args);
+ }
+
+ warn(message: string, ...args: any[]): void {
+ this.logger.warn(message, ...args);
+ }
+
+ error(message: string | Error, ...args: any[]): void {
+ this.logger.error(message, ...args);
+ }
+
+ critical(message: string | Error, ...args: any[]): void {
+ this.logger.critical(message, ...args);
+ }
+
+ flush(): void {
+ this.logger.flush();
+ }
+}
diff --git a/src/vs/workbench/contrib/sessionSync/test/browser/sessionSync.test.ts b/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts
index 5c29394116b..07917f285e5 100644
--- a/src/vs/workbench/contrib/sessionSync/test/browser/sessionSync.test.ts
+++ b/src/vs/workbench/contrib/editSessions/test/browser/editSessions.test.ts
@@ -9,8 +9,8 @@ import { FileService } from 'vs/platform/files/common/fileService';
import { Schemas } from 'vs/base/common/network';
import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
-import { NullLogService, ILogService } from 'vs/platform/log/common/log';
-import { SessionSyncContribution } from 'vs/workbench/contrib/sessionSync/browser/sessionSync.contribution';
+import { NullLogService } from 'vs/platform/log/common/log';
+import { EditSessionsContribution } from 'vs/workbench/contrib/editSessions/browser/editSessions.contribution';
import { ProgressService } from 'vs/workbench/services/progress/browser/progressService';
import { IProgressService } from 'vs/platform/progress/common/progress';
import { ISCMService } from 'vs/workbench/contrib/scm/common/scm';
@@ -21,7 +21,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace
import { mock } from 'vs/base/test/common/mock';
import * as sinon from 'sinon';
import * as assert from 'assert';
-import { ChangeType, FileType, ISessionSyncWorkbenchService } from 'vs/workbench/services/sessionSync/common/sessionSync';
+import { ChangeType, FileType, IEditSessionsLogService, IEditSessionsWorkbenchService } from 'vs/workbench/contrib/editSessions/common/editSessions';
import { URI } from 'vs/base/common/uri';
import { joinPath } from 'vs/base/common/resources';
import { INotificationService } from 'vs/platform/notification/common/notification';
@@ -34,7 +34,7 @@ const folderUri = URI.file(`/${folderName}`);
suite('Edit session sync', () => {
let instantiationService: TestInstantiationService;
- let sessionSyncContribution: SessionSyncContribution;
+ let editSessionsContribution: EditSessionsContribution;
let fileService: FileService;
let sandbox: sinon.SinonSandbox;
@@ -52,14 +52,14 @@ suite('Edit session sync', () => {
fileService.registerProvider(Schemas.file, fileSystemProvider);
// Stub out all services
- instantiationService.stub(ILogService, logService);
+ instantiationService.stub(IEditSessionsLogService, logService);
instantiationService.stub(IFileService, fileService);
instantiationService.stub(INotificationService, new TestNotificationService());
- instantiationService.stub(ISessionSyncWorkbenchService, new class extends mock<ISessionSyncWorkbenchService>() { });
+ instantiationService.stub(IEditSessionsWorkbenchService, new class extends mock<IEditSessionsWorkbenchService>() { });
instantiationService.stub(IProgressService, ProgressService);
instantiationService.stub(ISCMService, SCMService);
instantiationService.stub(IEnvironmentService, TestEnvironmentService);
- instantiationService.stub(IConfigurationService, new TestConfigurationService({ workbench: { experimental: { sessionSync: { enabled: true } } } }));
+ instantiationService.stub(IConfigurationService, new TestConfigurationService({ workbench: { experimental: { editSessions: { enabled: true } } } }));
instantiationService.stub(IWorkspaceContextService, new class extends mock<IWorkspaceContextService>() {
override getWorkspace() {
return {
@@ -77,7 +77,7 @@ suite('Edit session sync', () => {
// Stub repositories
instantiationService.stub(ISCMService, '_repositories', new Map());
- sessionSyncContribution = instantiationService.createInstance(SessionSyncContribution);
+ editSessionsContribution = instantiationService.createInstance(EditSessionsContribution);
});
teardown(() => {
@@ -107,13 +107,13 @@ suite('Edit session sync', () => {
// Stub sync service to return edit session data
const readStub = sandbox.stub().returns({ editSession, ref: '0' });
- instantiationService.stub(ISessionSyncWorkbenchService, 'read', readStub);
+ instantiationService.stub(IEditSessionsWorkbenchService, 'read', readStub);
// Create root folder
await fileService.createFolder(folderUri);
- // Apply edit session
- await sessionSyncContribution.applyEditSession();
+ // Resume edit session
+ await editSessionsContribution.resumeEditSession();
// Verify edit session was correctly applied
assert.equal((await fileService.readFile(fileUri)).value.toString(), fileContents);
@@ -121,12 +121,12 @@ suite('Edit session sync', () => {
test('Edit session not stored if there are no edits', async function () {
const writeStub = sandbox.stub();
- instantiationService.stub(ISessionSyncWorkbenchService, 'write', writeStub);
+ instantiationService.stub(IEditSessionsWorkbenchService, 'write', writeStub);
// Create root folder
await fileService.createFolder(folderUri);
- await sessionSyncContribution.storeEditSession(true);
+ await editSessionsContribution.storeEditSession(true);
// Verify that we did not attempt to write the edit session
assert.equal(writeStub.called, false);
diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts
index 381096c914f..c8632a19ecb 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
+ InstallAnotherVersionAction, ExtensionEditorManageExtensionAction, WebInstallAction, SwitchToPreReleaseVersionAction, SwitchToReleasedVersionAction, MigrateDeprecatedExtensionAction, SetLanguageAction
} from 'vs/workbench/contrib/extensions/browser/extensionsActions';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
@@ -329,6 +329,7 @@ export class ExtensionEditor extends EditorPane {
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 f64e267746c..f53f6a7de8a 100644
--- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts
+++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts
@@ -77,6 +77,8 @@ import { UnsupportedExtensionsMigrationContrib } from 'vs/workbench/contrib/exte
import { isWeb } from 'vs/base/common/platform';
import { ExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage';
import { IStorageService } from 'vs/platform/storage/common/storage';
+import product from 'vs/platform/product/common/product';
+import { IStringDictionary } from 'vs/base/common/collections';
// Singletons
registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService);
@@ -228,7 +230,7 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
'extensions.experimental.useUtilityProcess': {
type: 'boolean',
description: localize('extensionsUseUtilityProcess', "When enabled, the extension host will be launched using the new UtilityProcess Electron API."),
- default: false
+ default: product.quality === 'stable' ? false : true // disabled by default in stable for now
},
[WORKSPACE_TRUST_EXTENSION_SUPPORT]: {
type: 'object',
@@ -315,13 +317,17 @@ CommandsRegistry.registerCommand({
'type': 'boolean',
'description': localize('workbench.extensions.installExtension.option.donotSync', "When enabled, VS Code do not sync this extension when Settings Sync is on."),
default: false
+ },
+ 'context': {
+ 'type': 'object',
+ 'description': localize('workbench.extensions.installExtension.option.context', "Context for the installation. This is a JSON object that can be used to pass any information to the installation handlers. i.e. `{skipWalkthrough: true}` will skip opening the walkthrough upon install."),
}
}
}
}
]
},
- handler: async (accessor, arg: string | UriComponents, options?: { installOnlyNewlyAddedFromExtensionPackVSIX?: boolean; installPreReleaseVersion?: boolean; donotSync?: boolean }) => {
+ handler: async (accessor, arg: string | UriComponents, options?: { installOnlyNewlyAddedFromExtensionPackVSIX?: boolean; installPreReleaseVersion?: boolean; donotSync?: boolean; context?: IStringDictionary<any> }) => {
const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService);
try {
if (typeof arg === 'string') {
@@ -331,7 +337,8 @@ CommandsRegistry.registerCommand({
const installOptions: InstallOptions = {
isMachineScoped: options?.donotSync ? true : undefined, /* do not allow syncing extensions automatically while installing through the command */
installPreReleaseVersion: options?.installPreReleaseVersion,
- installGivenVersion: !!version
+ installGivenVersion: !!version,
+ context: options?.context
};
if (version) {
await extensionsWorkbenchService.installVersion(extension, version, installOptions);
@@ -445,7 +452,7 @@ async function runAction(action: IAction): Promise<void> {
}
interface IExtensionActionOptions extends IAction2Options {
- menuTitles?: { [id: number]: string };
+ menuTitles?: { [id: string]: string };
run(accessor: ServicesAccessor, ...args: any[]): Promise<any>;
}
diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts
index 710e811a4f7..23ddb1a771c 100644
--- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts
+++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts
@@ -14,7 +14,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
import { dispose } from 'vs/base/common/lifecycle';
import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewPaneContainer, IExtensionContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID, SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID, THEME_ACTIONS_GROUP, INSTALL_ACTIONS_GROUP } from 'vs/workbench/contrib/extensions/common/extensions';
import { ExtensionsConfigurationInitialContent } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate';
-import { IGalleryExtension, IExtensionGalleryService, ILocalExtension, InstallOptions, InstallOperation, TargetPlatformToString, ExtensionManagementErrorCode } from 'vs/platform/extensionManagement/common/extensionManagement';
+import { IGalleryExtension, IExtensionGalleryService, ILocalExtension, InstallOptions, InstallOperation, TargetPlatformToString, ExtensionManagementErrorCode, isTargetPlatformCompatible } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { ExtensionRecommendationReason, IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
import { areSameExtensions, getExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
@@ -56,7 +56,7 @@ import { IContextMenuProvider } from 'vs/base/browser/contextmenu';
import { ILogService } from 'vs/platform/log/common/log';
import * as Constants from 'vs/workbench/contrib/logs/common/logConstants';
import { errorIcon, infoIcon, manageExtensionIcon, preReleaseIcon, syncEnabledIcon, syncIgnoredIcon, trustIcon, warningIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons';
-import { isIOS, isWeb } from 'vs/base/common/platform';
+import { isIOS, isWeb, language } from 'vs/base/common/platform';
import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService';
import { IWorkspaceTrustEnablementService, IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust';
import { isVirtualWorkspace } from 'vs/platform/workspace/common/virtualWorkspace';
@@ -66,6 +66,7 @@ import { ViewContainerLocation } from 'vs/workbench/common/views';
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';
export class PromptExtensionInstallFailureAction extends Action {
@@ -264,11 +265,18 @@ export abstract class AbstractInstallAction extends ExtensionAction {
protected async computeAndUpdateEnablement(): Promise<void> {
this.enabled = false;
- if (this.extension && !this.extension.isBuiltin) {
- if (this.extension.state === ExtensionState.Uninstalled && await this.extensionsWorkbenchService.canInstall(this.extension)) {
- this.enabled = this.installPreReleaseVersion ? this.extension.hasPreReleaseVersion : this.extension.hasReleaseVersion;
- this.updateLabel();
- }
+ if (!this.extension) {
+ return;
+ }
+ if (this.extension.isBuiltin) {
+ return;
+ }
+ if (this.extensionsWorkbenchService.canSetLanguage(this.extension)) {
+ return;
+ }
+ if (this.extension.state === ExtensionState.Uninstalled && await this.extensionsWorkbenchService.canInstall(this.extension)) {
+ this.enabled = this.installPreReleaseVersion ? this.extension.hasPreReleaseVersion : this.extension.hasReleaseVersion;
+ this.updateLabel();
}
}
@@ -561,6 +569,7 @@ export abstract class InstallInOtherServerAction extends ExtensionAction {
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
@IExtensionManagementServerService protected readonly extensionManagementServerService: IExtensionManagementServerService,
@IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService,
+ @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
) {
super(id, InstallInOtherServerAction.INSTALL_LABEL, InstallInOtherServerAction.Class, false);
this.update();
@@ -635,19 +644,29 @@ export abstract class InstallInOtherServerAction extends ExtensionAction {
}
override async run(): Promise<void> {
- if (!this.extension) {
+ if (!this.extension?.local) {
return;
}
- if (this.server) {
- this.extensionsWorkbenchService.open(this.extension);
- alert(localize('installExtensionStart', "Installing extension {0} started. An editor is now open with more details on this extension", this.extension.displayName));
- if (this.extension.gallery) {
- await this.server.extensionManagementService.installFromGallery(this.extension.gallery, { installPreReleaseVersion: this.extension.local?.preRelease });
- } else {
- const vsix = await this.extension.server!.extensionManagementService.zip(this.extension.local!);
- await this.server.extensionManagementService.install(vsix);
- }
+ if (!this.extension?.server) {
+ return;
+ }
+ if (!this.server) {
+ return;
+ }
+ this.extensionsWorkbenchService.open(this.extension);
+ alert(localize('installExtensionStart', "Installing extension {0} started. An editor is now open with more details on this extension", this.extension.displayName));
+
+ const gallery = this.extension.gallery ?? (this.extensionGalleryService.isEnabled() && (await this.extensionGalleryService.getExtensions([this.extension.identifier], CancellationToken.None))[0]);
+ if (gallery) {
+ await this.server.extensionManagementService.installFromGallery(gallery, { installPreReleaseVersion: this.extension.local.preRelease });
+ return;
+ }
+ const targetPlatform = await this.server.extensionManagementService.getTargetPlatform();
+ if (!isTargetPlatformCompatible(this.extension.local.targetPlatform, [this.extension.local.targetPlatform], targetPlatform)) {
+ throw new Error(localize('incompatible', "Can't install '{0}' extension because it is not compatible.", this.extension.identifier.id));
}
+ const vsix = await this.extension.server.extensionManagementService.zip(this.extension.local);
+ await this.server.extensionManagementService.install(vsix);
}
protected abstract getInstallLabel(): string;
@@ -660,8 +679,9 @@ export class RemoteInstallAction extends InstallInOtherServerAction {
@IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService,
@IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService,
@IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService,
+ @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService,
) {
- super(`extensions.remoteinstall`, extensionManagementServerService.remoteExtensionManagementServer, canInstallAnyWhere, extensionsWorkbenchService, extensionManagementServerService, extensionManifestPropertiesService);
+ super(`extensions.remoteinstall`, extensionManagementServerService.remoteExtensionManagementServer, canInstallAnyWhere, extensionsWorkbenchService, extensionManagementServerService, extensionManifestPropertiesService, extensionGalleryService);
}
protected getInstallLabel(): string {
@@ -678,8 +698,9 @@ export class LocalInstallAction extends InstallInOtherServerAction {
@IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService,
@IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService,
@IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService,
+ @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService,
) {
- super(`extensions.localinstall`, extensionManagementServerService.localExtensionManagementServer, false, extensionsWorkbenchService, extensionManagementServerService, extensionManifestPropertiesService);
+ super(`extensions.localinstall`, extensionManagementServerService.localExtensionManagementServer, false, extensionsWorkbenchService, extensionManagementServerService, extensionManifestPropertiesService, extensionGalleryService);
}
protected getInstallLabel(): string {
@@ -694,8 +715,9 @@ export class WebInstallAction extends InstallInOtherServerAction {
@IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService,
@IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService,
@IExtensionManifestPropertiesService extensionManifestPropertiesService: IExtensionManifestPropertiesService,
+ @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService,
) {
- super(`extensions.webInstall`, extensionManagementServerService.webExtensionManagementServer, false, extensionsWorkbenchService, extensionManagementServerService, extensionManifestPropertiesService);
+ super(`extensions.webInstall`, extensionManagementServerService.webExtensionManagementServer, false, extensionsWorkbenchService, extensionManagementServerService, extensionManifestPropertiesService, extensionGalleryService);
}
protected getInstallLabel(): string {
@@ -1623,38 +1645,38 @@ export class SetColorThemeAction extends ExtensionAction {
private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} theme`;
private static readonly DisabledClass = `${SetColorThemeAction.EnabledClass} disabled`;
- private colorThemes: IWorkbenchColorTheme[] = [];
-
constructor(
@IExtensionService extensionService: IExtensionService,
@IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService,
@IQuickInputService private readonly quickInputService: IQuickInputService,
+ @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,
) {
super(SetColorThemeAction.ID, SetColorThemeAction.TITLE.value, SetColorThemeAction.DisabledClass, false);
this._register(Event.any<any>(extensionService.onDidChangeExtensions, workbenchThemeService.onDidColorThemeChange)(() => this.update(), this));
- workbenchThemeService.getColorThemes().then(colorThemes => {
- this.colorThemes = colorThemes;
- this.update();
- });
this.update();
}
update(): void {
- this.enabled = !!this.extension && (this.extension.state === ExtensionState.Installed) && this.colorThemes.some(th => isThemeFromExtension(th, this.extension));
- this.class = this.enabled ? SetColorThemeAction.EnabledClass : SetColorThemeAction.DisabledClass;
+ this.workbenchThemeService.getColorThemes().then(colorThemes => {
+ this.enabled = this.computeEnablement(colorThemes);
+ this.class = this.enabled ? SetColorThemeAction.EnabledClass : SetColorThemeAction.DisabledClass;
+ });
+ }
+
+ private computeEnablement(colorThemes: IWorkbenchColorTheme[]): boolean {
+ return !!this.extension && this.extension.state === ExtensionState.Installed && this.extensionEnablementService.isEnabledEnablementState(this.extension.enablementState) && colorThemes.some(th => isThemeFromExtension(th, this.extension));
}
override async run({ showCurrentTheme, ignoreFocusLost }: { showCurrentTheme: boolean; ignoreFocusLost: boolean } = { showCurrentTheme: false, ignoreFocusLost: false }): Promise<any> {
- this.colorThemes = await this.workbenchThemeService.getColorThemes();
+ const colorThemes = await this.workbenchThemeService.getColorThemes();
- this.update();
- if (!this.enabled) {
+ if (!this.computeEnablement(colorThemes)) {
return;
}
const currentTheme = this.workbenchThemeService.getColorTheme();
const delayer = new Delayer<any>(100);
- const picks = getQuickPickEntries(this.colorThemes, currentTheme, this.extension, showCurrentTheme);
+ const picks = getQuickPickEntries(colorThemes, currentTheme, this.extension, showCurrentTheme);
const pickedTheme = await this.quickInputService.pick(
picks,
{
@@ -1674,37 +1696,37 @@ export class SetFileIconThemeAction extends ExtensionAction {
private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} theme`;
private static readonly DisabledClass = `${SetFileIconThemeAction.EnabledClass} disabled`;
- private fileIconThemes: IWorkbenchFileIconTheme[] = [];
-
constructor(
@IExtensionService extensionService: IExtensionService,
@IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService,
- @IQuickInputService private readonly quickInputService: IQuickInputService
+ @IQuickInputService private readonly quickInputService: IQuickInputService,
+ @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,
) {
super(SetFileIconThemeAction.ID, SetFileIconThemeAction.TITLE.value, SetFileIconThemeAction.DisabledClass, false);
this._register(Event.any<any>(extensionService.onDidChangeExtensions, workbenchThemeService.onDidFileIconThemeChange)(() => this.update(), this));
- workbenchThemeService.getFileIconThemes().then(fileIconThemes => {
- this.fileIconThemes = fileIconThemes;
- this.update();
- });
this.update();
}
update(): void {
- this.enabled = !!this.extension && (this.extension.state === ExtensionState.Installed) && this.fileIconThemes.some(th => isThemeFromExtension(th, this.extension));
- this.class = this.enabled ? SetFileIconThemeAction.EnabledClass : SetFileIconThemeAction.DisabledClass;
+ this.workbenchThemeService.getFileIconThemes().then(fileIconThemes => {
+ this.enabled = this.computeEnablement(fileIconThemes);
+ this.class = this.enabled ? SetFileIconThemeAction.EnabledClass : SetFileIconThemeAction.DisabledClass;
+ });
+ }
+
+ private computeEnablement(colorThemfileIconThemess: IWorkbenchFileIconTheme[]): boolean {
+ return !!this.extension && this.extension.state === ExtensionState.Installed && this.extensionEnablementService.isEnabledEnablementState(this.extension.enablementState) && colorThemfileIconThemess.some(th => isThemeFromExtension(th, this.extension));
}
override async run({ showCurrentTheme, ignoreFocusLost }: { showCurrentTheme: boolean; ignoreFocusLost: boolean } = { showCurrentTheme: false, ignoreFocusLost: false }): Promise<any> {
- this.fileIconThemes = await this.workbenchThemeService.getFileIconThemes();
- this.update();
- if (!this.enabled) {
+ const fileIconThemes = await this.workbenchThemeService.getFileIconThemes();
+ if (!this.computeEnablement(fileIconThemes)) {
return;
}
const currentTheme = this.workbenchThemeService.getFileIconTheme();
const delayer = new Delayer<any>(100);
- const picks = getQuickPickEntries(this.fileIconThemes, currentTheme, this.extension, showCurrentTheme);
+ const picks = getQuickPickEntries(fileIconThemes, currentTheme, this.extension, showCurrentTheme);
const pickedTheme = await this.quickInputService.pick(
picks,
{
@@ -1724,38 +1746,38 @@ export class SetProductIconThemeAction extends ExtensionAction {
private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} theme`;
private static readonly DisabledClass = `${SetProductIconThemeAction.EnabledClass} disabled`;
- private productIconThemes: IWorkbenchProductIconTheme[] = [];
-
constructor(
@IExtensionService extensionService: IExtensionService,
@IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService,
- @IQuickInputService private readonly quickInputService: IQuickInputService
+ @IQuickInputService private readonly quickInputService: IQuickInputService,
+ @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService,
) {
super(SetProductIconThemeAction.ID, SetProductIconThemeAction.TITLE.value, SetProductIconThemeAction.DisabledClass, false);
this._register(Event.any<any>(extensionService.onDidChangeExtensions, workbenchThemeService.onDidProductIconThemeChange)(() => this.update(), this));
- workbenchThemeService.getProductIconThemes().then(productIconThemes => {
- this.productIconThemes = productIconThemes;
- this.update();
- });
this.update();
}
update(): void {
- this.enabled = !!this.extension && (this.extension.state === ExtensionState.Installed) && this.productIconThemes.some(th => isThemeFromExtension(th, this.extension));
- this.class = this.enabled ? SetProductIconThemeAction.EnabledClass : SetProductIconThemeAction.DisabledClass;
+ this.workbenchThemeService.getProductIconThemes().then(productIconThemes => {
+ this.enabled = this.computeEnablement(productIconThemes);
+ this.class = this.enabled ? SetProductIconThemeAction.EnabledClass : SetProductIconThemeAction.DisabledClass;
+ });
+ }
+
+ private computeEnablement(productIconThemes: IWorkbenchProductIconTheme[]): boolean {
+ return !!this.extension && this.extension.state === ExtensionState.Installed && this.extensionEnablementService.isEnabledEnablementState(this.extension.enablementState) && productIconThemes.some(th => isThemeFromExtension(th, this.extension));
}
override async run({ showCurrentTheme, ignoreFocusLost }: { showCurrentTheme: boolean; ignoreFocusLost: boolean } = { showCurrentTheme: false, ignoreFocusLost: false }): Promise<any> {
- this.productIconThemes = await this.workbenchThemeService.getProductIconThemes();
- this.update();
- if (!this.enabled) {
+ const productIconThemes = await this.workbenchThemeService.getProductIconThemes();
+ if (!this.computeEnablement(productIconThemes)) {
return;
}
const currentTheme = this.workbenchThemeService.getProductIconTheme();
const delayer = new Delayer<any>(100);
- const picks = getQuickPickEntries(this.productIconThemes, currentTheme, this.extension, showCurrentTheme);
+ const picks = getQuickPickEntries(productIconThemes, currentTheme, this.extension, showCurrentTheme);
const pickedTheme = await this.quickInputService.pick(
picks,
{
@@ -1767,6 +1789,43 @@ 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' };
+
+ private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} theme`;
+ private static readonly DisabledClass = `${SetLanguageAction.EnabledClass} disabled`;
+
+ constructor(
+ @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
+ @ILanguagePackService private readonly languagePackService: ILanguagePackService,
+ ) {
+ super(SetLanguageAction.ID, SetLanguageAction.TITLE.value, SetLanguageAction.DisabledClass, false);
+ this.update();
+ }
+
+ update(): void {
+ this.enabled = false;
+ this.class = SetLanguageAction.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 = SetLanguageAction.EnabledClass;
+ }
+
+ override async run(): Promise<any> {
+ return this.extension && this.extensionsWorkbenchService.setLanguage(this.extension);
+ }
+}
+
export class ShowRecommendedExtensionAction extends Action {
static readonly ID = 'workbench.extensions.action.showRecommendedExtension';
@@ -2259,6 +2318,10 @@ export class ExtensionStatusAction extends ExtensionAction {
return;
}
+ if (this.extensionsWorkbenchService.canSetLanguage(this.extension)) {
+ return;
+ }
+
if (this.extension.gallery && this.extension.state === ExtensionState.Uninstalled && !await this.extensionsWorkbenchService.canInstall(this.extension)) {
if (this.extensionManagementServerService.localExtensionManagementServer || this.extensionManagementServerService.remoteExtensionManagementServer) {
const targetPlatform = await (this.extensionManagementServerService.localExtensionManagementServer ? this.extensionManagementServerService.localExtensionManagementServer!.extensionManagementService.getTargetPlatform() : this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.getTargetPlatform());
diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts
index 1754e2233ab..84eaa15be18 100644
--- a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts
+++ b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts
@@ -13,7 +13,7 @@ import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { IPagedRenderer } from 'vs/base/browser/ui/list/listPaging';
import { Event } from 'vs/base/common/event';
import { IExtension, ExtensionContainers, ExtensionState, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions';
-import { UpdateAction, ManageExtensionAction, ReloadAction, ExtensionStatusLabelAction, RemoteInstallAction, ExtensionStatusAction, LocalInstallAction, ActionWithDropDownAction, InstallDropdownAction, InstallingLabelAction, ExtensionActionWithDropdownActionViewItem, ExtensionDropDownAction, WebInstallAction, SwitchToPreReleaseVersionAction, SwitchToReleasedVersionAction, MigrateDeprecatedExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
+import { UpdateAction, ManageExtensionAction, ReloadAction, ExtensionStatusLabelAction, RemoteInstallAction, ExtensionStatusAction, LocalInstallAction, ActionWithDropDownAction, InstallDropdownAction, InstallingLabelAction, ExtensionActionWithDropdownActionViewItem, ExtensionDropDownAction, WebInstallAction, SwitchToPreReleaseVersionAction, SwitchToReleasedVersionAction, MigrateDeprecatedExtensionAction, SetLanguageAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { RatingsWidget, InstallCountWidget, RecommendationWidget, RemoteBadgeWidget, ExtensionPackCountWidget as ExtensionPackBadgeWidget, SyncIgnoredWidget, ExtensionHoverWidget, ExtensionActivationStatusWidget, PreReleaseBookmarkWidget, extensionVerifiedPublisherIconColor } from 'vs/workbench/contrib/extensions/browser/extensionsWidgets';
import { IExtensionService, toExtension } from 'vs/workbench/services/extensions/common/extensions';
@@ -123,6 +123,7 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
reloadAction,
this.instantiationService.createInstance(InstallDropdownAction),
this.instantiationService.createInstance(InstallingLabelAction),
+ this.instantiationService.createInstance(SetLanguageAction),
this.instantiationService.createInstance(RemoteInstallAction, false),
this.instantiationService.createInstance(LocalInstallAction),
this.instantiationService.createInstance(WebInstallAction),
@@ -186,16 +187,25 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
data.extensionDisposables = dispose(data.extensionDisposables);
- const updateEnablement = async () => {
- let disabled = false;
- const deprecated = !!extension.deprecationInfo;
+ const computeEnablement = async () => {
if (extension.state === ExtensionState.Uninstalled) {
- disabled = deprecated || !(await this.extensionsWorkbenchService.canInstall(extension));
+ if (!!extension.deprecationInfo) {
+ return true;
+ }
+ if (this.extensionsWorkbenchService.canSetLanguage(extension)) {
+ return false;
+ }
+ return !(await this.extensionsWorkbenchService.canInstall(extension));
} else if (extension.local && !isLanguagePackExtension(extension.local.manifest)) {
const runningExtensions = await this.extensionService.getExtensions();
const runningExtension = runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, extension.identifier))[0];
- disabled = !(runningExtension && extension.server === this.extensionManagementServerService.getExtensionManagementServer(toExtension(runningExtension)));
+ return !(runningExtension && extension.server === this.extensionManagementServerService.getExtensionManagementServer(toExtension(runningExtension)));
}
+ return false;
+ };
+ const updateEnablement = async () => {
+ const disabled = await computeEnablement();
+ const deprecated = !!extension.deprecationInfo;
data.element.classList.toggle('deprecated', deprecated);
data.root.classList.toggle('disabled', disabled);
};
diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts
index 6faf5d3c10a..31cc538fa56 100644
--- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts
+++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts
@@ -158,7 +158,12 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio
constructor() {
super({
id: 'workbench.extensions.installLocalExtensions',
- get title() { return localize('select and install local extensions', "Install Local Extensions in '{0}'...", server.label); },
+ get title() {
+ return {
+ value: localize('select and install local extensions', "Install Local Extensions in '{0}'...", server.label),
+ original: `Install Local Extensions in '${server.label}'...`,
+ };
+ },
category: localize({ key: 'remote', comment: ['Remote as in remote machine'] }, "Remote"),
icon: installLocalInRemoteIcon,
f1: true,
diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts
index a533f0ad244..6cf90434c7b 100644
--- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts
+++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts
@@ -45,8 +45,10 @@ import { isBoolean, isUndefined } from 'vs/base/common/types';
import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService';
import { IExtensionService, IExtensionsStatus } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionEditor } from 'vs/workbench/contrib/extensions/browser/extensionEditor';
-import { isWeb } from 'vs/base/common/platform';
+import { isWeb, language } from 'vs/base/common/platform';
import { GDPRClassification } from 'vs/platform/telemetry/common/gdprTypings';
+import { ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks';
+import { ILocaleService } from 'vs/workbench/contrib/localization/common/locale';
interface IExtensionStateProvider<T> {
(extension: Extension): T;
@@ -710,6 +712,8 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
@IExtensionManifestPropertiesService private readonly extensionManifestPropertiesService: IExtensionManifestPropertiesService,
@ILogService private readonly logService: ILogService,
@IExtensionService private readonly extensionService: IExtensionService,
+ @ILanguagePackService private readonly languagePackService: ILanguagePackService,
+ @ILocaleService private readonly localeService: ILocaleService,
) {
super();
const preferPreReleasesValue = configurationService.getValue('_extensions.preferPreReleases');
@@ -1248,6 +1252,34 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
return this.installWithProgress(() => this.installFromGallery(extension, gallery, installOptions), gallery.displayName, progressLocation);
}
+ canSetLanguage(extension: IExtension): boolean {
+ if (!isWeb) {
+ return false;
+ }
+
+ if (!extension.gallery) {
+ return false;
+ }
+
+ const locale = this.languagePackService.getLocale(extension.gallery);
+ if (!locale) {
+ return false;
+ }
+
+ return true;
+ }
+
+ async setLanguage(extension: IExtension): Promise<void> {
+ if (!this.canSetLanguage(extension)) {
+ throw new Error('Can not set language');
+ }
+ const locale = this.languagePackService.getLocale(extension.gallery!);
+ if (locale === language) {
+ return;
+ }
+ return this.localeService.setLocale({ id: locale, galleryExtension: extension.gallery, extensionId: extension.identifier.id, label: extension.displayName });
+ }
+
setEnablement(extensions: IExtension | IExtension[], enablementState: EnablementState): Promise<void> {
extensions = Array.isArray(extensions) ? extensions : [extensions];
return this.promptAndSetEnablement(extensions, enablementState);
diff --git a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts
index d1b91197ff4..57b4387c1c7 100644
--- a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts
+++ b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts
@@ -14,7 +14,7 @@ import { localize } from 'vs/nls';
import { StorageScope, IStorageService, StorageTarget } from 'vs/platform/storage/common/storage';
import { IProductService } from 'vs/platform/product/common/productService';
import { ImportantExtensionTip } from 'vs/base/common/product';
-import { forEach, IStringDictionary } from 'vs/base/common/collections';
+import { IStringDictionary } from 'vs/base/common/collections';
import { ITextModel } from 'vs/editor/common/model';
import { Schemas } from 'vs/base/common/network';
import { basename, extname } from 'vs/base/common/resources';
@@ -115,10 +115,10 @@ export class FileBasedRecommendations extends ExtensionRecommendations {
this.tasExperimentService = tasExperimentService;
if (productService.extensionTips) {
- forEach(productService.extensionTips, ({ key, value }) => this.extensionTips.set(key.toLowerCase(), value));
+ Object.entries(productService.extensionTips).forEach(([key, value]) => this.extensionTips.set(key.toLowerCase(), value));
}
if (productService.extensionImportantTips) {
- forEach(productService.extensionImportantTips, ({ key, value }) => this.importantExtensionTips.set(key.toLowerCase(), value));
+ Object.entries(productService.extensionImportantTips).forEach(([key, value]) => this.importantExtensionTips.set(key.toLowerCase(), value));
}
}
@@ -153,7 +153,7 @@ export class FileBasedRecommendations extends ExtensionRecommendations {
const cachedRecommendations = this.getCachedRecommendations();
const now = Date.now();
// Retire existing recommendations if they are older than a week or are not part of this.productService.extensionTips anymore
- forEach(cachedRecommendations, ({ key, value }) => {
+ Object.entries(cachedRecommendations).forEach(([key, value]) => {
const diff = (now - value) / milliSecondsInADay;
if (diff <= 7 && allRecommendations.indexOf(key) > -1) {
this.fileBasedRecommendations.set(key.toLowerCase(), { recommendedTime: value });
@@ -400,7 +400,7 @@ export class FileBasedRecommendations extends ExtensionRecommendations {
storedRecommendations = storedRecommendations.reduce((result, id) => { result[id] = Date.now(); return result; }, <IStringDictionary<number>>{});
}
const result: IStringDictionary<number> = {};
- forEach(storedRecommendations, ({ key, value }) => {
+ Object.entries(storedRecommendations).forEach(([key, value]) => {
if (typeof value === 'number') {
result[key.toLowerCase()] = value;
}
diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts
index 60c5b415c54..ca1a3a5ff3f 100644
--- a/src/vs/workbench/contrib/extensions/common/extensions.ts
+++ b/src/vs/workbench/contrib/extensions/common/extensions.ts
@@ -107,6 +107,8 @@ export interface IExtensionsWorkbenchService {
uninstall(extension: IExtension): Promise<void>;
installVersion(extension: IExtension, version: string, installOptions?: InstallOptions): Promise<IExtension>;
reinstall(extension: IExtension): Promise<IExtension>;
+ canSetLanguage(extension: IExtension): boolean;
+ setLanguage(extension: IExtension): Promise<void>;
setEnablement(extensions: IExtension | IExtension[], enablementState: EnablementState): Promise<void>;
open(extension: IExtension, options?: IExtensionEditorOptions): Promise<void>;
checkForUpdates(): Promise<void>;
diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts
index 2641877c8cd..67ab692b603 100644
--- a/src/vs/workbench/contrib/files/browser/fileCommands.ts
+++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts
@@ -628,21 +628,23 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
args: [
{
isOptional: true,
- name: 'viewType',
- description: 'The editor view type',
+ name: 'New Untitled File args',
+ description: 'The editor view type and language ID if known',
schema: {
'type': 'object',
- 'required': ['viewType'],
'properties': {
'viewType': {
'type': 'string'
+ },
+ 'languageId': {
+ 'type': 'string'
}
}
}
}
]
},
- handler: async (accessor, args?: { viewType: string }) => {
+ handler: async (accessor, args?: { languageId?: string; viewType?: string }) => {
const editorService = accessor.get(IEditorService);
await editorService.openEditor({
@@ -650,7 +652,8 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
options: {
override: args?.viewType,
pinned: true
- }
+ },
+ languageId: args?.languageId,
});
}
});
diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts
index 592d559451a..1ebf70ce257 100644
--- a/src/vs/workbench/contrib/files/browser/files.contribution.ts
+++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts
@@ -159,7 +159,7 @@ configurationRegistry.registerConfiguration({
'type': 'string', // expression ({ "**/*.js": { "when": "$(basename).js" } })
'pattern': '\\w*\\$\\(basename\\)\\w*',
'default': '$(basename).ext',
- 'markdownDescription': nls.localize('files.exclude.when', "Additional check on the siblings of a matching file. Use \\$(basename) as variable for the matching file name.")
+ 'markdownDescription': nls.localize({ key: 'files.exclude.when', comment: ['\\$(basename) should not be translated'] }, "Additional check on the siblings of a matching file. Use \\$(basename) as variable for the matching file name.")
}
}
}
@@ -185,7 +185,7 @@ configurationRegistry.registerConfiguration({
'files.autoGuessEncoding': {
'type': 'boolean',
'default': false,
- 'markdownDescription': nls.localize('autoGuessEncoding', "When enabled, the editor will attempt to guess the character set encoding when opening files. This setting can also be configured per language. Note, this setting is not respected by text search. Only `#files.encoding#` is respected."),
+ 'markdownDescription': nls.localize('autoGuessEncoding', "When enabled, the editor will attempt to guess the character set encoding when opening files. This setting can also be configured per language. Note, this setting is not respected by text search. Only {0} is respected.", '`#files.encoding#`'),
'scope': ConfigurationScope.LANGUAGE_OVERRIDABLE
},
'files.eol': {
@@ -475,7 +475,7 @@ configurationRegistry.registerConfiguration({
},
'explorer.excludeGitIgnore': {
type: 'boolean',
- markdownDescription: nls.localize('excludeGitignore', "Controls whether entries in .gitignore should be parsed and excluded from the explorer. Similar to `#files.exclude#`."),
+ markdownDescription: nls.localize('excludeGitignore', "Controls whether entries in .gitignore should be parsed and excluded from the explorer. Similar to {0}.", '`#files.exclude#`'),
default: false,
scope: ConfigurationScope.RESOURCE
},
@@ -487,7 +487,7 @@ configurationRegistry.registerConfiguration({
},
'explorer.fileNesting.expand': {
'type': 'boolean',
- 'markdownDescription': nls.localize('fileNestingExpand', "Controls whether file nests are automatically expanded. `#explorer.fileNesting.enabled#` must be set for this to take effect."),
+ 'markdownDescription': nls.localize('fileNestingExpand', "Controls whether file nests are automatically expanded. {0} must be set for this to take effect.", '`#explorer.fileNesting.enabled#`'),
'default': true,
},
'explorer.fileNesting.patterns': {
diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts
index 1e2e605ec5e..6185a906efa 100644
--- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts
+++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts
@@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri';
import * as perf from 'vs/base/common/performance';
import { IAction, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions';
import { memoize } from 'vs/base/common/decorators';
-import { IFilesConfiguration, ExplorerFolderContext, FilesExplorerFocusedContext, ExplorerFocusedContext, ExplorerRootContext, ExplorerResourceReadonlyContext, ExplorerResourceCut, ExplorerResourceMoveableToTrash, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext, ExplorerCompressedLastFocusContext, ExplorerResourceAvailableEditorIdsContext, VIEW_ID, VIEWLET_ID, ExplorerResourceNotReadonlyContext } from 'vs/workbench/contrib/files/common/files';
+import { IFilesConfiguration, ExplorerFolderContext, FilesExplorerFocusedContext, ExplorerFocusedContext, ExplorerRootContext, ExplorerResourceReadonlyContext, ExplorerResourceCut, ExplorerResourceMoveableToTrash, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext, ExplorerCompressedLastFocusContext, ExplorerResourceAvailableEditorIdsContext, VIEW_ID, VIEWLET_ID, ExplorerResourceNotReadonlyContext, ViewHasSomeCollapsibleRootItemContext } from 'vs/workbench/contrib/files/common/files';
import { FileCopiedContext, NEW_FILE_COMMAND_ID, NEW_FOLDER_COMMAND_ID } from 'vs/workbench/contrib/files/browser/fileActions';
import * as DOM from 'vs/base/browser/dom';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
@@ -67,13 +67,19 @@ interface IExplorerViewStyles {
listDropBackground?: Color;
}
-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;
- }
+// 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;
}
}
}
@@ -161,6 +167,8 @@ export class ExplorerView extends ViewPane implements IExplorerView {
private compressedFocusFirstContext: IContextKey<boolean>;
private compressedFocusLastContext: IContextKey<boolean>;
+ private viewHasSomeCollapsibleRootItem: IContextKey<boolean>;
+
private horizontalScrolling: boolean | undefined;
private dragHandler!: DelayedDragHandler;
@@ -207,6 +215,7 @@ export class ExplorerView extends ViewPane implements IExplorerView {
this.compressedFocusContext = ExplorerCompressedFocusContext.bindTo(contextKeyService);
this.compressedFocusFirstContext = ExplorerCompressedFirstFocusContext.bindTo(contextKeyService);
this.compressedFocusLastContext = ExplorerCompressedLastFocusContext.bindTo(contextKeyService);
+ this.viewHasSomeCollapsibleRootItem = ViewHasSomeCollapsibleRootItemContext.bindTo(contextKeyService);
this.explorerService.registerView(this);
}
@@ -493,6 +502,9 @@ export class ExplorerView extends ViewPane implements IExplorerView {
}
}));
+ this._register(this.tree.onDidChangeCollapseState(() => this.updateAnyCollapsedContext()));
+ this.updateAnyCollapsedContext();
+
this._register(this.tree.onMouseDblClick(e => {
if (e.element === null) {
// click in empty area -> create a new file #116676
@@ -769,6 +781,23 @@ export class ExplorerView extends ViewPane implements IExplorerView {
}
}
+ expandAll(): void {
+ if (this.explorerService.isEditable(undefined)) {
+ 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();
+ }
+
collapseAll(): void {
if (this.explorerService.isEditable(undefined)) {
this.tree.domFocus();
@@ -837,6 +866,14 @@ export class ExplorerView extends ViewPane implements IExplorerView {
this.compressedFocusLastContext.set(controller.index === controller.count - 1);
}
+ private updateAnyCollapsedContext(): void {
+ const treeInput = this.tree.getInput();
+ if (treeInput === undefined) {
+ return;
+ }
+ this.viewHasSomeCollapsibleRootItem.set(hasExpandedRootChild(this.tree, treeInput));
+ }
+
styleListDropBackground(styles: IExplorerViewStyles): void {
const content: string[] = [];
@@ -951,7 +988,7 @@ registerAction2(class extends Action2 {
menu: {
id: MenuId.ViewTitle,
group: 'navigation',
- when: ContextKeyExpr.equals('view', VIEW_ID),
+ when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_ID), ViewHasSomeCollapsibleRootItemContext),
order: 40
}
});
@@ -963,3 +1000,26 @@ registerAction2(class extends Action2 {
explorerView.collapseAll();
}
});
+
+registerAction2(class extends Action2 {
+ constructor() {
+ super({
+ id: 'workbench.files.action.expandExplorerFolders',
+ title: { value: nls.localize('expandExplorerFolders', "Expand Folders in Explorer"), original: 'Expand Folders in Explorer' },
+ f1: true,
+ icon: Codicon.expandAll,
+ menu: {
+ id: MenuId.ViewTitle,
+ group: 'navigation',
+ when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_ID), ViewHasSomeCollapsibleRootItemContext.toNegated()),
+ order: 40
+ }
+ });
+ }
+
+ run(accessor: ServicesAccessor) {
+ const viewsService = accessor.get(IViewsService);
+ const explorerView = viewsService.getViewWithId(VIEW_ID) as ExplorerView;
+ explorerView.expandAll();
+ }
+});
diff --git a/src/vs/workbench/contrib/files/common/files.ts b/src/vs/workbench/contrib/files/common/files.ts
index d903a67b219..b9500df93d9 100644
--- a/src/vs/workbench/contrib/files/common/files.ts
+++ b/src/vs/workbench/contrib/files/common/files.ts
@@ -56,6 +56,8 @@ export const ExplorerCompressedFocusContext = new RawContextKey<boolean>('explor
export const ExplorerCompressedFirstFocusContext = new RawContextKey<boolean>('explorerViewletCompressedFirstFocus', true, { type: 'boolean', description: localize('explorerViewletCompressedFirstFocus', "True when the focus is inside a compact item's first part in the EXPLORER view.") });
export const ExplorerCompressedLastFocusContext = new RawContextKey<boolean>('explorerViewletCompressedLastFocus', true, { type: 'boolean', description: localize('explorerViewletCompressedLastFocus', "True when the focus is inside a compact item's last part in the EXPLORER view.") });
+export const ViewHasSomeCollapsibleRootItemContext = new RawContextKey<boolean>('viewHasSomeCollapsibleItem', false, { type: 'boolean', description: localize('viewHasSomeCollapsibleItem', "True when a workspace in the EXPLORER view has some collapsible root child.") });
+
export const FilesExplorerFocusCondition = ContextKeyExpr.and(ExplorerViewletVisibleContext, FilesExplorerFocusedContext, ContextKeyExpr.not(InputFocusedContextKey));
export const ExplorerFocusCondition = ContextKeyExpr.and(ExplorerViewletVisibleContext, ExplorerFocusedContext, ContextKeyExpr.not(InputFocusedContextKey));
diff --git a/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts b/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts
index 2766516fdc0..e3d83192f51 100644
--- a/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts
+++ b/src/vs/workbench/contrib/inlayHints/browser/inlayHintsAccessibilty.ts
@@ -174,7 +174,10 @@ registerAction2(class StartReadHints extends EditorAction2 {
constructor() {
super({
id: 'inlayHints.startReadingLineWithHint',
- title: localize('read.title', 'Read Line With Inline Hints'),
+ title: {
+ value: localize('read.title', 'Read Line With Inline Hints'),
+ original: 'Read Line With Inline Hints'
+ },
precondition: EditorContextKeys.hasInlayHintsProvider,
f1: true
});
@@ -193,7 +196,10 @@ registerAction2(class StopReadHints extends EditorAction2 {
constructor() {
super({
id: 'inlayHints.stopReadingLineWithHint',
- title: localize('stop.title', 'Stop Inlay Hints Reading'),
+ title: {
+ value: localize('stop.title', 'Stop Inlay Hints Reading'),
+ original: 'Stop Inlay Hints Reading'
+ },
precondition: InlayHintsAccessibility.IsReading,
f1: true,
keybinding: {
diff --git a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts
index f108213e01b..9969bd9c645 100644
--- a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts
+++ b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts
@@ -9,6 +9,7 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { parse } from 'vs/base/common/marshalling';
import { Schemas } from 'vs/base/common/network';
+import { extname } from 'vs/base/common/resources';
import { isFalsyOrWhitespace } from 'vs/base/common/strings';
import { assertType } from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
@@ -23,9 +24,10 @@ import { peekViewBorder /*, peekViewEditorBackground, peekViewResultsBackground
import { Context as SuggestContext } from 'vs/editor/contrib/suggest/browser/suggest';
import { localize } from 'vs/nls';
import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
+import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
-import { EditorActivation } from 'vs/platform/editor/common/editor';
+import { EditorActivation, IResourceEditorInput } from 'vs/platform/editor/common/editor';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
@@ -37,20 +39,21 @@ import { contrastBorder, listInactiveSelectionBackground, registerColor, transpa
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor';
import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
-import { EditorExtensions, EditorsOrder, IEditorSerializer } from 'vs/workbench/common/editor';
+import { EditorExtensions, EditorsOrder, IEditorFactoryRegistry, IEditorSerializer } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
// import { Color } from 'vs/base/common/color';
import { PANEL_BORDER } from 'vs/workbench/common/theme';
import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits';
-import { INTERACTIVE_INPUT_CURSOR_BOUNDARY } from 'vs/workbench/contrib/interactive/browser/interactiveCommon';
+import { InteractiveWindowSetting, INTERACTIVE_INPUT_CURSOR_BOUNDARY } from 'vs/workbench/contrib/interactive/browser/interactiveCommon';
import { IInteractiveDocumentService, InteractiveDocumentService } from 'vs/workbench/contrib/interactive/browser/interactiveDocumentService';
import { InteractiveEditor } from 'vs/workbench/contrib/interactive/browser/interactiveEditor';
import { InteractiveEditorInput } from 'vs/workbench/contrib/interactive/browser/interactiveEditorInput';
import { IInteractiveHistoryService, InteractiveHistoryService } from 'vs/workbench/contrib/interactive/browser/interactiveHistoryService';
import { NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT } from 'vs/workbench/contrib/notebook/browser/controller/coreActions';
+import { INotebookEditorOptions } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget';
import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons';
-import { CellEditType, CellKind, ICellOutput, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon';
+import { CellEditType, CellKind, CellUri, ICellOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
import { INotebookContentProvider, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { columnToEditorGroup } from 'vs/workbench/services/editor/common/editorGroupColumn';
@@ -192,7 +195,7 @@ export class InteractiveDocumentContribution extends Disposable implements IWork
editorResolverService.registerEditor(
`${Schemas.vscodeInteractiveInput}:/**`,
{
- id: InteractiveEditorInput.ID,
+ id: 'vscode-interactive-input',
label: 'Interactive Editor',
priority: RegisteredEditorPriority.exclusive
},
@@ -209,17 +212,31 @@ export class InteractiveDocumentContribution extends Disposable implements IWork
editorResolverService.registerEditor(
`*.interactive`,
{
- id: InteractiveEditorInput.ID,
+ id: 'interactive',
label: 'Interactive Editor',
priority: RegisteredEditorPriority.exclusive
},
{
- canSupportResource: uri => uri.scheme === Schemas.vscodeInteractive,
+ canSupportResource: uri => uri.scheme === Schemas.vscodeInteractive || (uri.scheme === Schemas.vscodeNotebookCell && extname(uri) === '.interactive'),
singlePerResource: true
},
- ({ resource }) => {
- const editorInput = editorService.getEditors(EditorsOrder.SEQUENTIAL).find(editor => editor.editor instanceof InteractiveEditorInput && editor.editor.resource?.toString() === resource.toString());
- return editorInput!;
+ ({ resource, options }) => {
+ const data = CellUri.parse(resource);
+ let notebookUri: URI = resource;
+ let cellOptions: IResourceEditorInput | undefined;
+
+ if (data) {
+ notebookUri = data.notebook;
+ cellOptions = { resource, options };
+ }
+
+ const notebookOptions = { ...options, cellOptions } as INotebookEditorOptions;
+
+ const editorInput = editorService.getEditors(EditorsOrder.SEQUENTIAL).find(editor => editor.editor instanceof InteractiveEditorInput && editor.editor.resource?.toString() === notebookUri.toString());
+ return {
+ editor: editorInput!.editor,
+ options: notebookOptions
+ };
}
);
}
@@ -256,8 +273,13 @@ workbenchContributionsRegistry.registerWorkbenchContribution(InteractiveDocument
workbenchContributionsRegistry.registerWorkbenchContribution(InteractiveInputContentProvider, LifecyclePhase.Starting);
export class InteractiveEditorSerializer implements IEditorSerializer {
+ public static readonly ID = InteractiveEditorInput.ID;
+
+ constructor(@IConfigurationService private configurationService: IConfigurationService) {
+ }
+
canSerialize(): boolean {
- return true;
+ return this.configurationService.getValue<boolean>(InteractiveWindowSetting.interactiveWindowHotExit);
}
serialize(input: EditorInput): string {
@@ -265,11 +287,16 @@ export class InteractiveEditorSerializer implements IEditorSerializer {
return JSON.stringify({
resource: input.primary.resource,
inputResource: input.inputResource,
+ name: input.getName(),
+ data: input.getSerialization()
});
}
deserialize(instantiationService: IInstantiationService, raw: string) {
- type Data = { resource: URI; inputResource: URI };
+ if (!this.canSerialize()) {
+ return undefined;
+ }
+ type Data = { resource: URI; inputResource: URI; data: any };
const data = <Data>parse(raw);
if (!data) {
return undefined;
@@ -280,14 +307,15 @@ export class InteractiveEditorSerializer implements IEditorSerializer {
}
const input = InteractiveEditorInput.create(instantiationService, resource, inputResource);
+ input.restoreSerialization(data.data);
return input;
}
}
-// Registry.as<EditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories).registerEditorInputSerializer(
-// InteractiveEditorInput.ID,
-// InteractiveEditorSerializer
-// );
+Registry.as<IEditorFactoryRegistry>(EditorExtensions.EditorFactory)
+ .registerEditorSerializer(
+ InteractiveEditorSerializer.ID,
+ InteractiveEditorSerializer);
registerSingleton(IInteractiveHistoryService, InteractiveHistoryService);
registerSingleton(IInteractiveDocumentService, InteractiveDocumentService);
@@ -722,15 +750,20 @@ registerThemingParticipant((theme) => {
});
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).registerConfiguration({
- id: 'notebook',
+ id: 'interactiveWindow',
order: 100,
type: 'object',
'properties': {
- [NotebookSetting.interactiveWindowAlwaysScrollOnNewCell]: {
+ [InteractiveWindowSetting.interactiveWindowAlwaysScrollOnNewCell]: {
type: 'boolean',
default: true,
markdownDescription: localize('interactiveWindow.alwaysScrollOnNewCell', "Automatically scroll the interactive window to show the output of the last statement executed. If this value is false, the window will only scroll if the last cell was already the one scrolled to.")
},
+ [InteractiveWindowSetting.interactiveWindowHotExit]: {
+ type: 'boolean',
+ default: false,
+ markdownDescription: localize('interactiveWindow.hotExit', "Controls whether the interactive window sessions should be restored when the workspace reloads.")
+ }
}
});
diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveCommon.ts b/src/vs/workbench/contrib/interactive/browser/interactiveCommon.ts
index ef37c64c93b..edda5c7f25f 100644
--- a/src/vs/workbench/contrib/interactive/browser/interactiveCommon.ts
+++ b/src/vs/workbench/contrib/interactive/browser/interactiveCommon.ts
@@ -6,3 +6,8 @@
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
export const INTERACTIVE_INPUT_CURSOR_BOUNDARY = new RawContextKey<'none' | 'top' | 'bottom' | 'both'>('interactiveInputCursorAtBoundary', 'none');
+
+export const InteractiveWindowSetting = {
+ interactiveWindowAlwaysScrollOnNewCell: 'interactiveWindow.alwaysScrollOnNewCell',
+ interactiveWindowHotExit: 'interactiveWindow.hotExit'
+};
diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts
index 37d2b4fde50..81992e7547d 100644
--- a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts
+++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts
@@ -22,7 +22,7 @@ import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
import { EditorPaneSelectionChangeReason, IEditorMemento, IEditorOpenContext, IEditorPaneSelectionChangeEvent } from 'vs/workbench/common/editor';
import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions';
import { InteractiveEditorInput } from 'vs/workbench/contrib/interactive/browser/interactiveEditorInput';
-import { ICellViewModel, INotebookEditorViewState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
+import { ICellViewModel, INotebookEditorOptions, INotebookEditorViewState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions';
import { IBorrowValue, INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService';
import { cellEditorBackground, NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidget';
@@ -33,9 +33,8 @@ import { PLAINTEXT_LANGUAGE_ID } from 'vs/editor/common/languages/modesRegistry'
import { ILanguageService } from 'vs/editor/common/languages/language';
import { IMenuService, MenuId } from 'vs/platform/actions/common/actions';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
-import { INTERACTIVE_INPUT_CURSOR_BOUNDARY } from 'vs/workbench/contrib/interactive/browser/interactiveCommon';
+import { InteractiveWindowSetting, INTERACTIVE_INPUT_CURSOR_BOUNDARY } from 'vs/workbench/contrib/interactive/browser/interactiveCommon';
import { ComplexNotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookEditorModel';
-import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { NotebookOptions } from 'vs/workbench/contrib/notebook/common/notebookOptions';
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
@@ -57,6 +56,7 @@ import { ITextEditorOptions, TextEditorSelectionSource } from 'vs/platform/edito
import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
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';
const DECORATION_KEY = 'interactiveInputDecoration';
const INTERACTIVE_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'InteractiveEditorViewState';
@@ -97,6 +97,7 @@ export class InteractiveEditor extends EditorPane {
#contextMenuService: IContextMenuService;
#editorGroupService: IEditorGroupsService;
#notebookExecutionStateService: INotebookExecutionStateService;
+ #extensionService: IExtensionService;
#widgetDisposableStore: DisposableStore = this._register(new DisposableStore());
#dimension?: DOM.Dimension;
#notebookOptions: NotebookOptions;
@@ -124,7 +125,8 @@ export class InteractiveEditor extends EditorPane {
@IContextMenuService contextMenuService: IContextMenuService,
@IEditorGroupsService editorGroupService: IEditorGroupsService,
@ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService,
- @INotebookExecutionStateService notebookExecutionStateService: INotebookExecutionStateService
+ @INotebookExecutionStateService notebookExecutionStateService: INotebookExecutionStateService,
+ @IExtensionService extensionService: IExtensionService,
) {
super(
InteractiveEditor.ID,
@@ -142,6 +144,7 @@ export class InteractiveEditor extends EditorPane {
this.#contextMenuService = contextMenuService;
this.#editorGroupService = editorGroupService;
this.#notebookExecutionStateService = notebookExecutionStateService;
+ this.#extensionService = extensionService;
this.#notebookOptions = new NotebookOptions(configurationService, notebookExecutionStateService, { cellToolbarInteraction: 'hover', globalToolbar: true, defaultCellCollapseConfig: { codeCell: { inputCollapsed: true } } });
this.#editorMemento = this.getEditorMemento<InteractiveEditorViewState>(editorGroupService, textResourceConfigurationService, INTERACTIVE_EDITOR_VIEW_STATE_PREFERENCE_KEY);
@@ -397,6 +400,7 @@ export class InteractiveEditor extends EditorPane {
this.#notebookWidget.value?.setParentContextKeyService(this.#contextKeyService);
const viewState = options?.viewState ?? this.#loadNotebookEditorViewState(input);
+ await this.#extensionService.whenInstalledExtensionsRegistered();
await this.#notebookWidget.value!.setModel(model.notebook, viewState?.notebook);
model.notebook.setCellCollapseDefault(this.#notebookOptions.getCellCollapseDefault());
this.#notebookWidget.value!.setOptions({
@@ -501,6 +505,11 @@ export class InteractiveEditor extends EditorPane {
this.#syncWithKernel();
}
+ override setOptions(options: INotebookEditorOptions | undefined): void {
+ this.#notebookWidget.value?.setOptions(options);
+ super.setOptions(options);
+ }
+
#toEditorPaneSelectionChangeReason(e: ICursorPositionChangedEvent): EditorPaneSelectionChangeReason {
switch (e.source) {
case TextEditorSelectionSource.PROGRAMMATIC: return EditorPaneSelectionChangeReason.PROGRAMMATIC;
@@ -523,7 +532,7 @@ export class InteractiveEditor extends EditorPane {
const index = this.#notebookWidget.value!.getCellIndex(cvm);
if (index === this.#notebookWidget.value!.getLength() - 1) {
// If we're already at the bottom or auto scroll is enabled, scroll to the bottom
- if (this.configurationService.getValue<boolean>(NotebookSetting.interactiveWindowAlwaysScrollOnNewCell) || this.#cellAtBottom(cvm)) {
+ if (this.configurationService.getValue<boolean>(InteractiveWindowSetting.interactiveWindowAlwaysScrollOnNewCell) || this.#cellAtBottom(cvm)) {
this.#notebookWidget.value!.scrollToBottom();
}
}
diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts
index 17d8eaa03cf..71b125ec799 100644
--- a/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts
+++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditorInput.ts
@@ -3,19 +3,21 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
+import { VSBuffer } from 'vs/base/common/buffer';
import { Event } from 'vs/base/common/event';
import { IReference } from 'vs/base/common/lifecycle';
import * as paths from 'vs/base/common/path';
import { isEqual } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
-import { IModelService } from 'vs/editor/common/services/model';
import { IResolvedTextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IUntypedEditorInput } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { IInteractiveDocumentService } from 'vs/workbench/contrib/interactive/browser/interactiveDocumentService';
import { IInteractiveHistoryService } from 'vs/workbench/contrib/interactive/browser/interactiveHistoryService';
-import { IResolvedNotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon';
+import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
+import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
+import { CellKind, ICellDto2, IOutputDto, IResolvedNotebookEditorModel, NotebookCellCollapseState, NotebookCellInternalMetadata, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { ICompositeNotebookEditorInput, NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput';
export class InteractiveEditorInput extends EditorInput implements ICompositeNotebookEditorInput {
@@ -44,8 +46,10 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot
return [this._notebookEditorInput];
}
- override get resource() {
- return this.primary.resource;
+ private _resource: URI;
+
+ override get resource(): URI {
+ return this._resource;
}
private _inputResource: URI;
@@ -71,7 +75,6 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot
inputResource: URI,
title: string | undefined,
@IInstantiationService instantiationService: IInstantiationService,
- @IModelService modelService: IModelService,
@ITextModelService textModelService: ITextModelService,
@IInteractiveDocumentService interactiveDocumentService: IInteractiveDocumentService,
@@ -82,6 +85,7 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot
this._notebookEditorInput = input;
this._register(this._notebookEditorInput);
this._initTitle = title;
+ this._resource = resource;
this._inputResource = inputResource;
this._inputResolver = null;
this._editorModelReference = null;
@@ -130,7 +134,14 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot
return this._inputResolver;
}
- this._inputResolver = this._resolveEditorModel();
+ this._inputResolver = this._resolveEditorModel().then(editorModel => {
+ if (this._data) {
+ editorModel?.notebook.reset(this._data.notebookData.cells.map((cell: ISerializedCell) => deserializeCell(cell)), this._data.notebookData.metadata, this._data.notebookData.transientOptions);
+ }
+
+ return editorModel;
+ });
+
return this._inputResolver;
}
@@ -142,6 +153,10 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot
this._interactiveDocumentService.willCreateInteractiveDocument(this.resource!, this.inputResource, language);
this._inputModelRef = await this._textModelService.createModelReference(this.inputResource);
+ if (this._data && this._data.inputData) {
+ this._inputModelRef.object.textEditorModel.setValue(this._data.inputData.value);
+ }
+
return this._inputModelRef.object.textEditorModel;
}
@@ -166,6 +181,37 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot
return basename.substr(0, basename.length - paths.extname(p).length);
}
+ getSerialization(): { notebookData: any | undefined; inputData: any | undefined } {
+ return {
+ notebookData: this._serializeNotebook(this._editorModelReference?.notebook),
+ inputData: this._inputModelRef ? {
+ value: this._inputModelRef.object.textEditorModel.getValue(),
+ language: this._inputModelRef.object.textEditorModel.getLanguageId()
+ } : undefined
+ };
+ }
+
+ private _data: { notebookData: any | undefined; inputData: any | undefined } | undefined;
+
+ async restoreSerialization(data: { notebookData: any | undefined; inputData: any | undefined } | undefined) {
+ this._data = data;
+ }
+
+ private _serializeNotebook(notebook?: NotebookTextModel) {
+ if (!notebook) {
+ return undefined;
+ }
+
+ const cells = notebook.cells.map(cell => serializeCell(cell));
+
+ return {
+ cells: cells,
+ metadata: notebook.metadata,
+ transientOptions: notebook.transientOptions
+ };
+ }
+
+
override dispose() {
// we support closing the interactive window without prompt, so the editor model should not be dirty
this._editorModelReference?.revert({ soft: true });
@@ -183,3 +229,74 @@ export class InteractiveEditorInput extends EditorInput implements ICompositeNot
return this._historyService;
}
}
+
+/**
+ * Serialization of interactive notebook.
+ * This is not placed in notebook land as regular notebooks are handled by file service directly.
+ */
+
+interface ISerializedOutputItem {
+ readonly mime: string;
+ readonly data: number[];
+}
+
+interface ISerializedCellOutput {
+ outputs: ISerializedOutputItem[];
+ metadata?: Record<string, any>;
+ outputId: string;
+}
+
+export interface ISerializedCell {
+ source: string;
+ language: string;
+ mime: string | undefined;
+ cellKind: CellKind;
+ outputs: ISerializedCellOutput[];
+ metadata?: NotebookCellMetadata;
+ internalMetadata?: NotebookCellInternalMetadata;
+ collapseState?: NotebookCellCollapseState;
+}
+
+function serializeCell(cell: NotebookCellTextModel): ISerializedCell {
+ return {
+ cellKind: cell.cellKind,
+ language: cell.language,
+ metadata: cell.metadata,
+ mime: cell.mime,
+ outputs: cell.outputs.map(output => serializeCellOutput(output)),
+ source: cell.getValue()
+ };
+}
+
+function deserializeCell(cell: ISerializedCell): ICellDto2 {
+ return {
+ cellKind: cell.cellKind,
+ source: cell.source,
+ language: cell.language,
+ metadata: cell.metadata,
+ mime: cell.mime,
+ outputs: cell.outputs.map((output) => deserializeCellOutput(output))
+ };
+}
+
+function serializeCellOutput(output: IOutputDto): ISerializedCellOutput {
+ return {
+ outputId: output.outputId,
+ outputs: output.outputs.map(ot => ({
+ mime: ot.mime,
+ data: ot.data.buffer ? Array.from(ot.data.buffer) : []
+ })),
+ metadata: output.metadata
+ };
+}
+
+function deserializeCellOutput(output: ISerializedCellOutput): IOutputDto {
+ return {
+ outputId: output.outputId,
+ outputs: output.outputs.map(ot => ({
+ mime: ot.mime,
+ data: VSBuffer.fromByteArray(ot.data)
+ })),
+ metadata: output.metadata
+ };
+}
diff --git a/src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts b/src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts
index a931e2bb2f2..ef593cb3de1 100644
--- a/src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts
+++ b/src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts
@@ -123,7 +123,7 @@ registerAction2(class extends Action2 {
constructor() {
super({
id: detectLanguageCommandId,
- title: localize('detectlang', 'Detect Language from Content'),
+ title: { value: localize('detectlang', 'Detect Language from Content'), original: 'Detect Language from Content' },
f1: true,
precondition: ContextKeyExpr.and(NOTEBOOK_EDITOR_EDITABLE.toNegated(), EditorContextKeys.editorTextFocus),
keybinding: { primary: KeyCode.KeyD | KeyMod.Alt | KeyMod.Shift, weight: KeybindingWeight.WorkbenchContrib }
diff --git a/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts b/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts
index 661059e89b9..63b120d3b3c 100644
--- a/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts
+++ b/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts
@@ -398,8 +398,14 @@ registerAction2(class extends Action2 {
constructor() {
super({
id: 'editor.inlayHints.Reset',
- title: localize('reset', 'Reset Language Status Interaction Counter'),
- category: localize('cat', 'View'),
+ title: {
+ value: localize('reset', 'Reset Language Status Interaction Counter'),
+ original: 'Reset Language Status Interaction Counter'
+ },
+ category: {
+ value: localize('cat', 'View'),
+ original: 'View'
+ },
f1: true
});
}
diff --git a/src/vs/workbench/contrib/localization/browser/localeService.ts b/src/vs/workbench/contrib/localization/browser/localeService.ts
index c59d84821b2..dc8138589bf 100644
--- a/src/vs/workbench/contrib/localization/browser/localeService.ts
+++ b/src/vs/workbench/contrib/localization/browser/localeService.ts
@@ -3,31 +3,62 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { language } from 'vs/base/common/platform';
+import { localize } from 'vs/nls';
+import { Language } from 'vs/base/common/platform';
+import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { ILanguagePackItem } from 'vs/platform/languagePacks/common/languagePacks';
import { ILocaleService } from 'vs/workbench/contrib/localization/common/locale';
+import { IHostService } from 'vs/workbench/services/host/browser/host';
+import { IProductService } from 'vs/platform/product/common/productService';
export class WebLocaleService implements ILocaleService {
declare readonly _serviceBrand: undefined;
- async setLocale(languagePackItem: ILanguagePackItem): Promise<boolean> {
+ constructor(
+ @IDialogService private readonly dialogService: IDialogService,
+ @IHostService private readonly hostService: IHostService,
+ @IProductService private readonly productService: IProductService
+ ) { }
+
+ async setLocale(languagePackItem: ILanguagePackItem): Promise<void> {
const locale = languagePackItem.id;
- if (locale === language || (!locale && language === navigator.language)) {
- return false;
+ if (locale === Language.value() || (!locale && Language.value() === navigator.language)) {
+ return;
}
if (locale) {
window.localStorage.setItem('vscode.nls.locale', locale);
} else {
window.localStorage.removeItem('vscode.nls.locale');
}
- return true;
- }
- async clearLocalePreference(): Promise<boolean> {
- if (language === navigator.language) {
- return false;
+ const restartDialog = await this.dialogService.confirm({
+ type: 'info',
+ message: localize('relaunchDisplayLanguageMessage', "To change the display language, {0} needs to reload", this.productService.nameLong),
+ detail: localize('relaunchDisplayLanguageDetail', "Press the reload button to refresh the page and set the display language to {0}.", languagePackItem.label),
+ primaryButton: localize({ key: 'reload', comment: ['&& denotes a mnemonic character'] }, "&&Reload"),
+ });
+
+ if (restartDialog.confirmed) {
+ this.hostService.restart();
}
+ }
+
+ async clearLocalePreference(): Promise<void> {
window.localStorage.removeItem('vscode.nls.locale');
- return true;
+
+ if (Language.value() === navigator.language) {
+ return;
+ }
+
+ const restartDialog = await this.dialogService.confirm({
+ type: 'info',
+ message: localize('clearDisplayLanguageMessage', "To change the display language, {0} needs to reload", this.productService.nameLong),
+ detail: localize('clearDisplayLanguageDetail', "Press the reload button to refresh the page and use your browser's language."),
+ primaryButton: localize({ key: 'reload', comment: ['&& denotes a mnemonic character'] }, "&&Reload"),
+ });
+
+ if (restartDialog.confirmed) {
+ this.hostService.restart();
+ }
}
}
diff --git a/src/vs/workbench/contrib/localization/browser/localizationsActions.ts b/src/vs/workbench/contrib/localization/browser/localizationsActions.ts
index 5bdb7500724..2be6435942d 100644
--- a/src/vs/workbench/contrib/localization/browser/localizationsActions.ts
+++ b/src/vs/workbench/contrib/localization/browser/localizationsActions.ts
@@ -5,9 +5,6 @@
import { localize } from 'vs/nls';
import { IQuickInputService, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
-import { IHostService } from 'vs/workbench/services/host/browser/host';
-import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
-import { IProductService } from 'vs/platform/product/common/productService';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { Action2, MenuId } from 'vs/platform/actions/common/actions';
@@ -15,8 +12,6 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation
import { ILanguagePackItem, ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks';
import { ILocaleService } from 'vs/workbench/contrib/localization/common/locale';
-const restart = localize('restart', "&&Restart");
-
export class ConfigureDisplayLanguageAction extends Action2 {
public static readonly ID = 'workbench.action.configureLocale';
public static readonly LABEL = localize('configureLocale', "Configure Display Language");
@@ -34,9 +29,6 @@ export class ConfigureDisplayLanguageAction extends Action2 {
public async run(accessor: ServicesAccessor): Promise<void> {
const languagePackService: ILanguagePackService = accessor.get(ILanguagePackService);
const quickInputService: IQuickInputService = accessor.get(IQuickInputService);
- const hostService: IHostService = accessor.get(IHostService);
- const dialogService: IDialogService = accessor.get(IDialogService);
- const productService: IProductService = accessor.get(IProductService);
const localeService: ILocaleService = accessor.get(ILocaleService);
const installedLanguages = await languagePackService.getInstalledLanguages();
@@ -72,19 +64,7 @@ export class ConfigureDisplayLanguageAction extends Action2 {
disposables.add(qp.onDidAccept(async () => {
const selectedLanguage = qp.activeItems[0];
qp.hide();
-
- if (await localeService.setLocale(selectedLanguage)) {
- const restartDialog = await dialogService.confirm({
- type: 'info',
- message: localize('relaunchDisplayLanguageMessage', "A restart is required for the change in display language to take effect."),
- detail: localize('relaunchDisplayLanguageDetail', "Press the restart button to restart {0} and change the display language.", productService.nameLong),
- primaryButton: restart
- });
-
- if (restartDialog.confirmed) {
- hostService.restart();
- }
- }
+ await localeService.setLocale(selectedLanguage);
}));
qp.show();
@@ -108,21 +88,6 @@ export class ClearDisplayLanguageAction extends Action2 {
public async run(accessor: ServicesAccessor): Promise<void> {
const localeService: ILocaleService = accessor.get(ILocaleService);
- const dialogService: IDialogService = accessor.get(IDialogService);
- const productService: IProductService = accessor.get(IProductService);
- const hostService: IHostService = accessor.get(IHostService);
-
- if (await localeService.clearLocalePreference()) {
- const restartDialog = await dialogService.confirm({
- type: 'info',
- message: localize('relaunchAfterClearDisplayLanguageMessage', "A restart is required for the change in display language to take effect."),
- detail: localize('relaunchAfterClearDisplayLanguageDetail', "Press the restart button to restart {0} and change the display language.", productService.nameLong),
- primaryButton: restart
- });
-
- if (restartDialog.confirmed) {
- hostService.restart();
- }
- }
+ await localeService.clearLocalePreference();
}
}
diff --git a/src/vs/workbench/contrib/localization/common/locale.ts b/src/vs/workbench/contrib/localization/common/locale.ts
index f447d40bc41..f2e8417c443 100644
--- a/src/vs/workbench/contrib/localization/common/locale.ts
+++ b/src/vs/workbench/contrib/localization/common/locale.ts
@@ -10,6 +10,6 @@ export const ILocaleService = createDecorator<ILocaleService>('localizationServi
export interface ILocaleService {
readonly _serviceBrand: undefined;
- setLocale(languagePackItem: ILanguagePackItem): Promise<boolean>;
- clearLocalePreference(): Promise<boolean>;
+ setLocale(languagePackItem: ILanguagePackItem): Promise<void>;
+ clearLocalePreference(): Promise<void>;
}
diff --git a/src/vs/workbench/contrib/localization/electron-sandbox/localeService.ts b/src/vs/workbench/contrib/localization/electron-sandbox/localeService.ts
index 00a6ec7036f..4c0da0bc253 100644
--- a/src/vs/workbench/contrib/localization/electron-sandbox/localeService.ts
+++ b/src/vs/workbench/contrib/localization/electron-sandbox/localeService.ts
@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { language } from 'vs/base/common/platform';
+import { Language } from 'vs/base/common/platform';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing';
@@ -19,6 +19,9 @@ import { toAction } from 'vs/base/common/actions';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { stripComments } from 'vs/base/common/stripComments';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
+import { IHostService } from 'vs/workbench/services/host/browser/host';
+import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
+import { IProductService } from 'vs/platform/product/common/productService';
export class NativeLocaleService implements ILocaleService {
_serviceBrand: undefined;
@@ -32,7 +35,10 @@ export class NativeLocaleService implements ILocaleService {
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@IProgressService private readonly progressService: IProgressService,
@ITextFileService private readonly textFileService: ITextFileService,
- @IEditorService private readonly editorService: IEditorService
+ @IEditorService private readonly editorService: IEditorService,
+ @IDialogService private readonly dialogService: IDialogService,
+ @IHostService private readonly hostService: IHostService,
+ @IProductService private readonly productService: IProductService
) { }
private async validateLocaleFile(): Promise<boolean> {
@@ -69,10 +75,10 @@ export class NativeLocaleService implements ILocaleService {
return true;
}
- async setLocale(languagePackItem: ILanguagePackItem): Promise<boolean> {
+ async setLocale(languagePackItem: ILanguagePackItem): Promise<void> {
const locale = languagePackItem.id;
- if (locale === language || (!locale && language === 'en')) {
- return false;
+ if (locale === Language.value() || (!locale && Language.isDefaultVariant())) {
+ return;
}
const installedLanguages = await this.languagePackService.getInstalledLanguages();
try {
@@ -87,7 +93,7 @@ export class NativeLocaleService implements ILocaleService {
// as of now, there are no 3rd party language packs available on the Marketplace.
const viewlet = await this.paneCompositePartService.openPaneComposite(EXTENSIONS_VIEWLET_ID, ViewContainerLocation.Sidebar);
(viewlet?.getViewPaneContainer() as IExtensionsViewPaneContainer).search(`@id:${languagePackItem.extensionId}`);
- return false;
+ return;
}
await this.progressService.withProgress(
@@ -102,22 +108,40 @@ export class NativeLocaleService implements ILocaleService {
);
}
- return await this.writeLocaleValue(locale);
+ if (await this.writeLocaleValue(locale)) {
+ await this.showRestartDialog(languagePackItem.label);
+ }
} catch (err) {
this.notificationService.error(err);
- return false;
}
}
- async clearLocalePreference(): Promise<boolean> {
- if (language === 'en') {
- return false;
- }
+ async clearLocalePreference(): Promise<void> {
try {
- return await this.writeLocaleValue(undefined);
+ await this.writeLocaleValue(undefined);
+ if (!Language.isDefaultVariant()) {
+ await this.showRestartDialog('English');
+ }
} catch (err) {
this.notificationService.error(err);
- return false;
+ }
+ }
+
+ private async showRestartDialog(languageName: string) {
+ const restartDialog = await this.dialogService.confirm({
+ type: 'info',
+ message: localize('restartDisplayLanguageMessage', "To change the display language, {0} needs to restart", this.productService.nameLong),
+ detail: localize(
+ 'restartDisplayLanguageDetail',
+ "Press the restart button to restart {0} and set the display language to {1}.",
+ this.productService.nameLong,
+ languageName
+ ),
+ primaryButton: localize({ key: 'restart', comment: ['&& denotes a mnemonic character'] }, "&&Restart"),
+ });
+
+ if (restartDialog.confirmed) {
+ this.hostService.restart();
}
}
}
diff --git a/src/vs/workbench/contrib/logs/common/logConstants.ts b/src/vs/workbench/contrib/logs/common/logConstants.ts
index 9ba3e7aa0ee..4342dd4a740 100644
--- a/src/vs/workbench/contrib/logs/common/logConstants.ts
+++ b/src/vs/workbench/contrib/logs/common/logConstants.ts
@@ -9,5 +9,6 @@ export const rendererLogChannelId = 'rendererLog';
export const extHostLogChannelId = 'extHostLog';
export const telemetryLogChannelId = 'telemetryLog';
export const userDataSyncLogChannelId = 'userDataSyncLog';
+export const editSessionsLogChannelId = 'editSessionsSyncLog';
export const showWindowLogActionId = 'workbench.action.showWindowLog';
diff --git a/src/vs/workbench/contrib/logs/common/logs.contribution.ts b/src/vs/workbench/contrib/logs/common/logs.contribution.ts
index 5e533149305..f45fe793b09 100644
--- a/src/vs/workbench/contrib/logs/common/logs.contribution.ts
+++ b/src/vs/workbench/contrib/logs/common/logs.contribution.ts
@@ -38,6 +38,7 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution {
private registerCommonContributions(): void {
this.registerLogChannel(Constants.userDataSyncLogChannelId, nls.localize('userDataSyncLog', "Settings Sync"), this.environmentService.userDataSyncLogResource);
+ this.registerLogChannel(Constants.editSessionsLogChannelId, nls.localize('editSessionsLog', "Edit Sessions"), this.environmentService.editSessionsLogResource);
this.registerLogChannel(Constants.rendererLogChannelId, nls.localize('rendererLog', "Window"), this.environmentService.logFile);
const registerTelemetryChannel = () => {
diff --git a/src/vs/workbench/contrib/markers/browser/markersTable.ts b/src/vs/workbench/contrib/markers/browser/markersTable.ts
index a4938d08d58..4d39773daed 100644
--- a/src/vs/workbench/contrib/markers/browser/markersTable.ts
+++ b/src/vs/workbench/contrib/markers/browser/markersTable.ts
@@ -135,15 +135,17 @@ class MarkerCodeColumnRenderer implements ITableRenderer<MarkerTableItem, IMarke
}
renderElement(element: MarkerTableItem, index: number, templateData: IMarkerCodeColumnTemplateData, height: number | undefined): void {
- if (element.marker.source && element.marker.code) {
- templateData.codeColumn.classList.toggle('code-link', typeof element.marker.code !== 'string');
- DOM.show(templateData.codeLabel.element);
+ templateData.codeColumn.classList.remove('code-label');
+ templateData.codeColumn.classList.remove('code-link');
+ if (element.marker.source && element.marker.code) {
if (typeof element.marker.code === 'string') {
+ templateData.codeColumn.classList.add('code-label');
templateData.codeColumn.title = `${element.marker.source} (${element.marker.code})`;
templateData.sourceLabel.set(element.marker.source, element.sourceMatches);
templateData.codeLabel.set(element.marker.code, element.codeMatches);
} else {
+ templateData.codeColumn.classList.add('code-link');
templateData.codeColumn.title = `${element.marker.source} (${element.marker.code.value})`;
templateData.sourceLabel.set(element.marker.source, element.sourceMatches);
@@ -159,7 +161,6 @@ class MarkerCodeColumnRenderer implements ITableRenderer<MarkerTableItem, IMarke
} else {
templateData.codeColumn.title = '';
templateData.sourceLabel.set('-');
- DOM.hide(templateData.codeLabel.element);
}
}
diff --git a/src/vs/workbench/contrib/markers/browser/media/markers.css b/src/vs/workbench/contrib/markers/browser/media/markers.css
index 4ce704bb880..4a47972350e 100644
--- a/src/vs/workbench/contrib/markers/browser/media/markers.css
+++ b/src/vs/workbench/contrib/markers/browser/media/markers.css
@@ -279,17 +279,18 @@
content: ')';
}
-.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .code.code-link > .code-label {
+.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .code > .code-label,
+.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .code > .monaco-link {
display: none;
}
-.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .code.code-link > .monaco-link {
+.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .code.code-label > .code-label {
display: inline;
- text-decoration: underline;
}
-.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .code > .monaco-link {
- display: none;
+.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .code.code-link > .monaco-link {
+ display: inline;
+ text-decoration: underline;
}
.markers-panel .markers-table-container .monaco-table .monaco-list-row .monaco-table-tr > .monaco-table-td > .file > .file-position {
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts
index 4fa542c981d..892febf378e 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts
@@ -6,9 +6,11 @@
import { Codicon } from 'vs/base/common/codicons';
import { URI, UriComponents } from 'vs/base/common/uri';
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 { 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';
import { MergeEditorInput, MergeEditorInputData } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput';
import { MergeEditor } from 'vs/workbench/contrib/mergeEditor/browser/view/mergeEditor';
@@ -19,7 +21,7 @@ export class OpenMergeEditor extends Action2 {
constructor() {
super({
id: '_open.mergeEditor',
- title: localize('title', "Open Merge Editor"),
+ title: { value: localize('title', "Open Merge Editor"), original: 'Open Merge Editor' },
});
}
run(accessor: ServicesAccessor, ...args: unknown[]): void {
@@ -111,14 +113,19 @@ export class SetMixedLayout extends Action2 {
constructor() {
super({
id: 'merge.mixedLayout',
- title: localize('layout.mixed', "Mixed Layout"),
+ title: {
+ value: localize('layout.mixed', 'Mixed Layout'),
+ original: 'Mixed Layout',
+ },
toggled: ctxMergeEditorLayout.isEqualTo('mixed'),
- menu: [{
- id: MenuId.EditorTitle,
- when: ctxIsMergeEditor,
- group: '1_merge',
- order: 9,
- }],
+ menu: [
+ {
+ id: MenuId.EditorTitle,
+ when: ctxIsMergeEditor,
+ group: '1_merge',
+ order: 9,
+ },
+ ],
precondition: ctxIsMergeEditor,
});
}
@@ -135,7 +142,7 @@ export class SetColumnLayout extends Action2 {
constructor() {
super({
id: 'merge.columnLayout',
- title: localize('layout.column', "Column Layout"),
+ title: { value: localize('layout.column', "Column Layout"), original: 'Column Layout' },
toggled: ctxMergeEditorLayout.isEqualTo('columns'),
menu: [{
id: MenuId.EditorTitle,
@@ -155,18 +162,28 @@ export class SetColumnLayout extends Action2 {
}
}
+const mergeEditorCategory: ILocalizedString = {
+ value: localize('mergeEditor', 'Merge Editor'),
+ original: 'Merge Editor',
+};
+
export class GoToNextConflict extends Action2 {
constructor() {
super({
id: 'merge.goToNextConflict',
- category: localize('mergeEditor', "Merge Editor"),
- title: localize('merge.goToNextConflict', "Go to Next Conflict"),
+ category: mergeEditorCategory,
+ title: {
+ value: localize('merge.goToNextConflict', 'Go to Next Conflict'),
+ original: 'Go to Next Conflict',
+ },
icon: Codicon.arrowDown,
- menu: [{
- id: MenuId.EditorTitle,
- when: ctxIsMergeEditor,
- group: 'navigation',
- }],
+ menu: [
+ {
+ id: MenuId.EditorTitle,
+ when: ctxIsMergeEditor,
+ group: 'navigation',
+ },
+ ],
f1: true,
precondition: ctxIsMergeEditor,
});
@@ -175,7 +192,7 @@ export class GoToNextConflict extends Action2 {
run(accessor: ServicesAccessor): void {
const { activeEditorPane } = accessor.get(IEditorService);
if (activeEditorPane instanceof MergeEditor) {
- activeEditorPane.viewModel.get()?.goToNextConflict();
+ activeEditorPane.viewModel.get()?.goToNextModifiedBaseRange(true);
}
}
}
@@ -184,14 +201,22 @@ export class GoToPreviousConflict extends Action2 {
constructor() {
super({
id: 'merge.goToPreviousConflict',
- category: localize('mergeEditor', "Merge Editor"),
- title: localize('merge.goToPreviousConflict', "Go to Previous Conflict"),
+ category: mergeEditorCategory,
+ title: {
+ value: localize(
+ 'merge.goToPreviousConflict',
+ 'Go to Previous Conflict'
+ ),
+ original: 'Go to Previous Conflict',
+ },
icon: Codicon.arrowUp,
- menu: [{
- id: MenuId.EditorTitle,
- when: ctxIsMergeEditor,
- group: 'navigation',
- }],
+ menu: [
+ {
+ id: MenuId.EditorTitle,
+ when: ctxIsMergeEditor,
+ group: 'navigation',
+ },
+ ],
f1: true,
precondition: ctxIsMergeEditor,
});
@@ -200,7 +225,7 @@ export class GoToPreviousConflict extends Action2 {
run(accessor: ServicesAccessor): void {
const { activeEditorPane } = accessor.get(IEditorService);
if (activeEditorPane instanceof MergeEditor) {
- activeEditorPane.viewModel.get()?.goToPreviousConflict();
+ activeEditorPane.viewModel.get()?.goToPreviousModifiedBaseRange(true);
}
}
}
@@ -209,8 +234,14 @@ export class ToggleActiveConflictInput1 extends Action2 {
constructor() {
super({
id: 'merge.toggleActiveConflictInput1',
- category: localize('mergeEditor', "Merge Editor"),
- title: localize('merge.toggleCurrentConflictFromLeft', "Toggle Current Conflict from Left"),
+ category: mergeEditorCategory,
+ title: {
+ value: localize(
+ 'merge.toggleCurrentConflictFromLeft',
+ 'Toggle Current Conflict from Left'
+ ),
+ original: 'Toggle Current Conflict from Left',
+ },
f1: true,
precondition: ctxIsMergeEditor,
});
@@ -232,8 +263,14 @@ export class ToggleActiveConflictInput2 extends Action2 {
constructor() {
super({
id: 'merge.toggleActiveConflictInput2',
- category: localize('mergeEditor', "Merge Editor"),
- title: localize('merge.toggleCurrentConflictFromRight', "Toggle Current Conflict from Right"),
+ category: mergeEditorCategory,
+ title: {
+ value: localize(
+ 'merge.toggleCurrentConflictFromRight',
+ 'Toggle Current Conflict from Right'
+ ),
+ original: 'Toggle Current Conflict from Right',
+ },
f1: true,
precondition: ctxIsMergeEditor,
});
@@ -255,8 +292,14 @@ export class CompareInput1WithBaseCommand extends Action2 {
constructor() {
super({
id: 'mergeEditor.compareInput1WithBase',
- category: localize('mergeEditor', "Merge Editor"),
- title: localize('mergeEditor.compareInput1WithBase', "Compare Input 1 With Base"),
+ category: mergeEditorCategory,
+ title: {
+ value: localize(
+ 'mergeEditor.compareInput1WithBase',
+ 'Compare Input 1 With Base'
+ ),
+ original: 'Compare Input 1 With Base',
+ },
f1: true,
precondition: ctxIsMergeEditor,
});
@@ -272,8 +315,14 @@ export class CompareInput2WithBaseCommand extends Action2 {
constructor() {
super({
id: 'mergeEditor.compareInput2WithBase',
- category: localize('mergeEditor', "Merge Editor"),
- title: localize('mergeEditor.compareInput2WithBase', "Compare Input 2 With Base"),
+ category: mergeEditorCategory,
+ title: {
+ value: localize(
+ 'mergeEditor.compareInput2WithBase',
+ 'Compare Input 2 With Base'
+ ),
+ original: 'Compare Input 2 With Base',
+ },
f1: true,
precondition: ctxIsMergeEditor,
});
@@ -302,3 +351,30 @@ function mergeEditorCompare(editorService: IEditorService, commandService: IComm
function openDiffEditor(commandService: ICommandService, left: URI, right: URI, label?: string) {
commandService.executeCommand(API_OPEN_DIFF_EDITOR_COMMAND_ID, left, right, label);
}
+
+export class OpenBaseFile extends Action2 {
+ constructor() {
+ super({
+ id: 'merge.openBaseEditor',
+ category: mergeEditorCategory,
+ title: {
+ value: localize('merge.openBaseEditor', 'Open Base File'),
+ original: 'Open Base File',
+ },
+ f1: true,
+ precondition: ctxIsMergeEditor,
+ });
+ }
+
+ run(accessor: ServicesAccessor): void {
+ const openerService = accessor.get(IOpenerService);
+ const { activeEditorPane } = accessor.get(IEditorService);
+ if (activeEditorPane instanceof MergeEditor) {
+ const vm = activeEditorPane.viewModel.get();
+ if (!vm) {
+ return;
+ }
+ openerService.open(vm.model.base.uri);
+ }
+ }
+}
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts b/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts
index 83a458d15f5..6728dc93b1a 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts
@@ -29,12 +29,17 @@ interface MergeEditorContents {
}
export class MergeEditorCopyContentsToJSON extends Action2 {
-
constructor() {
super({
id: 'merge.dev.copyContents',
category: 'Merge Editor (Dev)',
- title: localize('merge.dev.copyContents', "Copy Contents of Inputs, Base and Result as JSON"),
+ title: {
+ value: localize(
+ 'merge.dev.copyContents',
+ 'Copy Contents of Inputs, Base and Result as JSON'
+ ),
+ original: 'Copy Contents of Inputs, Base and Result as JSON',
+ },
icon: Codicon.layoutCentered,
f1: true,
precondition: ctxIsMergeEditor,
@@ -75,12 +80,17 @@ export class MergeEditorCopyContentsToJSON extends Action2 {
}
export class MergeEditorOpenContents extends Action2 {
-
constructor() {
super({
id: 'merge.dev.openContents',
category: 'Merge Editor (Dev)',
- title: localize('merge.dev.openContents', "Open Contents of Inputs, Base and Result from JSON"),
+ title: {
+ value: localize(
+ 'merge.dev.openContents',
+ 'Open Contents of Inputs, Base and Result from JSON'
+ ),
+ original: 'Open Contents of Inputs, Base and Result from JSON',
+ },
icon: Codicon.layoutCentered,
f1: true,
});
@@ -98,11 +108,14 @@ export class MergeEditorOpenContents extends Action2 {
prompt: localize('mergeEditor.enterJSON', 'Enter JSON'),
value: await clipboardService.readText(),
});
- if (!result) {
+ if (result === undefined) {
return;
}
- const content: MergeEditorContents = JSON.parse(result);
+ const content: MergeEditorContents =
+ result !== ''
+ ? JSON.parse(result)
+ : { base: '', input1: '', input2: '', result: '', languageId: 'plaintext' };
const scheme = 'merge-editor-dev';
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts
index 136249c41fa..09206f43520 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts
@@ -8,11 +8,13 @@ import { registerAction2 } from 'vs/platform/actions/common/actions';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
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, OpenMergeEditor, ToggleActiveConflictInput1, ToggleActiveConflictInput2, SetColumnLayout, SetMixedLayout } from 'vs/workbench/contrib/mergeEditor/browser/commands/commands';
+import { CompareInput1WithBaseCommand, CompareInput2WithBaseCommand, GoToNextConflict, GoToPreviousConflict, OpenBaseFile, OpenMergeEditor, 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 } from 'vs/workbench/contrib/mergeEditor/browser/view/mergeEditor';
+import { MergeEditor, MergeEditorOpenHandlerContribution } from 'vs/workbench/contrib/mergeEditor/browser/view/mergeEditor';
+import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { MergeEditorSerializer } from './mergeEditorSerializer';
Registry.as<IEditorPaneRegistry>(EditorExtensions.EditorPane).registerEditorPane(
@@ -34,6 +36,7 @@ Registry.as<IEditorFactoryRegistry>(EditorExtensions.EditorFactory).registerEdit
registerAction2(SetMixedLayout);
registerAction2(SetColumnLayout);
registerAction2(OpenMergeEditor);
+registerAction2(OpenBaseFile);
registerAction2(MergeEditorCopyContentsToJSON);
registerAction2(MergeEditorOpenContents);
@@ -46,3 +49,8 @@ registerAction2(ToggleActiveConflictInput2);
registerAction2(CompareInput1WithBaseCommand);
registerAction2(CompareInput2WithBaseCommand);
+
+
+Registry
+ .as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
+ .registerWorkbenchContribution(MergeEditorOpenHandlerContribution, LifecyclePhase.Restored);
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts
index 87aa4ac9b0c..4f29941dc13 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts
@@ -14,14 +14,13 @@ 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 { EditorInput } from 'vs/workbench/common/editor/editorInput';
+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';
-import { autorun } from 'vs/workbench/contrib/audioCues/browser/observable';
import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
-import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService';
import { ILanguageSupport, ITextFileEditorModel, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
+import { autorun } from 'vs/base/common/observable';
export class MergeEditorInputData {
constructor(
@@ -38,7 +37,8 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
private _model?: MergeEditorModel;
private _outTextModel?: ITextFileEditorModel;
- private _ignoreUnhandledConflictsForDirtyState?: true;
+
+ override closeHandler: MergeEditorCloseHandler | undefined;
constructor(
public readonly base: URI,
@@ -47,8 +47,6 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
public readonly result: URI,
@IInstantiationService private readonly _instaService: IInstantiationService,
@ITextModelService private readonly _textModelService: ITextModelService,
- @IDialogService private readonly _dialogService: IDialogService,
- @IFilesConfigurationService private readonly _filesConfigurationService: IFilesConfigurationService,
@IEditorService editorService: IEditorService,
@ITextFileService textFileService: ITextFileService,
@ILabelService labelService: ILabelService,
@@ -112,8 +110,18 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
this.input2.description,
result.object.textEditorModel,
this._instaService.createInstance(EditorWorkerServiceDiffComputer),
+ {
+ resetUnknownOnInitialization: true
+ },
);
+ // set/unset the closeHandler whenever unhandled conflicts are detected
+ const closeHandler = this._instaService.createInstance(MergeEditorCloseHandler, this._model);
+ this._store.add(autorun('closeHandler', reader => {
+ const value = this._model!.hasUnhandledConflicts.read(reader);
+ this.closeHandler = value ? closeHandler : undefined;
+ }));
+
await this._model.onInitialized;
this._store.add(this._model);
@@ -121,14 +129,8 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
this._store.add(input1);
this._store.add(input2);
this._store.add(result);
-
- this._store.add(autorun(reader => {
- this._model?.hasUnhandledConflicts.read(reader);
- this._onDidChangeDirty.fire(undefined);
- }, 'drive::onDidChangeDirty'));
}
- this._ignoreUnhandledConflictsForDirtyState = undefined;
return this._model;
}
@@ -145,64 +147,57 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
// ---- FileEditorInput
override isDirty(): boolean {
- const textModelDirty = Boolean(this._outTextModel?.isDirty());
- if (textModelDirty) {
- // text model dirty -> 3wm is dirty
- return true;
- }
- if (!this._ignoreUnhandledConflictsForDirtyState) {
- // unhandled conflicts -> 3wm is dirty UNLESS we explicitly set this input
- // to ignore unhandled conflicts for the dirty-state. This happens only
- // after confirming to ignore unhandled changes
- return Boolean(this._model && this._model.hasUnhandledConflicts.get());
- }
- return false;
+ return Boolean(this._outTextModel?.isDirty());
}
- override async confirm(editors?: ReadonlyArray<IEditorIdentifier>): Promise<ConfirmResult> {
+ setLanguageId(languageId: string, _setExplicitly?: boolean): void {
+ this._model?.setLanguageId(languageId);
+ }
- const inputs: MergeEditorInput[] = [this];
- if (editors) {
- for (const { editor } of editors) {
- if (editor instanceof MergeEditorInput) {
- inputs.push(editor);
- }
- }
- }
+ // implement get/set languageId
+ // implement get/set encoding
+}
+
+class MergeEditorCloseHandler implements IEditorCloseHandler {
+
+ private _ignoreUnhandledConflicts: boolean = false;
+
+ constructor(
+ private readonly _model: MergeEditorModel,
+ @IDialogService private readonly _dialogService: IDialogService,
+ ) { }
+
+ showConfirm(): boolean {
+ // unhandled conflicts -> 3wm asks to confirm UNLESS we explicitly set this input
+ // to ignore unhandled conflicts. This happens only after confirming to ignore unhandled changes
+ return !this._ignoreUnhandledConflicts && this._model.hasUnhandledConflicts.get();
+ }
- const inputsWithUnhandledConflicts = inputs
+ async confirm(editors?: readonly IEditorIdentifier[] | undefined): Promise<ConfirmResult> {
+
+ const handler: MergeEditorCloseHandler[] = [this];
+ editors?.forEach(candidate => candidate.editor.closeHandler instanceof MergeEditorCloseHandler && handler.push(candidate.editor.closeHandler));
+
+ const inputsWithUnhandledConflicts = handler
.filter(input => input._model && input._model.hasUnhandledConflicts.get());
if (inputsWithUnhandledConflicts.length === 0) {
+ // shouldn't happen
return ConfirmResult.SAVE;
}
- const actions: string[] = [];
+ const actions: string[] = [
+ localize('unhandledConflicts.ignore', "Continue with Conflicts"),
+ localize('unhandledConflicts.discard', "Discard Merge Changes"),
+ localize('unhandledConflicts.cancel', "Cancel"),
+ ];
const options = {
- cancelId: 0,
- detail: inputs.length > 1
- ? localize('unhandledConflicts.detailN', 'Merge conflicts in {0} editors will remain unhandled.', inputs.length)
+ cancelId: 2,
+ detail: handler.length > 1
+ ? localize('unhandledConflicts.detailN', 'Merge conflicts in {0} editors will remain unhandled.', handler.length)
: localize('unhandledConflicts.detail1', 'Merge conflicts in this editor will remain unhandled.')
};
- const isAnyAutoSave = this._filesConfigurationService.getAutoSaveMode() !== AutoSaveMode.OFF;
- if (!isAnyAutoSave) {
- // manual-save: FYI and discard
- actions.push(
- localize('unhandledConflicts.manualSaveIgnore', "Save and Continue with Conflicts"), // 0
- localize('unhandledConflicts.manualSaveNoSave', "Don't Save") // 1
- );
-
- } else {
- // auto-save: only FYI
- actions.push(
- localize('unhandledConflicts.ignore', "Continue with Conflicts"), // 0
- );
- }
-
- actions.push(localize('unhandledConflicts.cancel', "Cancel"));
- options.cancelId = actions.length - 1;
-
const { choice } = await this._dialogService.show(
Severity.Info,
localize('unhandledConflicts.msg', 'Do you want to continue with unhandled conflicts?'), // 1
@@ -217,23 +212,36 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements
// save or revert: in both cases we tell the inputs to ignore unhandled conflicts
// for the dirty state computation.
- for (const input of inputs) {
- input._ignoreUnhandledConflictsForDirtyState = true;
+ for (const input of handler) {
+ input._ignoreUnhandledConflicts = true;
}
if (choice === 0) {
// conflicts: continue with remaining conflicts
return ConfirmResult.SAVE;
- }
- // don't save
- return ConfirmResult.DONT_SAVE;
- }
+ } else if (choice === 1) {
+ // discard: undo all changes and save original (pre-merge) state
+ for (const input of handler) {
+ input._discardMergeChanges();
+ }
+ return ConfirmResult.SAVE;
- setLanguageId(languageId: string, _setExplicitly?: boolean): void {
- this._model?.setLanguageId(languageId);
+ } else {
+ // don't save
+ return ConfirmResult.DONT_SAVE;
+ }
}
- // implement get/set languageId
- // implement get/set encoding
+ private _discardMergeChanges(): void {
+ const chunks: string[] = [];
+ while (true) {
+ const chunk = this._model.resultSnapshot.read();
+ if (chunk === null) {
+ break;
+ }
+ chunks.push(chunk);
+ }
+ this._model.result.setValue(chunks.join());
+ }
}
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts
index d9f61d89baf..255615448d3 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel.ts
@@ -5,11 +5,11 @@
import { CompareResult, equals } from 'vs/base/common/arrays';
import { BugIndicatingError } from 'vs/base/common/errors';
+import { ISettableObservable, derived, waitForState, observableValue, keepAlive, autorunHandleChanges, transaction, IReader, ITransaction, IObservable } from 'vs/base/common/observable';
import { ILanguageService } from 'vs/editor/common/languages/language';
-import { ITextModel } from 'vs/editor/common/model';
+import { ITextModel, ITextSnapshot } from 'vs/editor/common/model';
import { IModelService } from 'vs/editor/common/services/model';
import { EditorModel } from 'vs/workbench/common/editor/editorModel';
-import { autorunHandleChanges, derivedObservable, IObservable, IReader, ITransaction, keepAlive, ObservableValue, transaction, waitForState } from 'vs/workbench/contrib/audioCues/browser/observable';
import { IDiffComputer } from 'vs/workbench/contrib/mergeEditor/browser/model/diffComputer';
import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';
import { DetailedLineRangeMapping, DocumentMapping, LineRangeMapping } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping';
@@ -28,7 +28,7 @@ export class MergeEditorModel extends EditorModel {
private readonly input2TextModelDiffs = this._register(new TextModelDiffs(this.base, this.input2, this.diffComputer));
private readonly resultTextModelDiffs = this._register(new TextModelDiffs(this.base, this.result, this.diffComputer));
- public readonly state = derivedObservable('state', reader => {
+ public readonly state = derived('state', reader => {
const states = [
this.input1TextModelDiffs,
this.input2TextModelDiffs,
@@ -44,11 +44,11 @@ export class MergeEditorModel extends EditorModel {
return MergeEditorModelState.upToDate;
});
- public readonly isUpToDate = derivedObservable('isUpdating', reader => this.state.read(reader) === MergeEditorModelState.upToDate);
+ public readonly isUpToDate = derived('isUpToDate', reader => this.state.read(reader) === MergeEditorModelState.upToDate);
public readonly onInitialized = waitForState(this.state, state => state === MergeEditorModelState.upToDate);
- public readonly modifiedBaseRanges = derivedObservable<ModifiedBaseRange[]>('modifiedBaseRanges', (reader) => {
+ public readonly modifiedBaseRanges = derived<ModifiedBaseRange[]>('modifiedBaseRanges', (reader) => {
const input1Diffs = this.input1TextModelDiffs.diffs.read(reader);
const input2Diffs = this.input2TextModelDiffs.diffs.read(reader);
@@ -60,22 +60,22 @@ export class MergeEditorModel extends EditorModel {
public readonly resultDiffs = this.resultTextModelDiffs.diffs;
private readonly modifiedBaseRangeStateStores =
- derivedObservable('modifiedBaseRangeStateStores', reader => {
+ derived('modifiedBaseRangeStateStores', reader => {
const map = new Map(
- this.modifiedBaseRanges.read(reader).map(s => ([s, new ObservableValue(ModifiedBaseRangeState.default, 'State')]))
+ this.modifiedBaseRanges.read(reader).map(s => ([s, observableValue(`BaseRangeState${s.baseRange}`, ModifiedBaseRangeState.default)]))
);
return map;
});
private readonly modifiedBaseRangeHandlingStateStores =
- derivedObservable('modifiedBaseRangeHandlingStateStores', reader => {
+ derived('modifiedBaseRangeHandlingStateStores', reader => {
const map = new Map(
- this.modifiedBaseRanges.read(reader).map(s => ([s, new ObservableValue(false, 'State')]))
+ this.modifiedBaseRanges.read(reader).map(s => ([s, observableValue(`BaseRangeHandledState${s.baseRange}`, false)]))
);
return map;
});
- public readonly unhandledConflictsCount = derivedObservable('unhandledConflictsCount', reader => {
+ public readonly unhandledConflictsCount = derived('unhandledConflictsCount', reader => {
const map = this.modifiedBaseRangeHandlingStateStores.read(reader);
let handledCount = 0;
for (const [_key, value] of map) {
@@ -84,9 +84,9 @@ export class MergeEditorModel extends EditorModel {
return map.size - handledCount;
});
- public readonly hasUnhandledConflicts = this.unhandledConflictsCount.map(value => value > 0);
+ public readonly hasUnhandledConflicts = this.unhandledConflictsCount.map(value => /** @description hasUnhandledConflicts */ value > 0);
- public readonly input1ResultMapping = derivedObservable('input1ResultMapping', reader => {
+ public readonly input1ResultMapping = derived('input1ResultMapping', reader => {
const resultDiffs = this.resultDiffs.read(reader);
const modifiedBaseRanges = DocumentMapping.betweenOutputs(this.input1LinesDiffs.read(reader), resultDiffs, this.input1.getLineCount());
@@ -103,7 +103,7 @@ export class MergeEditorModel extends EditorModel {
);
});
- public readonly input2ResultMapping = derivedObservable('input2ResultMapping', reader => {
+ public readonly input2ResultMapping = derived('input2ResultMapping', reader => {
const resultDiffs = this.resultDiffs.read(reader);
const modifiedBaseRanges = DocumentMapping.betweenOutputs(this.input2LinesDiffs.read(reader), resultDiffs, this.input2.getLineCount());
@@ -120,6 +120,8 @@ export class MergeEditorModel extends EditorModel {
);
});
+ readonly resultSnapshot: ITextSnapshot;
+
constructor(
readonly base: ITextModel,
readonly input1: ITextModel,
@@ -132,11 +134,13 @@ export class MergeEditorModel extends EditorModel {
readonly input2Description: string | undefined,
readonly result: ITextModel,
private readonly diffComputer: IDiffComputer,
+ options: { resetUnknownOnInitialization: boolean },
@IModelService private readonly modelService: IModelService,
@ILanguageService private readonly languageService: ILanguageService,
) {
super();
+ this.resultSnapshot = result.createSnapshot();
this._register(keepAlive(this.modifiedBaseRangeStateStores));
this._register(keepAlive(this.modifiedBaseRangeHandlingStateStores));
this._register(keepAlive(this.input1ResultMapping));
@@ -145,7 +149,7 @@ export class MergeEditorModel extends EditorModel {
let shouldResetHandlingState = true;
this._register(
autorunHandleChanges(
- 'Recompute State',
+ 'Merge Editor Model: Recompute State',
{
handleChange: (ctx) => {
if (ctx.didChange(this.modifiedBaseRangeHandlingStateStores)) {
@@ -165,6 +169,7 @@ export class MergeEditorModel extends EditorModel {
const resultDiffs = this.resultTextModelDiffs.diffs.read(reader);
const stores = this.modifiedBaseRangeStateStores.read(reader);
transaction(tx => {
+ /** @description Merge Editor Model: Recompute State */
this.recomputeState(resultDiffs, stores, tx);
if (shouldResetHandlingState) {
shouldResetHandlingState = false;
@@ -179,16 +184,18 @@ export class MergeEditorModel extends EditorModel {
)
);
- this.onInitialized.then(() => {
- this.resetUnknown();
- });
+ if (options.resetUnknownOnInitialization) {
+ this.onInitialized.then(() => {
+ this.resetUnknown();
+ });
+ }
}
public getRangeInResult(baseRange: LineRange, reader?: IReader): LineRange {
return this.resultTextModelDiffs.getResultRange(baseRange, reader);
}
- private recomputeState(resultDiffs: DetailedLineRangeMapping[], stores: Map<ModifiedBaseRange, ObservableValue<ModifiedBaseRangeState>>, tx: ITransaction): void {
+ private recomputeState(resultDiffs: DetailedLineRangeMapping[], stores: Map<ModifiedBaseRange, ISettableObservable<ModifiedBaseRangeState>>, tx: ITransaction): void {
const baseRangeWithStoreAndTouchingDiffs = leftJoin(
stores,
resultDiffs,
@@ -202,12 +209,16 @@ export class MergeEditorModel extends EditorModel {
);
for (const row of baseRangeWithStoreAndTouchingDiffs) {
- row.left[1].set(this.computeState(row.left[0], row.rights), tx);
+ const newState = this.computeState(row.left[0], row.rights);
+ if (!row.left[1].get().equals(newState)) {
+ row.left[1].set(newState, tx);
+ }
}
}
public resetUnknown(): void {
transaction(tx => {
+ /** @description Reset Unknown Base Range States */
for (const range of this.modifiedBaseRanges.get()) {
if (this.getState(range).get().conflicting) {
this.setState(range, ModifiedBaseRangeState.default, false, tx);
@@ -218,6 +229,7 @@ export class MergeEditorModel extends EditorModel {
public mergeNonConflictingDiffs(): void {
transaction((tx) => {
+ /** @description Merge None Conflicting Diffs */
for (const m of this.modifiedBaseRanges.get()) {
if (m.isConflicting) {
continue;
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange.ts
index 4dadbb6816a..2e6b6031382 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange.ts
@@ -281,18 +281,30 @@ export class ModifiedBaseRangeState {
}
public toString(): string {
- const arr: ('1' | '2')[] = [];
+ const arr: string[] = [];
if (this.input1) {
- arr.push('1');
+ arr.push('1✓');
}
if (this.input2) {
- arr.push('2');
+ arr.push('2✓');
}
if (this.input2First) {
arr.reverse();
}
+ if (this.conflicting) {
+ arr.push('conflicting');
+ }
return arr.join(',');
}
+
+ equals(other: ModifiedBaseRangeState): boolean {
+ return (
+ this.input1 === other.input1 &&
+ this.input2 === other.input2 &&
+ this.input2First === other.input2First &&
+ this.conflicting === other.conflicting
+ );
+ }
}
export const enum InputState {
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts b/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts
index c57c0ff51ea..af21adbebb5 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/model/textModelDiffs.ts
@@ -7,17 +7,17 @@ import { compareBy, numberComparator } from 'vs/base/common/arrays';
import { BugIndicatingError } from 'vs/base/common/errors';
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { ITextModel } from 'vs/editor/common/model';
-import { IObservable, IReader, ITransaction, ObservableValue, transaction } from 'vs/workbench/contrib/audioCues/browser/observable';
import { DetailedLineRangeMapping } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping';
import { LineRangeEdit } from 'vs/workbench/contrib/mergeEditor/browser/model/editing';
import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';
import { ReentrancyBarrier } from 'vs/workbench/contrib/mergeEditor/browser/utils';
import { IDiffComputer } from './diffComputer';
+import { IObservable, IReader, ITransaction, observableValue, transaction } from 'vs/base/common/observable';
export class TextModelDiffs extends Disposable {
private updateCount = 0;
- private readonly _state = new ObservableValue<TextModelDiffState, TextModelDiffChangeReason>(TextModelDiffState.initializing, 'LiveDiffState');
- private readonly _diffs = new ObservableValue<DetailedLineRangeMapping[], TextModelDiffChangeReason>([], 'LiveDiffs');
+ private readonly _state = observableValue<TextModelDiffState, TextModelDiffChangeReason>('LiveDiffState', TextModelDiffState.initializing);
+ private readonly _diffs = observableValue<DetailedLineRangeMapping[], TextModelDiffChangeReason>('LiveDiffs', []);
private readonly barrier = new ReentrancyBarrier();
private isDisposed = false;
@@ -54,6 +54,7 @@ export class TextModelDiffs extends Disposable {
}
transaction(tx => {
+ /** @description Starting Diff Computation. */
this._state.set(
initializing ? TextModelDiffState.initializing : TextModelDiffState.updating,
tx,
@@ -72,6 +73,7 @@ export class TextModelDiffs extends Disposable {
}
transaction(tx => {
+ /** @description Completed Diff Computation */
if (result.diffs) {
this._state.set(TextModelDiffState.upToDate, tx, TextModelDiffChangeReason.textChange);
this._diffs.set(result.diffs, tx, TextModelDiffChangeReason.textChange);
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/utils.ts b/src/vs/workbench/contrib/mergeEditor/browser/utils.ts
index 23277aeae95..7e92f970387 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/utils.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/utils.ts
@@ -5,11 +5,10 @@
import { CompareResult, ArrayQueue } from 'vs/base/common/arrays';
import { BugIndicatingError } from 'vs/base/common/errors';
-import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
+import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
+import { IObservable, autorun } from 'vs/base/common/observable';
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
import { IModelDeltaDecoration } from 'vs/editor/common/model';
-import { IObservable, autorun } from 'vs/workbench/contrib/audioCues/browser/observable';
-import { IDisposable } from 'xterm';
export class ReentrancyBarrier {
private isActive = false;
@@ -74,12 +73,12 @@ function toSize(value: number | string): string {
export function applyObservableDecorations(editor: CodeEditorWidget, decorations: IObservable<IModelDeltaDecoration[]>): IDisposable {
const d = new DisposableStore();
let decorationIds: string[] = [];
- d.add(autorun(reader => {
+ d.add(autorun(`Apply decorations from ${decorations.debugName}`, reader => {
const d = decorations.read(reader);
editor.changeDecorations(a => {
decorationIds = a.deltaDecorations(decorationIds, d);
});
- }, 'Update Decorations'));
+ }));
d.add({
dispose: () => {
editor.changeDecorations(a => {
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts
index 2b208650738..7289cc61637 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts
@@ -5,22 +5,23 @@
import { h } from 'vs/base/browser/dom';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
+import { autorun, IReader, observableFromEvent, observableSignalFromEvent } from 'vs/base/common/observable';
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
-import { EditorOption } from 'vs/editor/common/config/editorOptions';
-import { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';
import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';
export class EditorGutter<T extends IGutterItemInfo = IGutterItemInfo> extends Disposable {
private readonly scrollTop = observableFromEvent(
this._editor.onDidScrollChange,
- (e) => this._editor.getScrollTop()
+ (e) => /** @description editor.onDidScrollChange */ this._editor.getScrollTop()
);
+ private readonly isScrollTopZero = this.scrollTop.map((scrollTop) => /** @description isScrollTopZero */ scrollTop === 0);
private readonly modelAttached = observableFromEvent(
this._editor.onDidChangeModel,
- (e) => this._editor.hasModel()
+ (e) => /** @description editor.onDidChangeModel */ this._editor.hasModel()
);
- private readonly changeCounter = new ObservableValue(0, 'counter');
+ private readonly editorOnDidChangeViewZones = observableSignalFromEvent('onDidChangeViewZones', this._editor.onDidChangeViewZones);
+ private readonly editorOnDidContentSizeChange = observableSignalFromEvent('onDidContentSizeChange', this._editor.onDidContentSizeChange);
constructor(
private readonly _editor: CodeEditorWidget,
@@ -34,20 +35,11 @@ export class EditorGutter<T extends IGutterItemInfo = IGutterItemInfo> extends D
.root
);
- this._register(autorun((reader) => {
- scrollDecoration.className = this.scrollTop.read(reader) === 0 ? '' : 'scroll-decoration';
- }, 'update scroll decoration'));
+ this._register(autorun('update scroll decoration', (reader) => {
+ scrollDecoration.className = this.isScrollTopZero.read(reader) ? '' : 'scroll-decoration';
+ }));
-
- this._register(autorun((reader) => this.render(reader), 'Render'));
-
- this._editor.onDidChangeViewZones(e => {
- this.changeCounter.set(this.changeCounter.get() + 1, undefined);
- });
-
- this._editor.onDidContentSizeChange(e => {
- this.changeCounter.set(this.changeCounter.get() + 1, undefined);
- });
+ this._register(autorun('EditorGutter.Render', (reader) => this.render(reader)));
}
private readonly views = new Map<string, ManagedGutterItemView>();
@@ -56,7 +48,10 @@ export class EditorGutter<T extends IGutterItemInfo = IGutterItemInfo> extends D
if (!this.modelAttached.read(reader)) {
return;
}
- this.changeCounter.read(reader);
+
+ this.editorOnDidChangeViewZones.read(reader);
+ this.editorOnDidContentSizeChange.read(reader);
+
const scrollTop = this.scrollTop.read(reader);
const visibleRanges = this._editor.getVisibleRanges();
@@ -68,15 +63,13 @@ export class EditorGutter<T extends IGutterItemInfo = IGutterItemInfo> extends D
const visibleRange2 = new LineRange(
visibleRange.startLineNumber,
visibleRange.endLineNumber - visibleRange.startLineNumber
- );
+ ).deltaEnd(1);
const gutterItems = this.itemProvider.getIntersectingGutterItems(
visibleRange2,
reader
);
- const lineHeight = this._editor.getOptions().get(EditorOption.lineHeight);
-
for (const gutterItem of gutterItems) {
if (!gutterItem.range.touches(visibleRange2)) {
continue;
@@ -99,19 +92,10 @@ export class EditorGutter<T extends IGutterItemInfo = IGutterItemInfo> extends D
}
const top =
- (gutterItem.range.startLineNumber === 1
- ? -lineHeight
- : this._editor.getTopForLineNumber(
- gutterItem.range.startLineNumber - 1
- )) -
- scrollTop +
- lineHeight;
-
- const bottom = (
- gutterItem.range.endLineNumberExclusive <= this._editor.getModel()!.getLineCount()
- ? this._editor.getTopForLineNumber(gutterItem.range.endLineNumberExclusive)
- : this._editor.getTopForLineNumber(gutterItem.range.endLineNumberExclusive - 1) + lineHeight
- ) - scrollTop;
+ gutterItem.range.startLineNumber <= this._editor.getModel()!.getLineCount()
+ ? this._editor.getTopForLineNumber(gutterItem.range.startLineNumber, true) - scrollTop
+ : this._editor.getBottomForLineNumber(gutterItem.range.startLineNumber - 1, false) - scrollTop;
+ const bottom = this._editor.getBottomForLineNumber(gutterItem.range.endLineNumberExclusive - 1, true) - scrollTop;
const height = bottom - top;
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 e9a1b888ce0..321d1594dd5 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts
@@ -3,28 +3,32 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { h } from 'vs/base/browser/dom';
+import { h, reset } from 'vs/base/browser/dom';
import { IView, IViewSize } from 'vs/base/browser/ui/grid/grid';
-import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel';
+import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
+import { IObservable, observableFromEvent, observableValue, transaction } from 'vs/base/common/observable';
import { IEditorContributionDescription } from 'vs/editor/browser/editorExtensions';
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { ITextModel } from 'vs/editor/common/model';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { DEFAULT_EDITOR_MAX_DIMENSIONS, DEFAULT_EDITOR_MIN_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor';
-import { IObservable, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';
import { setStyle } from 'vs/workbench/contrib/mergeEditor/browser/utils';
import { MergeEditorViewModel } from 'vs/workbench/contrib/mergeEditor/browser/view/viewModel';
export abstract class CodeEditorView extends Disposable {
- private readonly _viewModel = new ObservableValue<undefined | MergeEditorViewModel>(undefined, 'viewModel');
+ private readonly _viewModel = observableValue<undefined | MergeEditorViewModel>('viewModel', undefined);
readonly viewModel: IObservable<undefined | MergeEditorViewModel> = this._viewModel;
- readonly model = this._viewModel.map(m => m?.model);
+ readonly model = this._viewModel.map(m => /** @description model */ m?.model);
protected readonly htmlElements = h('div.code-view', [
- h('div.title', { $: 'title' }),
+ 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' }),
@@ -44,7 +48,7 @@ export abstract class CodeEditorView extends Disposable {
setStyle(this.htmlElements.root, { width, height, top, left });
this.editor.layout({
width: width - this.htmlElements.gutterDiv.clientWidth,
- height: height - this.htmlElements.title.clientHeight,
+ height: height - this.htmlElements.header.clientHeight,
});
}
// preferredWidth?: number | undefined;
@@ -53,9 +57,6 @@ export abstract class CodeEditorView extends Disposable {
// snap?: boolean | undefined;
};
- private readonly _title = new IconLabel(this.htmlElements.title, { supportIcons: true });
- protected readonly _detail = new IconLabel(this.htmlElements.title, { supportIcons: true });
-
public readonly editor = this.instantiationService.createInstance(
CodeEditorWidget,
this.htmlElements.editor,
@@ -71,15 +72,15 @@ export abstract class CodeEditorView extends Disposable {
public readonly isFocused = observableFromEvent(
Event.any(this.editor.onDidBlurEditorWidget, this.editor.onDidFocusEditorWidget),
- () => this.editor.hasWidgetFocus()
+ () => /** @description editor.hasWidgetFocus */ this.editor.hasWidgetFocus()
);
public readonly cursorPosition = observableFromEvent(
this.editor.onDidChangeCursorPosition,
- () => this.editor.getPosition()
+ () => /** @description editor.getPosition */ this.editor.getPosition()
);
- public readonly cursorLineNumber = this.cursorPosition.map(p => p?.lineNumber);
+ public readonly cursorLineNumber = this.cursorPosition.map(p => /** @description cursorPosition.lineNumber */ p?.lineNumber);
constructor(
@IInstantiationService
@@ -100,9 +101,14 @@ export abstract class CodeEditorView extends Disposable {
detail: string | undefined
): void {
this.editor.setModel(textModel);
- this._title.setLabel(title, description);
- this._detail.setLabel('', detail);
- this._viewModel.set(viewModel, undefined);
+ reset(this.htmlElements.title, ...renderLabelWithIcons(title));
+ reset(this.htmlElements.description, ...(description ? renderLabelWithIcons(description) : []));
+ reset(this.htmlElements.detail, ...(detail ? renderLabelWithIcons(detail) : []));
+
+ transaction(tx => {
+ /** @description CodeEditorView: Set Model */
+ this._viewModel.set(viewModel, tx);
+ });
}
}
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts
index 1610a9a0b0d..abadef681ba 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView.ts
@@ -8,6 +8,7 @@ import { Toggle } from 'vs/base/browser/ui/toggle/toggle';
import { Action, IAction, Separator } from 'vs/base/common/actions';
import { Codicon } from 'vs/base/common/codicons';
import { Disposable } from 'vs/base/common/lifecycle';
+import { autorun, derived, IObservable, ISettableObservable, ITransaction, transaction, observableValue } from 'vs/base/common/observable';
import { noBreakWhitespace } from 'vs/base/common/strings';
import { isDefined } from 'vs/base/common/types';
import { EditorExtensionsRegistry, IEditorContributionDescription } from 'vs/editor/browser/editorExtensions';
@@ -18,7 +19,6 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { attachToggleStyler } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
-import { autorun, derivedObservable, IObservable, ITransaction, ObservableValue, transaction } from 'vs/workbench/contrib/audioCues/browser/observable';
import { InputState, ModifiedBaseRangeState } from 'vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange';
import { applyObservableDecorations, setFields } from 'vs/workbench/contrib/mergeEditor/browser/utils';
import { handledConflictMinimapOverViewRulerColor, unhandledConflictMinimapOverViewRulerColor } from 'vs/workbench/contrib/mergeEditor/browser/view/colors';
@@ -26,7 +26,7 @@ import { EditorGutter, IGutterItemInfo, IGutterItemView } from '../editorGutter'
import { CodeEditorView } from './codeEditorView';
export class InputCodeEditorView extends CodeEditorView {
- private readonly decorations = derivedObservable('decorations', reader => {
+ private readonly decorations = derived(`input${this.inputNumber}.decorations`, reader => {
const viewModel = this.viewModel.read(reader);
if (!viewModel) {
return [];
@@ -99,6 +99,136 @@ export class InputCodeEditorView extends CodeEditorView {
return result;
});
+ private readonly modifiedBaseRangeGutterItemInfos = derived(`input${this.inputNumber}.modifiedBaseRangeGutterItemInfos`, reader => {
+ const viewModel = this.viewModel.read(reader);
+ if (!viewModel) { return []; }
+ const model = viewModel.model;
+ const inputNumber = this.inputNumber;
+
+ return model.modifiedBaseRanges.read(reader)
+ .filter((r) => r.getInputDiffs(this.inputNumber).length > 0)
+ .map<ModifiedBaseRangeGutterItemInfo>((baseRange, idx) => ({
+ id: idx.toString(),
+ range: baseRange.getInputRange(this.inputNumber),
+ enabled: model.isUpToDate,
+ toggleState: derived('checkbox is checked', (reader) => {
+ const input = model
+ .getState(baseRange)
+ .read(reader)
+ .getInput(this.inputNumber);
+ return input === InputState.second && !baseRange.isOrderRelevant
+ ? InputState.first
+ : input;
+ }
+ ),
+ setState: (value, tx) => viewModel.setState(
+ baseRange,
+ model
+ .getState(baseRange)
+ .get()
+ .withInputValue(this.inputNumber, value),
+ tx
+ ),
+ toggleBothSides() {
+ transaction(tx => {
+ /** @description Context Menu: toggle both sides */
+ const state = model
+ .getState(baseRange)
+ .get();
+ model.setState(
+ baseRange,
+ state
+ .toggle(inputNumber)
+ .toggle(inputNumber === 1 ? 2 : 1),
+ true,
+ tx
+ );
+ });
+ },
+ getContextMenuActions: () => {
+ const state = model.getState(baseRange).get();
+ const handled = model.isHandled(baseRange).get();
+
+ const update = (newState: ModifiedBaseRangeState) => {
+ transaction(tx => {
+ /** @description Context Menu: Update Base Range State */
+ return viewModel.setState(baseRange, newState, tx);
+ });
+ };
+
+ function action(id: string, label: string, targetState: ModifiedBaseRangeState, checked: boolean) {
+ const action = new Action(id, label, undefined, true, () => {
+ update(targetState);
+ });
+ action.checked = checked;
+ return action;
+ }
+ const both = state.input1 && state.input2;
+
+ return [
+ baseRange.input1Diffs.length > 0
+ ? action(
+ 'mergeEditor.acceptInput1',
+ localize('mergeEditor.accept', 'Accept {0}', model.input1Title),
+ state.toggle(1),
+ state.input1
+ )
+ : undefined,
+ baseRange.input2Diffs.length > 0
+ ? action(
+ 'mergeEditor.acceptInput2',
+ localize('mergeEditor.accept', 'Accept {0}', model.input2Title),
+ state.toggle(2),
+ state.input2
+ )
+ : undefined,
+ baseRange.isConflicting
+ ? setFields(
+ action(
+ 'mergeEditor.acceptBoth',
+ localize(
+ 'mergeEditor.acceptBoth',
+ 'Accept Both'
+ ),
+ state.withInput1(!both).withInput2(!both),
+ both
+ ),
+ { enabled: baseRange.canBeCombined }
+ )
+ : undefined,
+ new Separator(),
+ baseRange.isConflicting
+ ? setFields(
+ action(
+ 'mergeEditor.swap',
+ localize('mergeEditor.swap', 'Swap'),
+ state.swap(),
+ false
+ ),
+ { enabled: !state.isEmpty && (!both || baseRange.isOrderRelevant) }
+ )
+ : undefined,
+
+ setFields(
+ new Action(
+ 'mergeEditor.markAsHandled',
+ localize('mergeEditor.markAsHandled', 'Mark as Handled'),
+ undefined,
+ true,
+ () => {
+ transaction((tx) => {
+ /** @description Context Menu: Mark as handled */
+ model.setHandled(baseRange, !handled, tx);
+ });
+ }
+ ),
+ { checked: handled }
+ ),
+ ].filter(isDefined);
+ }
+ }));
+ });
+
constructor(
public readonly inputNumber: 1 | 2,
@IInstantiationService instantiationService: IInstantiationService,
@@ -112,112 +242,7 @@ export class InputCodeEditorView extends CodeEditorView {
this._register(
new EditorGutter(this.editor, this.htmlElements.gutterDiv, {
getIntersectingGutterItems: (range, reader) => {
- const viewModel = this.viewModel.read(reader);
- if (!viewModel) { return []; }
- const model = viewModel.model;
-
- return model.modifiedBaseRanges.read(reader)
- .filter((r) => r.getInputDiffs(this.inputNumber).length > 0)
- .map<ModifiedBaseRangeGutterItemInfo>((baseRange, idx) => ({
- id: idx.toString(),
- range: baseRange.getInputRange(this.inputNumber),
- enabled: model.isUpToDate,
- toggleState: derivedObservable('toggle', (reader) => {
- const input = model
- .getState(baseRange)
- .read(reader)
- .getInput(this.inputNumber);
- return input === InputState.second && !baseRange.isOrderRelevant
- ? InputState.first
- : input;
- }
- ),
- setState: (value, tx) => viewModel.setState(
- baseRange,
- model
- .getState(baseRange)
- .get()
- .withInputValue(this.inputNumber, value),
- tx
- ),
- getContextMenuActions: () => {
- const state = model.getState(baseRange).get();
- const handled = model.isHandled(baseRange).get();
-
- const update = (newState: ModifiedBaseRangeState) => {
- transaction(tx => viewModel.setState(baseRange, newState, tx));
- };
-
- function action(id: string, label: string, targetState: ModifiedBaseRangeState, checked: boolean) {
- const action = new Action(id, label, undefined, true, () => {
- update(targetState);
- });
- action.checked = checked;
- return action;
- }
- const both = state.input1 && state.input2;
-
- return [
- baseRange.input1Diffs.length > 0
- ? action(
- 'mergeEditor.acceptInput1',
- localize('mergeEditor.accept', 'Accept {0}', model.input1Title),
- state.toggle(1),
- state.input1
- )
- : undefined,
- baseRange.input2Diffs.length > 0
- ? action(
- 'mergeEditor.acceptInput2',
- localize('mergeEditor.accept', 'Accept {0}', model.input2Title),
- state.toggle(2),
- state.input2
- )
- : undefined,
- baseRange.isConflicting
- ? setFields(
- action(
- 'mergeEditor.acceptBoth',
- localize(
- 'mergeEditor.acceptBoth',
- 'Accept Both'
- ),
- state.withInput1(!both).withInput2(!both),
- both
- ),
- { enabled: baseRange.canBeCombined }
- )
- : undefined,
- new Separator(),
- baseRange.isConflicting
- ? setFields(
- action(
- 'mergeEditor.swap',
- localize('mergeEditor.swap', 'Swap'),
- state.swap(),
- false
- ),
- { enabled: !state.isEmpty && (!both || baseRange.isOrderRelevant) }
- )
- : undefined,
-
- setFields(
- new Action(
- 'mergeEditor.markAsHandled',
- localize('mergeEditor.markAsHandled', 'Mark as Handled'),
- undefined,
- true,
- () => {
- transaction((tx) => {
- model.setHandled(baseRange, !handled, tx);
- });
- }
- ),
- { checked: handled }
- ),
- ].filter(isDefined);
- }
- }));
+ return this.modifiedBaseRangeGutterItemInfos.read(reader);
},
createView: (item, target) => new MergeConflictGutterItemView(item, target, contextMenuService, themeService),
})
@@ -233,11 +258,12 @@ export interface ModifiedBaseRangeGutterItemInfo extends IGutterItemInfo {
enabled: IObservable<boolean>;
toggleState: IObservable<InputState>;
setState(value: boolean, tx: ITransaction): void;
+ toggleBothSides(): void;
getContextMenuActions(): readonly IAction[];
}
export class MergeConflictGutterItemView extends Disposable implements IGutterItemView<ModifiedBaseRangeGutterItemInfo> {
- private readonly item = new ObservableValue<ModifiedBaseRangeGutterItemInfo | undefined>(undefined, 'item');
+ private readonly item: ISettableObservable<ModifiedBaseRangeGutterItemInfo>;
constructor(
item: ModifiedBaseRangeGutterItemInfo,
@@ -247,7 +273,7 @@ export class MergeConflictGutterItemView extends Disposable implements IGutterIt
) {
super();
- this.item.set(item, undefined);
+ this.item = observableValue('item', item);
target.classList.add('merge-accept-gutter-marker');
@@ -257,12 +283,12 @@ export class MergeConflictGutterItemView extends Disposable implements IGutterIt
this._register(
dom.addDisposableListener(checkBox.domNode, dom.EventType.MOUSE_DOWN, (e) => {
- if (e.button === 2) {
- const item = this.item.get();
- if (!item) {
- return;
- }
+ const item = this.item.get();
+ if (!item) {
+ return;
+ }
+ if (e.button === /* Right */ 2) {
contextMenuService.showContextMenu({
getAnchor: () => checkBox.domNode,
getActions: item.getContextMenuActions,
@@ -270,13 +296,19 @@ export class MergeConflictGutterItemView extends Disposable implements IGutterIt
e.stopPropagation();
e.preventDefault();
+ } else if (e.button === /* Middle */ 1) {
+ item.toggleBothSides();
+
+ e.stopPropagation();
+ e.preventDefault();
}
})
);
+
checkBox.domNode.classList.add('accept-conflict-group');
this._register(
- autorun((reader) => {
+ autorun('Update Checkbox', (reader) => {
const item = this.item.read(reader)!;
const value = item.toggleState.read(reader);
const iconMap: Record<InputState, { icon: Codicon | undefined; checked: boolean }> = {
@@ -293,11 +325,12 @@ export class MergeConflictGutterItemView extends Disposable implements IGutterIt
} else {
checkBox.enable();
}
- }, 'Update Toggle State')
+ })
);
this._register(checkBox.onChange(() => {
transaction(tx => {
+ /** @description Handle Checkbox Change */
this.item.get()!.setState(checkBox.checked, tx);
});
}));
@@ -315,6 +348,9 @@ export class MergeConflictGutterItemView extends Disposable implements IGutterIt
}
update(baseRange: ModifiedBaseRangeGutterItemInfo): void {
- this.item.set(baseRange, undefined);
+ transaction(tx => {
+ /** @description MergeConflictGutterItemView: Updating new base range */
+ this.item.set(baseRange, tx);
+ });
}
}
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts
index fc6919af08a..e1557eb0c9a 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView.ts
@@ -4,17 +4,17 @@
*--------------------------------------------------------------------------------------------*/
import { CompareResult } from 'vs/base/common/arrays';
+import { autorun, derived } from 'vs/base/common/observable';
import { IModelDeltaDecoration, MinimapPosition, OverviewRulerLane } from 'vs/editor/common/model';
import { localize } from 'vs/nls';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
-import { autorun, derivedObservable } from 'vs/workbench/contrib/audioCues/browser/observable';
import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';
import { applyObservableDecorations, join } from 'vs/workbench/contrib/mergeEditor/browser/utils';
import { handledConflictMinimapOverViewRulerColor, unhandledConflictMinimapOverViewRulerColor } from 'vs/workbench/contrib/mergeEditor/browser/view/colors';
import { CodeEditorView } from './codeEditorView';
export class ResultCodeEditorView extends CodeEditorView {
- private readonly decorations = derivedObservable('decorations', reader => {
+ private readonly decorations = derived('result.decorations', reader => {
const viewModel = this.viewModel.read(reader);
if (!viewModel) {
return [];
@@ -107,24 +107,25 @@ export class ResultCodeEditorView extends CodeEditorView {
this._register(applyObservableDecorations(this.editor, this.decorations));
- this._register(autorun(reader => {
+ this._register(autorun('update remainingConflicts label', reader => {
const model = this.model.read(reader);
if (!model) {
return;
}
const count = model.unhandledConflictsCount.read(reader);
- this._detail.setLabel(count === 1
+ this.htmlElements.detail.innerText = count === 1
? localize(
'mergeEditor.remainingConflicts',
- '{0} Remaining Conflict',
+ '{0} Conflict Remaining',
count
)
: localize(
'mergeEditor.remainingConflict',
- '{0} Remaining Conflicts',
+ '{0} Conflicts Remaining ',
count
- ));
- }, 'update label'));
+ );
+
+ }));
}
}
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/media/mergeEditor.css b/src/vs/workbench/contrib/mergeEditor/browser/view/media/mergeEditor.css
index b832e88bd84..e9c0ada7df3 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/view/media/mergeEditor.css
+++ b/src/vs/workbench/contrib/mergeEditor/browser/view/media/mergeEditor.css
@@ -8,11 +8,27 @@
height: 30px;
display: flex;
align-content: center;
- justify-content: space-between;
}
-.monaco-workbench .merge-editor .code-view > .title .monaco-icon-label {
- margin: auto 0;
+.monaco-workbench .merge-editor .code-view > .title>SPAN {
+ align-self: center;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ padding-right: 6px;
+}
+
+.monaco-workbench .merge-editor .code-view > .title>SPAN.title {
+ flex-shrink: 0;
+}
+
+.monaco-workbench .merge-editor .code-view > .title>SPAN.description {
+ opacity: 0.7;
+ font-size: 90%;
+}
+
+.monaco-workbench .merge-editor .code-view > .title>SPAN.detail {
+ margin-left: auto;
+ flex-shrink: 0;
}
.monaco-workbench .merge-editor .code-view > .container {
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts
index ae2fcbcd7f1..abd5efbe468 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts
@@ -10,11 +10,14 @@ import { IAction } from 'vs/base/common/actions';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Color } from 'vs/base/common/color';
import { BugIndicatingError } from 'vs/base/common/errors';
-import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle';
+import { Emitter, Event } from 'vs/base/common/event';
+import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
+import { autorunWithStore, IObservable } from 'vs/base/common/observable';
import { isEqual } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import 'vs/css!./media/mergeEditor';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
+import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
import { IEditorOptions as ICodeEditorOptions } from 'vs/editor/common/config/editorOptions';
import { ICodeEditorViewState, ScrollType } from 'vs/editor/common/editorCommon';
@@ -24,7 +27,7 @@ import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/men
import { IMenuService, MenuId } from 'vs/platform/actions/common/actions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
-import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor';
+import { IEditorOptions, ITextEditorOptions, ITextResourceEditorInput } from 'vs/platform/editor/common/editor';
import { IFileService } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILabelService } from 'vs/platform/label/common/label';
@@ -33,10 +36,9 @@ 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 { EditorInputWithOptions, EditorResourceAccessor, IEditorOpenContext } from 'vs/workbench/common/editor';
+import { IEditorOpenContext } from 'vs/workbench/common/editor';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { applyTextEditorOptions } from 'vs/workbench/common/editor/editorOptions';
-import { autorunWithStore, IObservable } from 'vs/workbench/contrib/audioCues/browser/observable';
import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput';
import { DocumentMapping, getOppositeDirection, MappingDirection } from 'vs/workbench/contrib/mergeEditor/browser/model/mapping';
import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel';
@@ -45,7 +47,6 @@ 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';
@@ -85,11 +86,9 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
private readonly _sessionDisposables = new DisposableStore();
private _grid!: Grid<IView>;
-
-
- private readonly input1View = this._register(this.instantiation.createInstance(InputCodeEditorView, 1));
- private readonly input2View = this._register(this.instantiation.createInstance(InputCodeEditorView, 2));
- private readonly inputResultView = this._register(this.instantiation.createInstance(ResultCodeEditorView));
+ private readonly input1View = this._register(this.instantiationService.createInstance(InputCodeEditorView, 1));
+ private readonly input2View = this._register(this.instantiationService.createInstance(InputCodeEditorView, 2));
+ private readonly inputResultView = this._register(this.instantiationService.createInstance(ResultCodeEditorView));
private readonly _layoutMode: MergeEditorLayout;
private readonly _ctxIsMergeEditor: IContextKey<boolean>;
@@ -104,7 +103,7 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
}
constructor(
- @IInstantiationService private readonly instantiation: IInstantiationService,
+ @IInstantiationService instantiation: IInstantiationService,
@ILabelService private readonly _labelService: ILabelService,
@IMenuService private readonly _menuService: IMenuService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@@ -116,7 +115,6 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
@IEditorService editorService: IEditorService,
@IEditorGroupsService editorGroupService: IEditorGroupsService,
@IFileService fileService: IFileService,
- @IEditorResolverService private readonly _editorResolverService: IEditorResolverService,
) {
super(MergeEditor.ID, telemetryService, instantiation, storageService, textResourceConfigurationService, themeService, editorService, editorGroupService, fileService);
@@ -187,7 +185,7 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
createAndFillInActionBarActions(toolbarMenu, { renderShortTitle: true, shouldForwardArgs: true }, actions);
if (actions.length > 0) {
const [first] = actions;
- const acceptBtn = this.instantiation.createInstance(FloatingClickWidget, this.inputResultView.editor, first.label, first.id);
+ const acceptBtn = this.instantiationService.createInstance(FloatingClickWidget, this.inputResultView.editor, first.label, first.id);
toolbarMenuDisposables.add(acceptBtn.onClick(() => first.run(this.inputResultView.editor.getModel()?.uri)));
toolbarMenuDisposables.add(acceptBtn);
acceptBtn.render();
@@ -209,6 +207,19 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
super.dispose();
}
+ // --- layout constraints
+
+ private readonly _onDidChangeSizeConstraints = new Emitter<void>();
+ override readonly onDidChangeSizeConstraints: Event<void> = this._onDidChangeSizeConstraints.event;
+
+ override get minimumWidth() {
+ return this._layoutMode.value === 'mixed'
+ ? this.input1View.view.minimumWidth + this.input1View.view.minimumWidth
+ : this.input1View.view.minimumWidth + this.input1View.view.minimumWidth + this.inputResultView.view.minimumWidth;
+ }
+
+ // ---
+
override getTitle(): string {
if (this.input) {
return this.input.getName();
@@ -284,7 +295,6 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
await super.setInput(input, options, context, token);
this._sessionDisposables.clear();
- this._toggleEditorOverwrite(true);
const model = await input.resolve();
this._model = model;
@@ -297,16 +307,17 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
this._ctxBaseResourceScheme.set(model.base.uri.scheme);
const viewState = this.loadEditorViewState(input, context);
- this._applyViewState(viewState);
-
- this._sessionDisposables.add(thenIfNotDisposed(model.onInitialized, () => {
- const firstConflict = model.modifiedBaseRanges.get().find(r => r.isConflicting);
- if (!firstConflict) {
- return;
- }
-
- this.input1View.editor.revealLineInCenter(firstConflict.input1Range.startLineNumber);
- }));
+ if (viewState) {
+ this._applyViewState(viewState);
+ } else {
+ this._sessionDisposables.add(thenIfNotDisposed(model.onInitialized, () => {
+ const firstConflict = model.modifiedBaseRanges.get().find(r => r.isConflicting);
+ if (!firstConflict) {
+ return;
+ }
+ this.input1View.editor.revealLineInCenter(firstConflict.input1Range.startLineNumber);
+ }));
+ }
this._sessionDisposables.add(autorunWithStore((reader, store) => {
@@ -361,7 +372,6 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
super.clearInput();
this._sessionDisposables.clear();
- this._toggleEditorOverwrite(false);
for (const { editor } of [this.input1View, this.input2View, this.inputResultView]) {
editor.setModel(null);
@@ -393,39 +403,6 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
}
this._ctxIsMergeEditor.set(visible);
- this._toggleEditorOverwrite(visible);
- }
-
- private readonly _editorOverrideHandle = this._store.add(new MutableDisposable());
-
- private _toggleEditorOverwrite(haveIt: boolean) {
- if (!haveIt) {
- this._editorOverrideHandle.clear();
- return;
- }
- // this is RATHER UGLY. I dynamically register an editor for THIS (editor,input) so that
- // navigating within the merge editor works, e.g navigating from the outline or breakcrumps
- // or revealing a definition, reference etc
- // TODO@jrieken @bpasero @lramos15
- const input = this.input;
- if (input instanceof MergeEditorInput) {
- this._editorOverrideHandle.value = this._editorResolverService.registerEditor(
- `${input.result.scheme}:${input.result.fsPath}`,
- {
- id: `${this.getId()}/fake`,
- label: this.input?.getName()!,
- priority: RegisteredEditorPriority.exclusive
- },
- {},
- (candidate): EditorInputWithOptions => {
- const resource = EditorResourceAccessor.getCanonicalUri(candidate);
- if (!isEqual(resource, this.model?.result.uri)) {
- throw new Error(`Expected to be called WITH ${input.result.toString()}`);
- }
- return { editor: input };
- }
- );
- }
}
// ---- interact with "outside world" via`getControl`, `scopedContextKeyService`: we only expose the result-editor keep the others internal
@@ -454,6 +431,7 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
}
this._layoutMode.value = newValue;
this._ctxUsesColumnLayout.set(newValue);
+ this._onDidChangeSizeConstraints.fire();
}
private _applyViewState(state: IMergeEditorViewState | undefined) {
@@ -493,6 +471,37 @@ export class MergeEditor extends AbstractTextEditor<IMergeEditorViewState> {
}
}
+export class MergeEditorOpenHandlerContribution extends Disposable {
+
+ constructor(
+ @IEditorService private readonly _editorService: IEditorService,
+ @ICodeEditorService codeEditorService: ICodeEditorService,
+ ) {
+ super();
+ this._store.add(codeEditorService.registerCodeEditorOpenHandler(this.openCodeEditorFromMergeEditor.bind(this)));
+ }
+
+ private async openCodeEditorFromMergeEditor(input: ITextResourceEditorInput, _source: ICodeEditor | null, sideBySide?: boolean | undefined): Promise<ICodeEditor | null> {
+ const activePane = this._editorService.activeEditorPane;
+ if (!sideBySide
+ && input.options
+ && activePane instanceof MergeEditor
+ && activePane.getControl()
+ && activePane.input instanceof MergeEditorInput
+ && isEqual(input.resource, activePane.input.result)
+ ) {
+ // Special: stay inside the merge editor when it is active and when the input
+ // targets the result editor of the merge editor.
+ const targetEditor = <ICodeEditor>activePane.getControl()!;
+ applyTextEditorOptions(input.options, targetEditor, ScrollType.Smooth);
+ return targetEditor;
+ }
+
+ // cannot handle this
+ return null;
+ }
+}
+
type IMergeEditorViewState = ICodeEditorViewState & {
readonly input1State?: ICodeEditorViewState;
readonly input2State?: ICodeEditorViewState;
diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts
index b53549fea4b..de910cceaee 100644
--- a/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts
+++ b/src/vs/workbench/contrib/mergeEditor/browser/view/viewModel.ts
@@ -3,13 +3,12 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { findLast, lastOrDefault } from 'vs/base/common/arrays';
+import { findLast } from 'vs/base/common/arrays';
+import { derived, derivedObservableWithWritableCache, IReader, ITransaction, observableValue, transaction } from 'vs/base/common/observable';
import { ScrollType } from 'vs/editor/common/editorCommon';
-import { derivedObservable, derivedObservableWithWritableCache, IReader, ITransaction, ObservableValue, transaction } from 'vs/workbench/contrib/audioCues/browser/observable';
import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';
import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel';
import { ModifiedBaseRange, ModifiedBaseRangeState } from 'vs/workbench/contrib/mergeEditor/browser/model/modifiedBaseRange';
-import { elementAtOrUndefined } from 'vs/workbench/contrib/mergeEditor/browser/utils';
import { CodeEditorView } from 'vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView';
import { InputCodeEditorView } from 'vs/workbench/contrib/mergeEditor/browser/view/editors/inputCodeEditorView';
import { ResultCodeEditorView } from 'vs/workbench/contrib/mergeEditor/browser/view/editors/resultCodeEditorView';
@@ -26,9 +25,9 @@ export class MergeEditorViewModel {
return editors.find((e) => e.isFocused.read(reader)) || lastValue;
});
- private readonly manuallySetActiveModifiedBaseRange = new ObservableValue<
+ private readonly manuallySetActiveModifiedBaseRange = observableValue<
ModifiedBaseRange | undefined
- >(undefined, 'manuallySetActiveModifiedBaseRange');
+ >('manuallySetActiveModifiedBaseRange', undefined);
private getRange(editor: CodeEditorView, modifiedBaseRange: ModifiedBaseRange, reader: IReader | undefined): LineRange {
if (editor === this.resultCodeEditorView) {
@@ -39,7 +38,7 @@ export class MergeEditorViewModel {
}
}
- public readonly activeModifiedBaseRange = derivedObservable(
+ public readonly activeModifiedBaseRange = derived(
'activeModifiedBaseRange',
(reader) => {
const focusedEditor = this.lastFocusedEditor.read(reader);
@@ -78,7 +77,7 @@ export class MergeEditorViewModel {
this.model.setState(baseRange, state, true, tx);
}
- public goToConflict(getModifiedBaseRange: (editor: CodeEditorView, curLineNumber: number) => ModifiedBaseRange | undefined): void {
+ private goToConflict(getModifiedBaseRange: (editor: CodeEditorView, curLineNumber: number) => ModifiedBaseRange | undefined): void {
const lastFocusedEditor = this.lastFocusedEditor.get();
if (!lastFocusedEditor) {
return;
@@ -98,23 +97,35 @@ export class MergeEditorViewModel {
}
}
- public goToNextConflict(): void {
+ public goToNextModifiedBaseRange(onlyConflicting: boolean): void {
this.goToConflict(
(e, l) =>
this.model.modifiedBaseRanges
.get()
- .find((r) => this.getRange(e, r, undefined).startLineNumber > l) ||
- elementAtOrUndefined(this.model.modifiedBaseRanges.get(), 0)
+ .find(
+ (r) =>
+ (!onlyConflicting || r.isConflicting) &&
+ this.getRange(e, r, undefined).startLineNumber > l
+ ) ||
+ this.model.modifiedBaseRanges
+ .get()
+ .find((r) => !onlyConflicting || r.isConflicting)
);
}
- public goToPreviousConflict(): void {
+ public goToPreviousModifiedBaseRange(onlyConflicting: boolean): void {
this.goToConflict(
(e, l) =>
findLast(
this.model.modifiedBaseRanges.get(),
- (r) => this.getRange(e, r, undefined).endLineNumberExclusive < l
- ) || lastOrDefault(this.model.modifiedBaseRanges.get())
+ (r) =>
+ (!onlyConflicting || r.isConflicting) &&
+ this.getRange(e, r, undefined).endLineNumberExclusive < l
+ ) ||
+ findLast(
+ this.model.modifiedBaseRanges.get(),
+ (r) => !onlyConflicting || r.isConflicting
+ )
);
}
@@ -124,6 +135,7 @@ export class MergeEditorViewModel {
return;
}
transaction(tx => {
+ /** @description Toggle Active Conflict */
this.setState(
activeModifiedBaseRange,
this.model.getState(activeModifiedBaseRange).get().toggle(inputNumber),
diff --git a/src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts b/src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts
index 9963829694f..ebef4581ab6 100644
--- a/src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts
+++ b/src/vs/workbench/contrib/mergeEditor/test/browser/model.test.ts
@@ -5,13 +5,13 @@
import assert = require('assert');
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
+import { transaction } from 'vs/base/common/observable';
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
import { Range } from 'vs/editor/common/core/range';
-import { ITextModel } from 'vs/editor/common/model';
+import { EndOfLinePreference, ITextModel } from 'vs/editor/common/model';
import { EditorSimpleWorker } from 'vs/editor/common/services/editorSimpleWorker';
import { createModelServices, createTextModel } from 'vs/editor/test/common/testTextModel';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
-import { transaction } from 'vs/workbench/contrib/audioCues/browser/observable';
import { EditorWorkerServiceDiffComputer } from 'vs/workbench/contrib/mergeEditor/browser/model/diffComputer';
import { MergeEditorModel } from 'vs/workbench/contrib/mergeEditor/browser/model/mergeEditorModel';
@@ -29,9 +29,10 @@ suite('merge editor model', () => {
},
model => {
assert.deepStrictEqual(model.getProjections(), {
- base: '⟦⟧₀line1\nline2',
- input1: '⟦0\n⟧₀line1\nline2',
- input2: '⟦0\n⟧₀line1\nline2',
+ base: ['⟦⟧₀line1', 'line2'],
+ input1: ['⟦0', '⟧₀line1', 'line2'],
+ input2: ['⟦0', '⟧₀line1', 'line2'],
+ result: ['⟦⟧{conflicting}₀'],
});
model.toggleConflict(0, 1);
@@ -59,7 +60,12 @@ suite('merge editor model', () => {
"result": ""
},
model => {
- assert.deepStrictEqual(model.getProjections(), ({ base: "⟦⟧₀", input1: "⟦input1⟧₀", input2: "⟦input2⟧₀" }));
+ assert.deepStrictEqual(model.getProjections(), {
+ base: ['⟦⟧₀'],
+ input1: ['⟦input1⟧₀'],
+ input2: ['⟦input2⟧₀'],
+ result: ['⟦⟧{}₀'],
+ });
model.toggleConflict(0, 1);
assert.deepStrictEqual(
@@ -87,9 +93,10 @@ suite('merge editor model', () => {
},
model => {
assert.deepStrictEqual(model.getProjections(), {
- base: '⟦hello⟧₀',
- input1: '⟦hallo⟧₀',
- input2: '⟦helloworld⟧₀',
+ base: ['⟦hello⟧₀'],
+ input1: ['⟦hallo⟧₀'],
+ input2: ['⟦helloworld⟧₀'],
+ result: ['⟦⟧{conflicting}₀'],
});
model.toggleConflict(0, 1);
@@ -115,11 +122,34 @@ suite('merge editor model', () => {
},
model => {
assert.deepStrictEqual(model.getProjections(), {
- base: 'Zürich\nBern\n⟦Basel\n⟧₀Chur\n⟦⟧₁Genf\nThun⟦⟧₂',
- input1:
- 'Zürich\nBern\n⟦⟧₀Chur\n⟦Davos\n⟧₁Genf\nThun\n⟦function f(b:boolean) {}⟧₂',
- input2:
- 'Zürich\nBern\n⟦Basel (FCB)\n⟧₀Chur\n⟦⟧₁Genf\nThun\n⟦function f(a:number) {}⟧₂',
+ base: ['Zürich', 'Bern', '⟦Basel', '⟧₀Chur', '⟦⟧₁Genf', 'Thun⟦⟧₂'],
+ input1: [
+ 'Zürich',
+ 'Bern',
+ '⟦⟧₀Chur',
+ '⟦Davos',
+ '⟧₁Genf',
+ 'Thun',
+ '⟦function f(b:boolean) {}⟧₂',
+ ],
+ input2: [
+ 'Zürich',
+ 'Bern',
+ '⟦Basel (FCB)',
+ '⟧₀Chur',
+ '⟦⟧₁Genf',
+ 'Thun',
+ '⟦function f(a:number) {}⟧₂',
+ ],
+ result: [
+ 'Zürich',
+ 'Bern',
+ '⟦Basel',
+ '⟧{}₀Chur',
+ '⟦Davos',
+ '⟧{1✓}₁Genf',
+ 'Thun⟦⟧{}₂',
+ ],
});
model.toggleConflict(2, 1);
@@ -135,6 +165,61 @@ suite('merge editor model', () => {
}
);
});
+
+ test('conflicts are reset', async () => {
+ await testMergeModel(
+ {
+ "languageId": "typescript",
+ "base": "import { h } from 'vs/base/browser/dom';\nimport { Disposable, IDisposable } from 'vs/base/common/lifecycle';\nimport { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';\nimport { EditorOption } from 'vs/editor/common/config/editorOptions';\nimport { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';\nimport { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';\n",
+ "input1": "import { h } from 'vs/base/browser/dom';\nimport { Disposable, IDisposable } from 'vs/base/common/lifecycle';\nimport { observableSignalFromEvent } from 'vs/base/common/observable';\nimport { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';\nimport { autorun, IReader, observableFromEvent } from 'vs/workbench/contrib/audioCues/browser/observable';\nimport { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';\n",
+ "input2": "import { h } from 'vs/base/browser/dom';\nimport { Disposable, IDisposable } from 'vs/base/common/lifecycle';\nimport { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';\nimport { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';\nimport { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';\n",
+ "result": "import { h } from 'vs/base/browser/dom';\r\nimport { Disposable, IDisposable } from 'vs/base/common/lifecycle';\r\nimport { observableSignalFromEvent } from 'vs/base/common/observable';\r\nimport { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';\r\n<<<<<<< Updated upstream\r\nimport { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';\r\n=======\r\nimport { autorun, IReader, observableFromEvent } from 'vs/workbench/contrib/audioCues/browser/observable';\r\n>>>>>>> Stashed changes\r\nimport { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';\r\n"
+ },
+ model => {
+ assert.deepStrictEqual(model.getProjections(), {
+ base: [
+ "import { h } from 'vs/base/browser/dom';",
+ "import { Disposable, IDisposable } from 'vs/base/common/lifecycle';",
+ "⟦⟧₀import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';",
+ "⟦import { EditorOption } from 'vs/editor/common/config/editorOptions';",
+ "import { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';",
+ "⟧₁import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';",
+ '',
+ ],
+ input1: [
+ "import { h } from 'vs/base/browser/dom';",
+ "import { Disposable, IDisposable } from 'vs/base/common/lifecycle';",
+ "⟦import { observableSignalFromEvent } from 'vs/base/common/observable';",
+ "⟧₀import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';",
+ "⟦import { autorun, IReader, observableFromEvent } from 'vs/workbench/contrib/audioCues/browser/observable';",
+ "⟧₁import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';",
+ '',
+ ],
+ input2: [
+ "import { h } from 'vs/base/browser/dom';",
+ "import { Disposable, IDisposable } from 'vs/base/common/lifecycle';",
+ "⟦⟧₀import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';",
+ "⟦import { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';",
+ "⟧₁import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';",
+ '',
+ ],
+ result: [
+ "import { h } from 'vs/base/browser/dom';",
+ "import { Disposable, IDisposable } from 'vs/base/common/lifecycle';",
+ "⟦import { observableSignalFromEvent } from 'vs/base/common/observable';",
+ "⟧{1✓}₀import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';",
+ '⟦<<<<<<< Updated upstream',
+ "import { autorun, IReader, observableFromEvent, ObservableValue } from 'vs/workbench/contrib/audioCues/browser/observable';",
+ '=======',
+ "import { autorun, IReader, observableFromEvent } from 'vs/workbench/contrib/audioCues/browser/observable';",
+ '>>>>>>> Stashed changes',
+ "⟧{conflicting}₁import { LineRange } from 'vs/workbench/contrib/mergeEditor/browser/model/lineRange';",
+ '',
+ ],
+ });
+ }
+ );
+ });
});
async function testMergeModel(
@@ -197,7 +282,9 @@ class MergeModelInterface extends Disposable {
),
};
},
- }
+ }, {
+ resetUnknownOnInitialization: false
+ }
));
}
@@ -241,14 +328,25 @@ class MergeModelInterface extends Disposable {
}))
);
+ const resultTextModel = createTextModel(this.mergeModel.result.getValue());
+ applyRanges(
+ resultTextModel,
+ baseRanges.map<LabeledRange>((r, idx) => ({
+ range: this.mergeModel.getRangeInResult(r.baseRange).toRange(),
+ label: `{${this.mergeModel.getState(r).get()}}${toSmallNumbersDec(idx)}`,
+ }))
+ );
+
const result = {
- base: baseTextModel.getValue(),
- input1: input1TextModel.getValue(),
- input2: input2TextModel.getValue(),
+ base: baseTextModel.getValue(EndOfLinePreference.LF).split('\n'),
+ input1: input1TextModel.getValue(EndOfLinePreference.LF).split('\n'),
+ input2: input2TextModel.getValue(EndOfLinePreference.LF).split('\n'),
+ result: resultTextModel.getValue(EndOfLinePreference.LF).split('\n'),
};
baseTextModel.dispose();
input1TextModel.dispose();
input2TextModel.dispose();
+ resultTextModel.dispose();
return result;
}
diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts b/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts
index 2adc303d4e1..5074d759b06 100644
--- a/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts
+++ b/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts
@@ -31,7 +31,10 @@ registerAction2(class extends NotebookCellAction {
super(
{
id: MOVE_CELL_UP_COMMAND_ID,
- title: localize('notebookActions.moveCellUp', "Move Cell Up"),
+ title: {
+ value: localize('notebookActions.moveCellUp', "Move Cell Up"),
+ original: 'Move Cell Up'
+ },
icon: icons.moveUpIcon,
keybinding: {
primary: KeyMod.Alt | KeyCode.UpArrow,
@@ -57,7 +60,10 @@ registerAction2(class extends NotebookCellAction {
super(
{
id: MOVE_CELL_DOWN_COMMAND_ID,
- title: localize('notebookActions.moveCellDown', "Move Cell Down"),
+ title: {
+ value: localize('notebookActions.moveCellDown', "Move Cell Down"),
+ original: 'Move Cell Down'
+ },
icon: icons.moveDownIcon,
keybinding: {
primary: KeyMod.Alt | KeyCode.DownArrow,
@@ -83,7 +89,10 @@ registerAction2(class extends NotebookCellAction {
super(
{
id: COPY_CELL_UP_COMMAND_ID,
- title: localize('notebookActions.copyCellUp', "Copy Cell Up"),
+ title: {
+ value: localize('notebookActions.copyCellUp', "Copy Cell Up"),
+ original: 'Copy Cell Up'
+ },
keybinding: {
primary: KeyMod.Alt | KeyMod.Shift | KeyCode.UpArrow,
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()),
@@ -102,7 +111,10 @@ registerAction2(class extends NotebookCellAction {
super(
{
id: COPY_CELL_DOWN_COMMAND_ID,
- title: localize('notebookActions.copyCellDown', "Copy Cell Down"),
+ title: {
+ value: localize('notebookActions.copyCellDown', "Copy Cell Down"),
+ original: 'Copy Cell Down'
+ },
keybinding: {
primary: KeyMod.Alt | KeyMod.Shift | KeyCode.DownArrow,
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, InputFocusedContext.toNegated()),
@@ -137,7 +149,10 @@ registerAction2(class extends NotebookCellAction {
super(
{
id: SPLIT_CELL_COMMAND_ID,
- title: localize('notebookActions.splitCell', "Split Cell"),
+ title: {
+ value: localize('notebookActions.splitCell', "Split Cell"),
+ original: 'Split Cell'
+ },
menu: {
id: MenuId.NotebookCellTitle,
when: ContextKeyExpr.and(
@@ -212,7 +227,10 @@ registerAction2(class extends NotebookCellAction {
super(
{
id: JOIN_CELL_ABOVE_COMMAND_ID,
- title: localize('notebookActions.joinCellAbove', "Join With Previous Cell"),
+ title: {
+ value: localize('notebookActions.joinCellAbove', "Join With Previous Cell"),
+ original: 'Join With Previous Cell'
+ },
keybinding: {
when: NOTEBOOK_EDITOR_FOCUSED,
primary: KeyMod.WinCtrl | KeyMod.Alt | KeyMod.Shift | KeyCode.KeyJ,
@@ -238,7 +256,10 @@ registerAction2(class extends NotebookCellAction {
super(
{
id: JOIN_CELL_BELOW_COMMAND_ID,
- title: localize('notebookActions.joinCellBelow', "Join With Next Cell"),
+ title: {
+ value: localize('notebookActions.joinCellBelow', "Join With Next Cell"),
+ original: 'Join With Next Cell'
+ },
keybinding: {
when: NOTEBOOK_EDITOR_FOCUSED,
primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.KeyJ,
@@ -270,7 +291,10 @@ registerAction2(class ChangeCellToCodeAction extends NotebookMultiCellAction {
constructor() {
super({
id: CHANGE_CELL_TO_CODE_COMMAND_ID,
- title: localize('notebookActions.changeCellToCode', "Change Cell to Code"),
+ title: {
+ value: localize('notebookActions.changeCellToCode', "Change Cell to Code"),
+ original: 'Change Cell to Code'
+ },
keybinding: {
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)),
primary: KeyCode.KeyY,
@@ -294,7 +318,10 @@ registerAction2(class ChangeCellToMarkdownAction extends NotebookMultiCellAction
constructor() {
super({
id: CHANGE_CELL_TO_MARKDOWN_COMMAND_ID,
- title: localize('notebookActions.changeCellToMarkdown', "Change Cell to Markdown"),
+ title: {
+ value: localize('notebookActions.changeCellToMarkdown', "Change Cell to Markdown"),
+ original: 'Change Cell to Markdown'
+ },
keybinding: {
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, ContextKeyExpr.not(InputFocusedContextKey)),
primary: KeyCode.KeyM,
@@ -330,7 +357,10 @@ registerAction2(class CollapseCellInputAction extends NotebookMultiCellAction {
constructor() {
super({
id: COLLAPSE_CELL_INPUT_COMMAND_ID,
- title: localize('notebookActions.collapseCellInput', "Collapse Cell Input"),
+ title: {
+ value: localize('notebookActions.collapseCellInput', "Collapse Cell Input"),
+ original: 'Collapse Cell Input'
+ },
keybinding: {
when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_INPUT_COLLAPSED.toNegated(), InputFocusedContext.toNegated()),
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyC),
@@ -356,7 +386,10 @@ registerAction2(class ExpandCellInputAction extends NotebookMultiCellAction {
constructor() {
super({
id: EXPAND_CELL_INPUT_COMMAND_ID,
- title: localize('notebookActions.expandCellInput', "Expand Cell Input"),
+ title: {
+ value: localize('notebookActions.expandCellInput', "Expand Cell Input"),
+ original: 'Expand Cell Input'
+ },
keybinding: {
when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_INPUT_COLLAPSED),
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyC),
@@ -382,7 +415,10 @@ registerAction2(class CollapseCellOutputAction extends NotebookMultiCellAction {
constructor() {
super({
id: COLLAPSE_CELL_OUTPUT_COMMAND_ID,
- title: localize('notebookActions.collapseCellOutput', "Collapse Cell Output"),
+ title: {
+ value: localize('notebookActions.collapseCellOutput', "Collapse Cell Output"),
+ original: 'Collapse Cell Output'
+ },
keybinding: {
when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_OUTPUT_COLLAPSED.toNegated(), InputFocusedContext.toNegated(), NOTEBOOK_CELL_HAS_OUTPUTS),
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyT),
@@ -404,7 +440,10 @@ registerAction2(class ExpandCellOuputAction extends NotebookMultiCellAction {
constructor() {
super({
id: EXPAND_CELL_OUTPUT_COMMAND_ID,
- title: localize('notebookActions.expandCellOutput', "Expand Cell Output"),
+ title: {
+ value: localize('notebookActions.expandCellOutput', "Expand Cell Output"),
+ original: 'Expand Cell Output'
+ },
keybinding: {
when: ContextKeyExpr.and(NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_OUTPUT_COLLAPSED),
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyT),
@@ -427,7 +466,10 @@ registerAction2(class extends NotebookMultiCellAction {
super({
id: TOGGLE_CELL_OUTPUTS_COMMAND_ID,
precondition: NOTEBOOK_CELL_LIST_FOCUSED,
- title: localize('notebookActions.toggleOutputs', "Toggle Outputs"),
+ title: {
+ value: localize('notebookActions.toggleOutputs', "Toggle Outputs"),
+ original: 'Toggle Outputs'
+ },
description: {
description: localize('notebookActions.toggleOutputs', "Toggle Outputs"),
args: cellExecutionArgs
@@ -457,7 +499,10 @@ registerAction2(class CollapseAllCellInputsAction extends NotebookMultiCellActio
constructor() {
super({
id: COLLAPSE_ALL_CELL_INPUTS_COMMAND_ID,
- title: localize('notebookActions.collapseAllCellInput', "Collapse All Cell Inputs"),
+ title: {
+ value: localize('notebookActions.collapseAllCellInput', "Collapse All Cell Inputs"),
+ original: 'Collapse All Cell Inputs'
+ },
f1: true,
});
}
@@ -471,7 +516,10 @@ registerAction2(class ExpandAllCellInputsAction extends NotebookMultiCellAction
constructor() {
super({
id: EXPAND_ALL_CELL_INPUTS_COMMAND_ID,
- title: localize('notebookActions.expandAllCellInput', "Expand All Cell Inputs"),
+ title: {
+ value: localize('notebookActions.expandAllCellInput', "Expand All Cell Inputs"),
+ original: 'Expand All Cell Inputs'
+ },
f1: true
});
}
@@ -485,7 +533,10 @@ registerAction2(class CollapseAllCellOutputsAction extends NotebookMultiCellActi
constructor() {
super({
id: COLLAPSE_ALL_CELL_OUTPUTS_COMMAND_ID,
- title: localize('notebookActions.collapseAllCellOutput', "Collapse All Cell Outputs"),
+ title: {
+ value: localize('notebookActions.collapseAllCellOutput', "Collapse All Cell Outputs"),
+ original: 'Collapse All Cell Outputs'
+ },
f1: true,
});
}
@@ -499,7 +550,10 @@ registerAction2(class ExpandAllCellOutputsAction extends NotebookMultiCellAction
constructor() {
super({
id: EXPAND_ALL_CELL_OUTPUTS_COMMAND_ID,
- title: localize('notebookActions.expandAllCellOutput', "Expand All Cell Outputs"),
+ title: {
+ value: localize('notebookActions.expandAllCellOutput', "Expand All Cell Outputs"),
+ original: 'Expand All Cell Outputs'
+ },
f1: true
});
}
diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts
index 285dae8f94e..53ce0466795 100644
--- a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts
+++ b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts
@@ -243,7 +243,7 @@ registerAction2(class extends Action2 {
quickPickItems.push({
id: 'installSuggested',
description: suggestedExtension.displayName ?? suggestedExtension.extensionId,
- label: nls.localize('installSuggestedKernel', '$({0}) Install suggested extensions', Codicon.lightbulb.id),
+ label: `$(${Codicon.lightbulb.id}) ` + nls.localize('installSuggestedKernel', 'Install suggested extensions'),
});
}
// there is no kernel, show the install from marketplace
diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/gettingStarted/notebookGettingStarted.ts b/src/vs/workbench/contrib/notebook/browser/contrib/gettingStarted/notebookGettingStarted.ts
index 3e1c47af18a..2165fa94cc2 100644
--- a/src/vs/workbench/contrib/notebook/browser/contrib/gettingStarted/notebookGettingStarted.ts
+++ b/src/vs/workbench/contrib/notebook/browser/contrib/gettingStarted/notebookGettingStarted.ts
@@ -81,7 +81,10 @@ registerAction2(class NotebookClearNotebookLayoutAction extends Action2 {
constructor() {
super({
id: 'workbench.notebook.layout.gettingStarted',
- title: localize('workbench.notebook.layout.gettingStarted.label', "Reset notebook getting started"),
+ title: {
+ value: localize('workbench.notebook.layout.gettingStarted.label', "Reset notebook getting started"),
+ original: 'Reset notebook getting started'
+ },
f1: true,
precondition: ContextKeyExpr.equals(`config.${NotebookSetting.openGettingStarted}`, true),
category: CATEGORIES.Developer,
diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts b/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts
index b3af4143741..ab714f85b51 100644
--- a/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts
+++ b/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts
@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Disposable, DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle';
+import { localize } from 'vs/nls';
import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { CATEGORIES } from 'vs/workbench/common/actions';
@@ -121,7 +122,10 @@ registerAction2(class extends Action2 {
constructor() {
super({
id: 'notebook.toggleLayoutTroubleshoot',
- title: 'Toggle Notebook Layout Troubleshoot',
+ title: {
+ value: localize('workbench.notebook.toggleLayoutTroubleshoot', "Toggle Layout Troubleshoot"),
+ original: 'Toggle Notebook Layout Troubleshoot'
+ },
category: CATEGORIES.Developer,
f1: true
});
@@ -144,7 +148,10 @@ registerAction2(class extends Action2 {
constructor() {
super({
id: 'notebook.inspectLayout',
- title: 'Inspect Notebook Layout',
+ title: {
+ value: localize('workbench.notebook.inspectLayout', "Inspect Notebook Layout"),
+ original: 'Inspect Notebook Layout'
+ },
category: CATEGORIES.Developer,
f1: true
});
@@ -169,7 +176,10 @@ registerAction2(class extends Action2 {
constructor() {
super({
id: 'notebook.clearNotebookEdtitorTypeCache',
- title: 'Clear Notebook Editor Cache',
+ title: {
+ value: localize('workbench.notebook.clearNotebookEdtitorTypeCache', "Clear Notebook Editor Type Cache"),
+ original: 'Clear Notebook Editor Cache'
+ },
category: CATEGORIES.Developer,
f1: true
});
diff --git a/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts
index f7b01782aa0..9a42a89b378 100644
--- a/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts
+++ b/src/vs/workbench/contrib/notebook/browser/controller/coreActions.ts
@@ -42,7 +42,8 @@ export const enum CellToolbarOrder {
export const enum CellOverflowToolbarGroups {
Copy = '1_copy',
Insert = '2_insert',
- Edit = '3_edit'
+ Edit = '3_edit',
+ Share = '4_share'
}
export interface INotebookActionContext {
@@ -427,3 +428,9 @@ MenuRegistry.appendMenuItem(MenuId.EditorContext, {
group: CellOverflowToolbarGroups.Insert,
when: NOTEBOOK_EDITOR_FOCUSED
});
+
+MenuRegistry.appendMenuItem(MenuId.NotebookCellTitle, {
+ title: localize('miShare', "Share"),
+ submenu: MenuId.EditorContextShare,
+ group: CellOverflowToolbarGroups.Share
+});
diff --git a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts
index 7250d2dc560..1f64bddb5e3 100644
--- a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts
+++ b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts
@@ -49,6 +49,7 @@ export class DeleteCellAction extends MenuItemAction {
},
undefined,
{ shouldForwardArgs: true },
+ undefined,
contextKeyService,
commandService);
}
@@ -469,7 +470,7 @@ registerAction2(class DetectCellLanguageAction extends NotebookCellAction {
constructor() {
super({
id: DETECT_CELL_LANGUAGE,
- title: localize('detectLanguage', 'Accept Detected Language for Cell'),
+ title: { value: localize('detectLanguage', 'Accept Detected Language for Cell'), original: 'Accept Detected Language for Cell' },
f1: true,
precondition: ContextKeyExpr.and(NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_CELL_EDITABLE),
keybinding: { primary: KeyCode.KeyD | KeyMod.Alt | KeyMod.Shift, weight: KeybindingWeight.WorkbenchContrib }
diff --git a/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts
index 6a85c41a24a..454b565d71e 100644
--- a/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts
+++ b/src/vs/workbench/contrib/notebook/browser/controller/insertCellActions.ts
@@ -224,7 +224,7 @@ registerAction2(class InsertMarkdownCellAtTopAction extends NotebookAction {
MenuRegistry.appendMenuItem(MenuId.NotebookCellBetween, {
command: {
id: INSERT_CODE_CELL_BELOW_COMMAND_ID,
- title: localize('notebookActions.menu.insertCode', "$(add) Code"),
+ title: '$(add) ' + localize('notebookActions.menu.insertCode', "Code"),
tooltip: localize('notebookActions.menu.insertCode.tooltip', "Add Code Cell")
},
order: 0,
@@ -269,7 +269,7 @@ MenuRegistry.appendMenuItem(MenuId.NotebookToolbar, {
MenuRegistry.appendMenuItem(MenuId.NotebookCellListTop, {
command: {
id: INSERT_CODE_CELL_AT_TOP_COMMAND_ID,
- title: localize('notebookActions.menu.insertCode', "$(add) Code"),
+ title: '$(add) ' + localize('notebookActions.menu.insertCode', "Code"),
tooltip: localize('notebookActions.menu.insertCode.tooltip', "Add Code Cell")
},
order: 0,
@@ -299,7 +299,7 @@ MenuRegistry.appendMenuItem(MenuId.NotebookCellListTop, {
MenuRegistry.appendMenuItem(MenuId.NotebookCellBetween, {
command: {
id: INSERT_MARKDOWN_CELL_BELOW_COMMAND_ID,
- title: localize('notebookActions.menu.insertMarkdown', "$(add) Markdown"),
+ title: '$(add) ' + localize('notebookActions.menu.insertMarkdown', "Markdown"),
tooltip: localize('notebookActions.menu.insertMarkdown.tooltip', "Add Markdown Cell")
},
order: 1,
@@ -331,7 +331,7 @@ MenuRegistry.appendMenuItem(MenuId.NotebookToolbar, {
MenuRegistry.appendMenuItem(MenuId.NotebookCellListTop, {
command: {
id: INSERT_MARKDOWN_CELL_AT_TOP_COMMAND_ID,
- title: localize('notebookActions.menu.insertMarkdown', "$(add) Markdown"),
+ title: '$(add) ' + localize('notebookActions.menu.insertMarkdown', "Markdown"),
tooltip: localize('notebookActions.menu.insertMarkdown.tooltip', "Add Markdown Cell")
},
order: 1,
diff --git a/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts
index 4f6a3e489c5..7f6ac0a645b 100644
--- a/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts
+++ b/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts
@@ -21,7 +21,10 @@ registerAction2(class NotebookConfigureLayoutAction extends Action2 {
constructor() {
super({
id: 'workbench.notebook.layout.select',
- title: localize('workbench.notebook.layout.select.label', "Select between Notebook Layouts"),
+ title: {
+ value: localize('workbench.notebook.layout.select.label', "Select between Notebook Layouts"),
+ original: 'Select between Notebook Layouts'
+ },
f1: true,
precondition: ContextKeyExpr.equals(`config.${NotebookSetting.openGettingStarted}`, true),
category: NOTEBOOK_ACTIONS_CATEGORY,
@@ -57,7 +60,10 @@ registerAction2(class NotebookConfigureLayoutAction extends Action2 {
constructor() {
super({
id: 'workbench.notebook.layout.configure',
- title: localize('workbench.notebook.layout.configure.label', "Customize Notebook Layout"),
+ title: {
+ value: localize('workbench.notebook.layout.configure.label', "Customize Notebook Layout"),
+ original: 'Customize Notebook Layout'
+ },
f1: true,
category: NOTEBOOK_ACTIONS_CATEGORY,
menu: [
@@ -79,7 +85,10 @@ registerAction2(class NotebookConfigureLayoutFromEditorTitle extends Action2 {
constructor() {
super({
id: 'workbench.notebook.layout.configure.editorTitle',
- title: localize('workbench.notebook.layout.configure.label', "Customize Notebook Layout"),
+ title: {
+ value: localize('workbench.notebook.layout.configure.label', "Customize Notebook Layout"),
+ original: 'Customize Notebook Layout'
+ },
f1: false,
category: NOTEBOOK_ACTIONS_CATEGORY,
menu: [
@@ -177,7 +186,10 @@ registerAction2(class SaveMimeTypeDisplayOrder extends Action2 {
constructor() {
super({
id: 'notebook.saveMimeTypeOrder',
- title: localize('notebook.saveMimeTypeOrder', 'Save Mimetype Display Order'),
+ title: {
+ value: localize('notebook.saveMimeTypeOrder', 'Save Mimetype Display Order'),
+ original: 'Save Mimetype Display Order'
+ },
f1: true,
category: NOTEBOOK_ACTIONS_CATEGORY,
precondition: NOTEBOOK_IS_ACTIVE_EDITOR,
diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts
index c7d1f0d6594..da1a9c3a04a 100644
--- a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts
+++ b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts
@@ -156,7 +156,7 @@ class PropertyHeader extends Disposable {
this._toolbar = new ToolBar(cellToolbarContainer, this.contextMenuService, {
actionViewItemProvider: action => {
if (action instanceof MenuItemAction) {
- const item = new CodiconActionViewItem(action, this.keybindingService, this.notificationService, this.contextKeyService, this.themeService);
+ const item = new CodiconActionViewItem(action, undefined, this.keybindingService, this.notificationService, this.contextKeyService, this.themeService, this.contextMenuService);
return item;
}
diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts
index 07374920577..244f65f50ca 100644
--- a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts
+++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts
@@ -187,7 +187,7 @@ export class CellDiffSideBySideRenderer implements IListRenderer<SideBySideDiffE
const toolbar = new ToolBar(cellToolbarContainer, this.contextMenuService, {
actionViewItemProvider: action => {
if (action instanceof MenuItemAction) {
- const item = new CodiconActionViewItem(action, this.keybindingService, this.notificationService, this.contextKeyService, this.themeService);
+ const item = new CodiconActionViewItem(action, undefined, this.keybindingService, this.notificationService, this.contextKeyService, this.themeService, this.contextMenuService);
return item;
}
diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts
index 466fd1bf565..33bf079467f 100644
--- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts
+++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts
@@ -846,9 +846,10 @@ configurationRegistry.registerConfiguration({
[NotebookSetting.showFoldingControls]: {
description: nls.localize('notebook.showFoldingControls.description', "Controls when the Markdown header folding arrow is shown."),
type: 'string',
- enum: ['always', 'mouseover'],
+ enum: ['always', 'never', 'mouseover'],
enumDescriptions: [
nls.localize('showFoldingControls.always', "The folding controls are always visible."),
+ nls.localize('showFoldingControls.never', "Never show the folding controls and reduce the gutter size."),
nls.localize('showFoldingControls.mouseover', "The folding controls are visible only on mouseover."),
],
default: 'mouseover',
@@ -880,7 +881,7 @@ configurationRegistry.registerConfiguration({
tags: ['notebookLayout']
},
[NotebookSetting.markupFontSize]: {
- markdownDescription: nls.localize('notebook.markup.fontSize', "Controls the font size in pixels of rendered markup in notebooks. When set to `0`, 120% of `#editor.fontSize#` is used."),
+ markdownDescription: nls.localize('notebook.markup.fontSize', "Controls the font size in pixels of rendered markup in notebooks. When set to {0}, 120% of {1} is used.", '`0`', '`#editor.fontSize#`'),
type: 'number',
default: 0,
tags: ['notebookLayout']
@@ -899,13 +900,13 @@ configurationRegistry.registerConfiguration({
tags: ['notebookLayout']
},
[NotebookSetting.outputFontSize]: {
- markdownDescription: nls.localize('notebook.outputFontSize', "Font size for the output text for notebook cells. When set to 0 `#editor.fontSize#` is used."),
+ markdownDescription: nls.localize('notebook.outputFontSize', "Font size for the output text for notebook cells. When set to {0}, {1} is used.", '`0`', '`#editor.fontSize#`'),
type: 'number',
default: 0,
tags: ['notebookLayout']
},
[NotebookSetting.outputFontFamily]: {
- markdownDescription: nls.localize('notebook.outputFontFamily', "The font family for the output text for notebook cells. When set to empty, the `#editor.fontFamily#` is used."),
+ markdownDescription: nls.localize('notebook.outputFontFamily', "The font family for the output text for notebook cells. When set to empty, the {0} is used.", '`#editor.fontFamily#`'),
type: 'string',
tags: ['notebookLayout']
},
diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts
index 367c1f80e41..99d1d8de1f0 100644
--- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts
+++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts
@@ -244,6 +244,7 @@ export class NotebookEditor extends EditorPane implements IEditorPaneWithSelecti
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' };
diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts
index 6047d713272..3debc2599a6 100644
--- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts
+++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts
@@ -1100,6 +1100,7 @@ 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' };
@@ -1692,8 +1693,11 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
private _restoreSelectedKernel(viewState: INotebookEditorViewState | undefined): void {
if (viewState?.selectedKernelId && this.textModel) {
- const kernel = this.notebookKernelService.getMatchingKernel(this.textModel).all.find(k => k.id === viewState.selectedKernelId);
- if (kernel) {
+ const matching = this.notebookKernelService.getMatchingKernel(this.textModel);
+ const kernel = matching.all.find(k => k.id === viewState.selectedKernelId);
+ // Selected kernel may have already been picked prior to the view state loading
+ // If so, don't overwrite it with the saved kernel.
+ if (kernel && !matching.selected) {
this.notebookKernelService.selectKernelForNotebook(kernel, this.textModel);
}
}
diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts
index fe682e0e9aa..1808c007e8f 100644
--- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts
+++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView.ts
@@ -6,22 +6,9 @@
import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';
import * as DOM from 'vs/base/browser/dom';
import { MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
-import { MenuItemAction } from 'vs/platform/actions/common/actions';
-import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
-import { INotificationService } from 'vs/platform/notification/common/notification';
-import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
-import { IThemeService } from 'vs/platform/theme/common/themeService';
export class CodiconActionViewItem extends MenuEntryActionViewItem {
- constructor(
- _action: MenuItemAction,
- @IKeybindingService keybindingService: IKeybindingService,
- @INotificationService notificationService: INotificationService,
- @IContextKeyService contextKeyService: IContextKeyService,
- @IThemeService themeService: IThemeService,
- ) {
- super(_action, undefined, keybindingService, notificationService, contextKeyService, themeService);
- }
+
override updateLabel(): void {
if (this.options.label && this.label) {
DOM.reset(this.label, ...renderLabelWithIcons(this._commandAction.label ?? ''));
@@ -32,16 +19,6 @@ export class CodiconActionViewItem extends MenuEntryActionViewItem {
export class ActionViewWithLabel extends MenuEntryActionViewItem {
private _actionLabel?: HTMLAnchorElement;
- constructor(
- _action: MenuItemAction,
- @IKeybindingService keybindingService: IKeybindingService,
- @INotificationService notificationService: INotificationService,
- @IContextKeyService contextKeyService: IContextKeyService,
- @IThemeService themeService: IThemeService,
- ) {
- super(_action, undefined, keybindingService, notificationService, contextKeyService, themeService);
- }
-
override render(container: HTMLElement): void {
super.render(container);
container.classList.add('notebook-action-view-item');
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 c84443b70ab..35502e4e1a9 100644
--- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts
+++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts
@@ -42,7 +42,7 @@ export class BetweenCellToolbar extends CellPart {
actionViewItemProvider: action => {
if (action instanceof MenuItemAction) {
if (this._notebookEditor.notebookOptions.getLayoutConfiguration().insertToolbarAlignment === 'center') {
- return instantiationService.createInstance(CodiconActionViewItem, action);
+ return instantiationService.createInstance(CodiconActionViewItem, action, undefined);
} else {
return instantiationService.createInstance(MenuEntryActionViewItem, action, undefined);
}
diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts
index 0ce36982b24..f0eb46d08bb 100644
--- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts
+++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts
@@ -400,6 +400,10 @@ async function webviewPreloads(ctx: PreloadContext) {
function focusFirstFocusableInCell(cellId: string) {
const cellOutputContainer = document.getElementById(cellId);
if (cellOutputContainer) {
+ if (cellOutputContainer.contains(document.activeElement)) {
+ return;
+ }
+
const focusableElement = cellOutputContainer.querySelector('[tabindex="0"], [href], button, input, option, select, textarea') as HTMLElement | null;
focusableElement?.focus();
}
diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts
index 951fbe0e2e2..a024bbe93a6 100644
--- a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts
+++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl.ts
@@ -17,7 +17,7 @@ import { FindMatch, IModelDecorationOptions, IModelDeltaDecoration, TrackedRange
import { MultiModelEditStackElement, SingleModelEditStackElement } from 'vs/editor/common/model/editStack';
import { IntervalNode, IntervalTree } from 'vs/editor/common/model/intervalTree';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
-import { WorkspaceTextEdit } from 'vs/editor/common/languages';
+import { IWorkspaceTextEdit } from 'vs/editor/common/languages';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { FoldingRegions } from 'vs/editor/contrib/folding/browser/foldingRanges';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -924,14 +924,15 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD
return;
}
- const textEdits: WorkspaceTextEdit[] = [];
+ const textEdits: IWorkspaceTextEdit[] = [];
this._lastNotebookEditResource.push(matches[0].cell.uri);
matches.forEach(match => {
match.matches.forEach((singleMatch, index) => {
if ((singleMatch as OutputFindMatch).index === undefined) {
textEdits.push({
- edit: { range: (singleMatch as FindMatch).range, text: texts[index] },
+ versionId: undefined,
+ textEdit: { range: (singleMatch as FindMatch).range, text: texts[index] },
resource: match.cell.uri
});
}
diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts
index 1d1d38c2689..c80acf0d2ec 100644
--- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts
+++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts
@@ -69,7 +69,7 @@ class FixedLabelStrategy implements IActionLayoutStrategy {
const a = this.editorToolbar.primaryActions.find(a => a.action.id === action.id);
if (a && a.renderLabel) {
- return action instanceof MenuItemAction ? this.instantiationService.createInstance(ActionViewWithLabel, action) : undefined;
+ return action instanceof MenuItemAction ? this.instantiationService.createInstance(ActionViewWithLabel, action, undefined) : undefined;
} else {
return action instanceof MenuItemAction ? this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined) : undefined;
}
@@ -144,7 +144,7 @@ class DynamicLabelStrategy implements IActionLayoutStrategy {
const a = this.editorToolbar.primaryActions.find(a => a.action.id === action.id);
if (a && a.renderLabel) {
- return action instanceof MenuItemAction ? this.instantiationService.createInstance(ActionViewWithLabel, action) : undefined;
+ return action instanceof MenuItemAction ? this.instantiationService.createInstance(ActionViewWithLabel, action, undefined) : undefined;
} else {
return action instanceof MenuItemAction ? this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined) : undefined;
}
@@ -360,7 +360,7 @@ export class NotebookEditorToolbar extends Disposable {
if (this._renderLabel !== RenderLabel.Never) {
const a = this._primaryActions.find(a => a.action.id === action.id);
if (a && a.renderLabel) {
- return action instanceof MenuItemAction ? this.instantiationService.createInstance(ActionViewWithLabel, action) : undefined;
+ return action instanceof MenuItemAction ? this.instantiationService.createInstance(ActionViewWithLabel, action, undefined) : undefined;
} else {
return action instanceof MenuItemAction ? this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined) : undefined;
}
diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar.ts
index 92d6c18e45f..ab97127c986 100644
--- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar.ts
+++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar.ts
@@ -37,7 +37,7 @@ export class ListTopCellToolbar extends Disposable {
this.toolbar = this._register(new ToolBar(this.topCellToolbar, this.contextMenuService, {
actionViewItemProvider: action => {
if (action instanceof MenuItemAction) {
- const item = this.instantiationService.createInstance(CodiconActionViewItem, action);
+ const item = this.instantiationService.createInstance(CodiconActionViewItem, action, undefined);
return item;
}
diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts
index e590c5cddb9..7eca3e5a6cf 100644
--- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts
+++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts
@@ -17,7 +17,7 @@ import { ISplice } from 'vs/base/common/sequence';
import { URI, UriComponents } from 'vs/base/common/uri';
import { ILineChange } from 'vs/editor/common/diff/diffComputer';
import * as editorCommon from 'vs/editor/common/editorCommon';
-import { Command } from 'vs/editor/common/languages';
+import { Command, WorkspaceEditMetadata } from 'vs/editor/common/languages';
import { IReadonlyTextBuffer } from 'vs/editor/common/model';
import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility';
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
@@ -497,6 +497,14 @@ export interface ICellMoveEdit {
export type IImmediateCellEditOperation = ICellOutputEditByHandle | ICellPartialMetadataEditByHandle | ICellOutputItemEdit | ICellPartialInternalMetadataEdit | ICellPartialInternalMetadataEditByHandle | ICellPartialMetadataEdit;
export type ICellEditOperation = IImmediateCellEditOperation | ICellReplaceEdit | ICellOutputEdit | ICellMetadataEdit | ICellPartialMetadataEdit | ICellPartialInternalMetadataEdit | IDocumentMetadataEdit | ICellMoveEdit | ICellOutputItemEdit | ICellLanguageEdit;
+
+export interface IWorkspaceNotebookCellEdit {
+ metadata?: WorkspaceEditMetadata;
+ resource: URI;
+ notebookVersionId: number | undefined;
+ cellEdit: ICellPartialMetadataEdit | IDocumentMetadataEdit | ICellReplaceEdit;
+}
+
export interface NotebookData {
readonly cells: ICellDto2[];
readonly metadata: NotebookDocumentMetadata;
@@ -927,8 +935,7 @@ export const NotebookSetting = {
interactiveWindowCollapseCodeCells: 'interactiveWindow.collapseCellInputCode',
outputLineHeight: 'notebook.outputLineHeight',
outputFontSize: 'notebook.outputFontSize',
- outputFontFamily: 'notebook.outputFontFamily',
- interactiveWindowAlwaysScrollOnNewCell: 'interactiveWindow.alwaysScrollOnNewCell'
+ outputFontFamily: 'notebook.outputFontFamily'
} as const;
export const enum CellStatusbarAlignment {
diff --git a/src/vs/workbench/contrib/notebook/common/notebookOptions.ts b/src/vs/workbench/contrib/notebook/common/notebookOptions.ts
index 426871f84fa..2aa4ca14cf8 100644
--- a/src/vs/workbench/contrib/notebook/common/notebookOptions.ts
+++ b/src/vs/workbench/contrib/notebook/common/notebookOptions.ts
@@ -59,7 +59,7 @@ export interface NotebookLayoutConfiguration {
globalToolbar: boolean;
consolidatedOutputButton: boolean;
consolidatedRunButton: boolean;
- showFoldingControls: 'always' | 'mouseover';
+ showFoldingControls: 'always' | 'never' | 'mouseover';
dragAndDropEnabled: boolean;
fontSize: number;
outputFontSize: number;
@@ -385,7 +385,7 @@ export class NotebookOptions extends Disposable {
}
private _computeShowFoldingControlsOption() {
- return this.configurationService.getValue<'always' | 'mouseover'>(NotebookSetting.showFoldingControls) ?? 'mouseover';
+ return this.configurationService.getValue<'always' | 'never' | 'mouseover'>(NotebookSetting.showFoldingControls) ?? 'mouseover';
}
private _computeFocusIndicatorOption() {
diff --git a/src/vs/workbench/contrib/output/browser/outputServices.ts b/src/vs/workbench/contrib/output/browser/outputServices.ts
index c13ec6b5e0c..e9f97c01b29 100644
--- a/src/vs/workbench/contrib/output/browser/outputServices.ts
+++ b/src/vs/workbench/contrib/output/browser/outputServices.ts
@@ -48,7 +48,7 @@ class OutputChannel extends Disposable implements IOutputChannel {
}
update(mode: OutputChannelUpdateMode, till?: number): void {
- this.model.update(mode, till);
+ this.model.update(mode, till, true);
}
clear(): void {
diff --git a/src/vs/workbench/contrib/output/common/outputChannelModel.ts b/src/vs/workbench/contrib/output/common/outputChannelModel.ts
index 4c01e67797c..58f8374650b 100644
--- a/src/vs/workbench/contrib/output/common/outputChannelModel.ts
+++ b/src/vs/workbench/contrib/output/common/outputChannelModel.ts
@@ -26,7 +26,7 @@ import { OutputChannelUpdateMode } from 'vs/workbench/services/output/common/out
export interface IOutputChannelModel extends IDisposable {
readonly onDispose: Event<void>;
append(output: string): void;
- update(mode: OutputChannelUpdateMode, till?: number): void;
+ update(mode: OutputChannelUpdateMode, till: number | undefined, immediate: boolean): void;
loadModel(): Promise<ITextModel>;
clear(): void;
replace(value: string): void;
@@ -129,12 +129,12 @@ export class FileOutputChannelModel extends Disposable implements IOutputChannel
}
clear(): void {
- this.update(OutputChannelUpdateMode.Clear, this.endOffset);
+ this.update(OutputChannelUpdateMode.Clear, this.endOffset, true);
}
- update(mode: OutputChannelUpdateMode, till?: number): void {
+ update(mode: OutputChannelUpdateMode, till: number | undefined, immediate: boolean): void {
const loadModelPromise: Promise<any> = this.loadModelPromise ? this.loadModelPromise : Promise.resolve();
- loadModelPromise.then(() => this.doUpdate(mode, till));
+ loadModelPromise.then(() => this.doUpdate(mode, till, immediate));
}
loadModel(): Promise<ITextModel> {
@@ -174,7 +174,7 @@ export class FileOutputChannelModel extends Disposable implements IOutputChannel
return this.model;
}
- private doUpdate(mode: OutputChannelUpdateMode, till?: number): void {
+ private doUpdate(mode: OutputChannelUpdateMode, till: number | undefined, immediate: boolean): void {
if (mode === OutputChannelUpdateMode.Clear || mode === OutputChannelUpdateMode.Replace) {
this.startOffset = this.endOffset = isNumber(till) ? till : this.endOffset;
this.cancelModelUpdate();
@@ -198,7 +198,7 @@ export class FileOutputChannelModel extends Disposable implements IOutputChannel
}
else {
- this.appendContent(this.model, token);
+ this.appendContent(this.model, immediate, token);
}
}
@@ -206,7 +206,7 @@ export class FileOutputChannelModel extends Disposable implements IOutputChannel
this.doUpdateModel(model, [EditOperation.delete(model.getFullModelRange())], VSBuffer.fromString(''));
}
- private async appendContent(model: ITextModel, token: CancellationToken): Promise<void> {
+ private async appendContent(model: ITextModel, immediate: boolean, token: CancellationToken): Promise<void> {
this.appendThrottler.trigger(async () => {
/* Abort if operation is cancelled */
if (token.isCancellationRequested) {
@@ -234,7 +234,7 @@ export class FileOutputChannelModel extends Disposable implements IOutputChannel
const lastLineMaxColumn = model.getLineMaxColumn(lastLine);
const edits = [EditOperation.insert(new Position(lastLine, lastLineMaxColumn), contentToAppend.toString())];
this.doUpdateModel(model, edits, contentToAppend);
- });
+ }, immediate ? 0 : undefined);
}
private async replaceContent(model: ITextModel, token: CancellationToken): Promise<void> {
@@ -298,10 +298,10 @@ export class FileOutputChannelModel extends Disposable implements IOutputChannel
if (!this.modelUpdateInProgress) {
if (isNumber(size) && this.endOffset > size) {
// Reset - Content is removed
- this.update(OutputChannelUpdateMode.Clear, 0);
+ this.update(OutputChannelUpdateMode.Clear, 0, true);
}
}
- this.update(OutputChannelUpdateMode.Append);
+ this.update(OutputChannelUpdateMode.Append, undefined, false /* Not needed to update immediately. Wait to collect more changes and update. */);
}
}
@@ -340,13 +340,13 @@ class OutputChannelBackedByFile extends FileOutputChannelModel implements IOutpu
override append(message: string): void {
this.write(message);
- this.update(OutputChannelUpdateMode.Append);
+ this.update(OutputChannelUpdateMode.Append, undefined, this.isVisible());
}
override replace(message: string): void {
const till = this._offset;
this.write(message);
- this.update(OutputChannelUpdateMode.Replace, till);
+ this.update(OutputChannelUpdateMode.Replace, till, true);
}
private write(content: string): void {
@@ -391,8 +391,8 @@ export class DelegatedOutputChannelModel extends Disposable implements IOutputCh
this.outputChannelModel.then(outputChannelModel => outputChannelModel.append(output));
}
- update(mode: OutputChannelUpdateMode, till?: number): void {
- this.outputChannelModel.then(outputChannelModel => outputChannelModel.update(mode, till));
+ update(mode: OutputChannelUpdateMode, till: number | undefined, immediate: boolean): void {
+ this.outputChannelModel.then(outputChannelModel => outputChannelModel.update(mode, till, immediate));
}
loadModel(): Promise<ITextModel> {
diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css
index d44eec7e5d5..11f242b2646 100644
--- a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css
+++ b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css
@@ -351,7 +351,7 @@
font-style: italic;
}
-.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-title > .misc-label .setting-item-overrides:hover,
+.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-title > .misc-label .setting-item-overrides.with-custom-hover:hover,
.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-title > .misc-label .setting-item-ignored:hover,
.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-title > .misc-label .setting-item-default-overridden:hover {
text-decoration: underline;
diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts b/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts
index 8f833f178f6..2c59b8bfc44 100644
--- a/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts
+++ b/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts
@@ -554,7 +554,7 @@ export class SettingMatches {
// Trim excess ending characters off the query.
singleWordQuery = singleWordQuery.toLowerCase().replace(/[\s-\._]+$/, '');
lineToSearch = lineToSearch.toLowerCase();
- const singleWordRegex = new RegExp(`\\b${singleWordQuery}\\b`);
+ const singleWordRegex = new RegExp(`\\b${strings.escapeRegExpCharacters(singleWordQuery)}\\b`);
if (singleWordRegex.test(lineToSearch)) {
this.matchType |= SettingMatchType.WholeWordMatch;
}
diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts
index a10179c4ac7..ae71ad571f6 100644
--- a/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts
+++ b/src/vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators.ts
@@ -51,12 +51,6 @@ export class SettingsTreeIndicatorsLabel implements IDisposable {
this.indicatorsContainerElement = DOM.append(container, $('.misc-label'));
this.indicatorsContainerElement.style.display = 'inline';
- const scopeOverridesIndicator = this.createScopeOverridesIndicator();
- this.scopeOverridesElement = scopeOverridesIndicator.element;
- this.scopeOverridesLabel = scopeOverridesIndicator.label;
- this.syncIgnoredElement = this.createSyncIgnoredElement();
- this.defaultOverrideIndicatorElement = this.createDefaultOverrideIndicator();
-
this.hoverDelegate = {
showHover: (options: IHoverDelegateOptions, focus?: boolean) => {
return hoverService.showHover(options, focus);
@@ -65,6 +59,12 @@ export class SettingsTreeIndicatorsLabel implements IDisposable {
delay: configurationService.getValue<number>('workbench.hover.delay'),
placement: 'element'
};
+
+ const scopeOverridesIndicator = this.createScopeOverridesIndicator();
+ this.scopeOverridesElement = scopeOverridesIndicator.element;
+ this.scopeOverridesLabel = scopeOverridesIndicator.label;
+ this.syncIgnoredElement = this.createSyncIgnoredElement();
+ this.defaultOverrideIndicatorElement = this.createDefaultOverrideIndicator();
}
private createScopeOverridesIndicator(): { element: HTMLElement; label: SimpleIconLabel } {
@@ -139,6 +139,7 @@ export class SettingsTreeIndicatorsLabel implements IDisposable {
// Render inline if we have the flag and there are scope overrides to render,
// or if there is only one scope override to render and no language overrides.
this.scopeOverridesElement.style.display = 'inline';
+ this.scopeOverridesElement.classList.remove('with-custom-hover');
this.hover?.dispose();
// Just show all the text in the label.
@@ -170,6 +171,7 @@ export class SettingsTreeIndicatorsLabel implements IDisposable {
// show the text in a custom hover only if
// the feature flag isn't on.
this.scopeOverridesElement.style.display = 'inline';
+ this.scopeOverridesElement.classList.add('with-custom-hover');
const scopeOverridesLabelText = element.isConfigured ?
localize('alsoConfiguredElsewhere', "Also modified elsewhere") :
localize('configuredElsewhere', "Modified elsewhere");
diff --git a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts
index 148c6c8d379..1680881aeb1 100644
--- a/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts
+++ b/src/vs/workbench/contrib/relauncher/browser/relauncher.contribution.ts
@@ -26,7 +26,7 @@ interface IConfiguration extends IWindowsConfiguration {
debug?: { console?: { wordWrap?: boolean } };
editor?: { accessibilitySupport?: 'on' | 'off' | 'auto' };
security?: { workspace?: { trust?: { enabled?: boolean } } };
- window: IWindowSettings & { experimental?: { windowControlsOverlay?: { enabled?: boolean } } };
+ window: IWindowSettings & { experimental?: { windowControlsOverlay?: { enabled?: boolean }; useSandbox?: boolean } };
workbench?: { experimental?: { settingsProfiles?: { enabled?: boolean } } };
}
@@ -34,6 +34,7 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo
private titleBarStyle: 'native' | 'custom' | undefined;
private windowControlsOverlayEnabled: boolean | undefined;
+ private windowSandboxEnabled: boolean | undefined;
private nativeTabs: boolean | undefined;
private nativeFullScreen: boolean | undefined;
private clickThroughInactive: boolean | undefined;
@@ -66,11 +67,16 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo
}
// Windows: Window Controls Overlay
- if (isWindows && typeof config.window?.experimental?.windowControlsOverlay?.enabled === 'boolean' && config.window?.experimental?.windowControlsOverlay?.enabled !== this.windowControlsOverlayEnabled) {
+ if (isWindows && typeof config.window?.experimental?.windowControlsOverlay?.enabled === 'boolean' && config.window.experimental.windowControlsOverlay.enabled !== this.windowControlsOverlayEnabled) {
this.windowControlsOverlayEnabled = config.window.experimental.windowControlsOverlay.enabled;
changed = true;
}
+ // Windows: Sandbox
+ if (typeof config.window?.experimental?.useSandbox === 'boolean' && config.window.experimental.useSandbox !== this.windowSandboxEnabled) {
+ this.windowSandboxEnabled = config.window.experimental.useSandbox;
+ changed = true;
+ }
// macOS: Native tabs
if (isMacintosh && typeof config.window?.nativeTabs === 'boolean' && config.window.nativeTabs !== this.nativeTabs) {
diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts
index b6488780711..0b2e94b01bc 100644
--- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts
+++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts
@@ -353,7 +353,7 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
},
'remote.autoForwardPortsSource': {
type: 'string',
- markdownDescription: localize('remote.autoForwardPortsSource', "Sets the source from which ports are automatically forwarded when `remote.autoForwardPorts` is true. On Windows and Mac remotes, the `process` option has no effect and `output` will be used. Requires a reload to take effect."),
+ markdownDescription: localize('remote.autoForwardPortsSource', "Sets the source from which ports are automatically forwarded when {0} is true. On Windows and Mac remotes, the `process` option has no effect and `output` will be used. Requires a reload to take effect.", '`#remote.autoForwardPorts#`'),
enum: ['process', 'output'],
enumDescriptions: [
localize('remote.autoForwardPortsSource.process', "Ports will be automatically forwarded when discovered by watching for processes that are started and include a port."),
@@ -463,7 +463,7 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
}
},
defaultSnippets: [{ body: { onAutoForward: 'ignore' } }],
- markdownDescription: localize('remote.portsAttributes.defaults', "Set default properties that are applied to all ports that don't get properties from the setting `remote.portsAttributes`. For example:\n\n```\n{\n \"onAutoForward\": \"ignore\"\n}\n```"),
+ markdownDescription: localize('remote.portsAttributes.defaults', "Set default properties that are applied to all ports that don't get properties from the setting {0}. For example:\n\n```\n{\n \"onAutoForward\": \"ignore\"\n}\n```", '`#remote.portsAttributes#`'),
additionalProperties: false
},
'remote.localPortHost': {
diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts
index 1a733fb7680..56be4b4065f 100644
--- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts
+++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts
@@ -2400,6 +2400,7 @@ export class SCMViewPane extends ViewPane {
if (widget) {
widget.focus();
+ this.tree.setFocus([], e.browserEvent);
const selection = this.tree.getSelection();
@@ -2411,7 +2412,13 @@ export class SCMViewPane extends ViewPane {
return;
} else if (isSCMActionButton(e.element)) {
this.scmViewService.focus(e.element.repository);
- this.actionButtonRenderer.focusActionButton(e.element);
+
+ // Focus the action button
+ const target = e.browserEvent?.target as HTMLElement;
+ if (target.classList.contains('monaco-tl-row') || target.classList.contains('button-container')) {
+ this.actionButtonRenderer.focusActionButton(e.element);
+ this.tree.setFocus([], e.browserEvent);
+ }
return;
}
@@ -2637,19 +2644,11 @@ export class SCMActionButton implements IDisposable {
return;
}
- const executeButtonAction = async (commandId: string, ...args: any[]) => {
- try {
- await this.commandService.executeCommand(commandId, ...args);
- } catch (ex) {
- this.notificationService.error(ex);
- }
- };
-
if (button.secondaryCommands?.length) {
const actions: IAction[] = [];
for (let index = 0; index < button.secondaryCommands.length; index++) {
for (const command of button.secondaryCommands[index]) {
- actions.push(new Action(command.id, command.title, undefined, true, async () => await executeButtonAction(command.id, ...(command.arguments || []))));
+ actions.push(new Action(command.id, command.title, undefined, true, async () => await this.executeCommand(command.id, ...(command.arguments || []))));
}
if (index !== button.secondaryCommands.length - 1) {
actions.push(new Separator());
@@ -2661,6 +2660,7 @@ export class SCMActionButton implements IDisposable {
actions: actions,
addPrimaryActionToDropdown: false,
contextMenuProvider: this.contextMenuService,
+ title: button.command.tooltip,
supportIcons: true
});
} else if (button.description) {
@@ -2674,8 +2674,7 @@ export class SCMActionButton implements IDisposable {
this.button.enabled = button.enabled;
this.button.label = button.command.title;
- this.button.element.title = button.command.tooltip ?? '';
- this.button.onDidClick(async () => await executeButtonAction(button.command.id, ...(button.command.arguments || [])), null, this.disposables.value);
+ this.button.onDidClick(async () => await this.executeCommand(button.command.id, ...(button.command.arguments || [])), null, this.disposables.value);
this.disposables.value!.add(this.button);
this.disposables.value!.add(attachButtonStyler(this.button, this.themeService));
@@ -2690,4 +2689,12 @@ export class SCMActionButton implements IDisposable {
this.button = undefined;
clearNode(this.container);
}
+
+ private async executeCommand(commandId: string, ...args: any[]): Promise<void> {
+ try {
+ await this.commandService.executeCommand(commandId, ...args);
+ } catch (ex) {
+ this.notificationService.error(ex);
+ }
+ }
}
diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts
index 77782e09059..099169c6ab3 100644
--- a/src/vs/workbench/contrib/search/browser/search.contribution.ts
+++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts
@@ -365,7 +365,10 @@ registerAction2(class CancelSearchAction extends Action2 {
constructor() {
super({
id: 'search.action.cancel',
- title: nls.localize('CancelSearchAction.label', "Cancel Search"),
+ title: {
+ value: nls.localize('CancelSearchAction.label', "Cancel Search"),
+ original: 'Cancel Search'
+ },
icon: searchStopIcon,
category,
f1: true,
@@ -392,7 +395,10 @@ registerAction2(class RefreshAction extends Action2 {
constructor() {
super({
id: 'search.action.refreshSearchResults',
- title: nls.localize('RefreshAction.label', "Refresh"),
+ title: {
+ value: nls.localize('RefreshAction.label', "Refresh"),
+ original: 'Refresh'
+ },
icon: searchRefreshIcon,
precondition: Constants.ViewHasSearchPatternKey,
category,
@@ -414,7 +420,10 @@ registerAction2(class CollapseDeepestExpandedLevelAction extends Action2 {
constructor() {
super({
id: 'search.action.collapseSearchResults',
- title: nls.localize('CollapseDeepestExpandedLevelAction.label', "Collapse All"),
+ title: {
+ value: nls.localize('CollapseDeepestExpandedLevelAction.label', "Collapse All"),
+ original: 'Collapse All'
+ },
category,
icon: searchCollapseAllIcon,
f1: true,
@@ -436,7 +445,10 @@ registerAction2(class ExpandAllAction extends Action2 {
constructor() {
super({
id: 'search.action.expandSearchResults',
- title: nls.localize('ExpandAllAction.label', "Expand All"),
+ title: {
+ value: nls.localize('ExpandAllAction.label', "Expand All"),
+ original: 'Expand All'
+ },
category,
icon: searchExpandAllIcon,
f1: true,
@@ -458,7 +470,10 @@ registerAction2(class ClearSearchResultsAction extends Action2 {
constructor() {
super({
id: 'search.action.clearSearchResults',
- title: nls.localize('ClearSearchResultsAction.label', "Clear Search Results"),
+ title: {
+ value: nls.localize('ClearSearchResultsAction.label', "Clear Search Results"),
+ original: 'Clear Search Results'
+ },
category,
icon: searchClearIcon,
f1: true,
@@ -829,7 +844,7 @@ configurationRegistry.registerConfiguration({
type: 'string', // expression ({ "**/*.js": { "when": "$(basename).js" } })
pattern: '\\w*\\$\\(basename\\)\\w*',
default: '$(basename).ext',
- markdownDescription: nls.localize('exclude.when', 'Additional check on the siblings of a matching file. Use \\$(basename) as variable for the matching file name.')
+ markdownDescription: nls.localize({ key: 'exclude.when', comment: ['\\$(basename) should not be translated'] }, 'Additional check on the siblings of a matching file. Use \\$(basename) as variable for the matching file name.')
}
}
}
@@ -981,7 +996,7 @@ configurationRegistry.registerConfiguration({
'search.searchOnTypeDebouncePeriod': {
type: 'number',
default: 300,
- markdownDescription: nls.localize('search.searchOnTypeDebouncePeriod', "When `#search.searchOnType#` is enabled, controls the timeout in milliseconds between a character being typed and the search starting. Has no effect when `search.searchOnType` is disabled.")
+ markdownDescription: nls.localize('search.searchOnTypeDebouncePeriod', "When {0} is enabled, controls the timeout in milliseconds between a character being typed and the search starting. Has no effect when {0} is disabled.", '`#search.searchOnType#`')
},
'search.searchEditor.doubleClickBehaviour': {
type: 'string',
diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts
index ed23a5f2632..a75b4184bd2 100644
--- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts
+++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts
@@ -41,6 +41,8 @@ import { Disposable } from 'vs/base/common/lifecycle';
const OpenInEditorCommandId = 'search.action.openInEditor';
const OpenNewEditorToSideCommandId = 'search.action.openNewEditorToSide';
const FocusQueryEditorWidgetCommandId = 'search.action.focusQueryEditorWidget';
+const FocusQueryEditorFilesToIncludeCommandId = 'search.action.focusFilesToInclude';
+const FocusQueryEditorFilesToExcludeCommandId = 'search.action.focusFilesToExclude';
const ToggleSearchEditorCaseSensitiveCommandId = 'toggleSearchEditorCaseSensitive';
const ToggleSearchEditorWholeWordCommandId = 'toggleSearchEditorWholeWord';
@@ -377,6 +379,44 @@ registerAction2(class extends Action2 {
registerAction2(class extends Action2 {
constructor() {
super({
+ id: FocusQueryEditorFilesToIncludeCommandId,
+ title: { value: localize('search.action.focusFilesToInclude', "Focus Search Editor Files to Include"), original: 'Focus Search Editor Files to Include' },
+ category,
+ f1: true,
+ precondition: SearchEditorConstants.InSearchEditor,
+ });
+ }
+ async run(accessor: ServicesAccessor) {
+ const editorService = accessor.get(IEditorService);
+ const input = editorService.activeEditor;
+ if (input instanceof SearchEditorInput) {
+ (editorService.activeEditorPane as SearchEditor).focusFilesToIncludeInput();
+ }
+ }
+});
+
+registerAction2(class extends Action2 {
+ constructor() {
+ super({
+ id: FocusQueryEditorFilesToExcludeCommandId,
+ title: { value: localize('search.action.focusFilesToExclude', "Focus Search Editor Files to Exclude"), original: 'Focus Search Editor Files to Exclude' },
+ category,
+ f1: true,
+ precondition: SearchEditorConstants.InSearchEditor,
+ });
+ }
+ async run(accessor: ServicesAccessor) {
+ const editorService = accessor.get(IEditorService);
+ const input = editorService.activeEditor;
+ if (input instanceof SearchEditorInput) {
+ (editorService.activeEditorPane as SearchEditor).focusFilesToExcludeInput();
+ }
+ }
+});
+
+registerAction2(class extends Action2 {
+ constructor() {
+ super({
id: ToggleSearchEditorCaseSensitiveCommandId,
title: { value: localize('searchEditor.action.toggleSearchEditorCaseSensitive', "Toggle Match Case"), original: 'Toggle Match Case' },
category,
diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts
index 380f8dbcc8f..54be4156b6e 100644
--- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts
+++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.ts
@@ -275,6 +275,20 @@ export class SearchEditor extends AbstractTextCodeEditor<SearchEditorViewState>
this.queryEditorWidget.searchInput.focus();
}
+ focusFilesToIncludeInput() {
+ if (!this.showingIncludesExcludes) {
+ this.toggleIncludesExcludes(true);
+ }
+ this.inputPatternIncludes.focus();
+ }
+
+ focusFilesToExcludeInput() {
+ if (!this.showingIncludesExcludes) {
+ this.toggleIncludesExcludes(true);
+ }
+ this.inputPatternExcludes.focus();
+ }
+
focusNextInput() {
if (this.queryEditorWidget.searchInputHasFocus()) {
if (this.showingIncludesExcludes) {
diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts b/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts
index eb1497201da..784296b70de 100644
--- a/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts
+++ b/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts
@@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import { parse as jsonParse, getNodeType } from 'vs/base/common/json';
-import { forEach } from 'vs/base/common/collections';
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';
@@ -256,17 +255,15 @@ export class SnippetFile {
this._loadPromise = Promise.resolve(this._load()).then(content => {
const data = <JsonSerializedSnippets>jsonParse(content);
if (getNodeType(data) === 'object') {
- forEach(data, entry => {
- const { key: name, value: scopeOrTemplate } = entry;
+ for (const [name, scopeOrTemplate] of Object.entries(data)) {
if (isJsonSerializedSnippet(scopeOrTemplate)) {
this._parseSnippet(name, scopeOrTemplate, this.data);
} else {
- forEach(scopeOrTemplate, entry => {
- const { key: name, value: template } = entry;
+ for (const [name, template] of Object.entries(scopeOrTemplate)) {
this._parseSnippet(name, template, this.data);
- });
+ }
}
- });
+ }
}
return this;
});
diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts
index 366ed64536b..abc007e863d 100644
--- a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts
+++ b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts
@@ -358,7 +358,12 @@ class SnippetsService implements ISnippetsService {
await this._initFolderSnippets(SnippetSource.User, userSnippetsFolder, disposables);
};
this._disposables.add(disposables);
- this._disposables.add(this._userDataProfileService.onDidChangeCurrentProfile(() => this._pendingWork.push(updateUserSnippets())));
+ this._disposables.add(this._userDataProfileService.onDidChangeCurrentProfile(e => e.join((async () => {
+ if (e.preserveData) {
+ await this._fileService.copy(e.previous.snippetsHome, e.profile.snippetsHome);
+ }
+ this._pendingWork.push(updateUserSnippets());
+ })())));
await updateUserSnippets();
}
diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts
index 9ef8982eebe..93cbe879116 100644
--- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts
+++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts
@@ -53,7 +53,9 @@ import {
ITaskSet, TaskGroup, ExecutionEngine, JsonSchemaVersion, TaskSourceKind,
TaskSorter, ITaskIdentifier, TASK_RUNNING_STATE, TaskRunSource,
KeyedTaskIdentifier as KeyedTaskIdentifier, TaskDefinition, RuntimeType,
- USER_TASKS_GROUP_KEY
+ USER_TASKS_GROUP_KEY,
+ 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 { getTemplates as getTaskTemplates } from 'vs/workbench/contrib/tasks/common/taskTemplates';
@@ -1082,7 +1084,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
}).then((value) => {
if (runSource === TaskRunSource.User) {
this.getWorkspaceTasks().then(workspaceTasks => {
- RunAutomaticTasks.promptForPermission(this, this._storageService, this._notificationService, this._workspaceTrustManagementService, this._openerService, workspaceTasks);
+ RunAutomaticTasks.promptForPermission(this, this._storageService, this._notificationService, this._workspaceTrustManagementService, this._openerService, this._configurationService, workspaceTasks);
});
}
return value;
@@ -1093,7 +1095,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
}
private _isProvideTasksEnabled(): boolean {
- const settingValue = this._configurationService.getValue('task.autoDetect');
+ const settingValue = this._configurationService.getValue(TaskSettingId.AutoDetect);
return settingValue === 'on';
}
@@ -1588,7 +1590,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
{
identifier: id,
dependsOn: extensionTasks.map((extensionTask) => { return { uri: extensionTask.getWorkspaceFolder()!.uri, task: extensionTask._id }; }),
- name: id,
+ name: id
}
);
return { task, resolver };
@@ -1688,7 +1690,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
Prompt = 'prompt'
}
- const saveBeforeRunTaskConfig: SaveBeforeRunConfigOptions = this._configurationService.getValue('task.saveBeforeRun');
+ const saveBeforeRunTaskConfig: SaveBeforeRunConfigOptions = this._configurationService.getValue(TaskSettingId.SaveBeforeRun);
if (saveBeforeRunTaskConfig === SaveBeforeRunConfigOptions.Never) {
return false;
@@ -2738,7 +2740,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
}
this._showQuickPick(tasks ? tasks : taskResult!.tasks, placeholder,
{
- label: nls.localize('TaskService.noEntryToRunSlow', '$(plus) Configure a Task'),
+ label: '$(plus) ' + nls.localize('TaskService.noEntryToRun', 'Configure a Task'),
task: null
},
true).
@@ -2748,7 +2750,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
} else {
this._showTwoLevelQuickPick(placeholder,
{
- label: nls.localize('TaskService.noEntryToRun', '$(plus) Configure a Task'),
+ label: '$(plus) ' + nls.localize('TaskService.noEntryToRun', 'Configure a Task'),
task: null
}).
then(pickThen);
@@ -3523,11 +3525,11 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
}
const configTasks: (TaskConfig.ICustomTask | TaskConfig.IConfiguringTask)[] = [];
- const suppressTaskName = !!this._configurationService.getValue('tasks.suppressTaskName', { resource: folder.uri });
+ const suppressTaskName = !!this._configurationService.getValue(TasksSchemaProperties.SuppressTaskName, { resource: folder.uri });
const globalConfig = {
- windows: <ICommandUpgrade>this._configurationService.getValue('tasks.windows', { resource: folder.uri }),
- osx: <ICommandUpgrade>this._configurationService.getValue('tasks.osx', { resource: folder.uri }),
- linux: <ICommandUpgrade>this._configurationService.getValue('tasks.linux', { resource: folder.uri })
+ windows: <ICommandUpgrade>this._configurationService.getValue(TasksSchemaProperties.Windows, { resource: folder.uri }),
+ osx: <ICommandUpgrade>this._configurationService.getValue(TasksSchemaProperties.Osx, { resource: folder.uri }),
+ linux: <ICommandUpgrade>this._configurationService.getValue(TasksSchemaProperties.Linux, { resource: folder.uri })
};
tasks.get(folder).forEach(task => {
const configTask = this._upgradeTask(task, suppressTaskName, globalConfig);
@@ -3539,14 +3541,14 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
this._workspaceTasksPromise = undefined;
await this._writeConfiguration(folder, 'tasks.tasks', configTasks);
await this._writeConfiguration(folder, 'tasks.version', '2.0.0');
- if (this._configurationService.getValue('tasks.showOutput', { resource: folder.uri })) {
- await this._configurationService.updateValue('tasks.showOutput', undefined, { resource: folder.uri });
+ if (this._configurationService.getValue(TasksSchemaProperties.ShowOutput, { resource: folder.uri })) {
+ await this._configurationService.updateValue(TasksSchemaProperties.ShowOutput, undefined, { resource: folder.uri });
}
- if (this._configurationService.getValue('tasks.isShellCommand', { resource: folder.uri })) {
- await this._configurationService.updateValue('tasks.isShellCommand', undefined, { resource: folder.uri });
+ if (this._configurationService.getValue(TasksSchemaProperties.IsShellCommand, { resource: folder.uri })) {
+ await this._configurationService.updateValue(TasksSchemaProperties.IsShellCommand, undefined, { resource: folder.uri });
}
- if (this._configurationService.getValue('tasks.suppressTaskName', { resource: folder.uri })) {
- await this._configurationService.updateValue('tasks.suppressTaskName', undefined, { resource: folder.uri });
+ if (this._configurationService.getValue(TasksSchemaProperties.SuppressTaskName, { resource: folder.uri })) {
+ await this._configurationService.updateValue(TasksSchemaProperties.SuppressTaskName, undefined, { resource: folder.uri });
}
}
this._updateSetup();
diff --git a/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts b/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts
index 3d91091af90..aa0d0441b79 100644
--- a/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts
+++ b/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts
@@ -8,7 +8,6 @@ import * as resources from 'vs/base/common/resources';
import { Disposable } from 'vs/base/common/lifecycle';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { ITaskService, IWorkspaceFolderTaskResult } from 'vs/workbench/contrib/tasks/common/taskService';
-import { forEach } from 'vs/base/common/collections';
import { RunOnOptions, Task, TaskRunSource, TaskSource, TaskSourceKind, TASKS_CATEGORY, WorkspaceFileTaskSource, IWorkspaceTaskSource } from 'vs/workbench/contrib/tasks/common/tasks';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
@@ -16,18 +15,19 @@ import { IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/commo
import { Action2 } from 'vs/platform/actions/common/actions';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust';
-import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
+import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { URI } from 'vs/base/common/uri';
import { Event } from 'vs/base/common/event';
import { ILogService } from 'vs/platform/log/common/log';
-const ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE = 'tasks.run.allowAutomatic';
+const HAS_PROMPTED_FOR_AUTOMATIC_TASKS = 'task.hasPromptedForAutomaticTasks';
+const ALLOW_AUTOMATIC_TASKS = 'task.allowAutomaticTasks';
export class RunAutomaticTasks extends Disposable implements IWorkbenchContribution {
constructor(
@ITaskService private readonly _taskService: ITaskService,
- @IStorageService private readonly _storageService: IStorageService,
+ @IConfigurationService private readonly _configurationService: IConfigurationService,
@IWorkspaceTrustManagementService private readonly _workspaceTrustManagementService: IWorkspaceTrustManagementService,
@ILogService private readonly _logService: ILogService) {
super();
@@ -43,7 +43,7 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut
}
this._logService.trace('RunAutomaticTasks: Checking if automatic tasks should run.');
- const isFolderAutomaticAllowed = this._storageService.getBoolean(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, StorageScope.WORKSPACE, undefined);
+ const isFolderAutomaticAllowed = this._configurationService.getValue(ALLOW_AUTOMATIC_TASKS) !== 'off';
await this._workspaceTrustManagementService.workspaceTrustInitialized;
const isWorkspaceTrusted = this._workspaceTrustManagementService.isWorkspaceTrusted();
// Only run if allowed. Prompting for permission occurs when a user first tries to run a task.
@@ -106,22 +106,22 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut
});
}
if (resultElement.configurations) {
- forEach(resultElement.configurations.byIdentifier, (configedTask) => {
- if (configedTask.value.runOptions.runOn === RunOnOptions.folderOpen) {
+ for (const configuredTask of Object.values(resultElement.configurations.byIdentifier)) {
+ if (configuredTask.runOptions.runOn === RunOnOptions.folderOpen) {
tasks.push(new Promise<Task | undefined>(resolve => {
- taskService.getTask(resultElement.workspaceFolder, configedTask.value._id, true).then(task => resolve(task));
+ taskService.getTask(resultElement.workspaceFolder, configuredTask._id, true).then(task => resolve(task));
}));
- if (configedTask.value._label) {
- taskNames.push(configedTask.value._label);
+ if (configuredTask._label) {
+ taskNames.push(configuredTask._label);
} else {
- taskNames.push(configedTask.value.configures.task);
+ taskNames.push(configuredTask.configures.task);
}
- const location = RunAutomaticTasks._getTaskSource(configedTask.value._source);
+ const location = RunAutomaticTasks._getTaskSource(configuredTask._source);
if (location) {
locations.set(location.fsPath, location);
}
}
- });
+ }
}
});
}
@@ -129,30 +129,33 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut
}
public static async promptForPermission(taskService: ITaskService, storageService: IStorageService, notificationService: INotificationService, workspaceTrustManagementService: IWorkspaceTrustManagementService,
- openerService: IOpenerService, workspaceTaskResult: Map<string, IWorkspaceFolderTaskResult>) {
+ openerService: IOpenerService, configurationService: IConfigurationService, workspaceTaskResult: Map<string, IWorkspaceFolderTaskResult>) {
const isWorkspaceTrusted = workspaceTrustManagementService.isWorkspaceTrusted;
if (!isWorkspaceTrusted) {
return;
}
-
- const isFolderAutomaticAllowed = storageService.getBoolean(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, StorageScope.WORKSPACE, undefined);
- if (isFolderAutomaticAllowed !== undefined) {
+ if (configurationService.getValue(ALLOW_AUTOMATIC_TASKS) === 'off') {
return;
}
+ const hasShownPromptForAutomaticTasks = storageService.getBoolean(HAS_PROMPTED_FOR_AUTOMATIC_TASKS, StorageScope.WORKSPACE, undefined);
const { tasks, taskNames, locations } = RunAutomaticTasks._findAutoTasks(taskService, workspaceTaskResult);
if (taskNames.length > 0) {
- // We have automatic tasks, prompt to allow.
- this._showPrompt(notificationService, storageService, taskService, openerService, taskNames, locations).then(allow => {
- if (allow) {
- RunAutomaticTasks._runTasks(taskService, tasks);
- }
- });
+ if (configurationService.getValue(ALLOW_AUTOMATIC_TASKS) === 'on') {
+ RunAutomaticTasks._runTasks(taskService, tasks);
+ } else if (!hasShownPromptForAutomaticTasks) {
+ // We have automatic tasks, prompt to allow.
+ this._showPrompt(notificationService, storageService, openerService, configurationService, taskNames, locations).then(allow => {
+ if (allow) {
+ RunAutomaticTasks._runTasks(taskService, tasks);
+ }
+ });
+ }
}
}
- private static _showPrompt(notificationService: INotificationService, storageService: IStorageService, taskService: ITaskService,
- openerService: IOpenerService, taskNames: Array<string>, locations: Map<string, URI>): Promise<boolean> {
+ private static _showPrompt(notificationService: INotificationService, storageService: IStorageService,
+ openerService: IOpenerService, configurationService: IConfigurationService, taskNames: Array<string>, locations: Map<string, URI>): Promise<boolean> {
return new Promise<boolean>(resolve => {
notificationService.prompt(Severity.Info, nls.localize('tasks.run.allowAutomatic',
"This workspace has tasks ({0}) defined ({1}) that run automatically when you open this workspace. Do you allow automatic tasks to run when you open this workspace?",
@@ -163,14 +166,15 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut
label: nls.localize('allow', "Allow and run"),
run: () => {
resolve(true);
- storageService.store(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, true, StorageScope.WORKSPACE, StorageTarget.MACHINE);
+ configurationService.updateValue(ALLOW_AUTOMATIC_TASKS, true, ConfigurationTarget.WORKSPACE);
}
},
{
label: nls.localize('disallow', "Disallow"),
run: () => {
resolve(false);
- storageService.store(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, false, StorageScope.WORKSPACE, StorageTarget.MACHINE);
+ configurationService.updateValue(ALLOW_AUTOMATIC_TASKS, false, ConfigurationTarget.WORKSPACE);
+
}
},
{
@@ -183,9 +187,9 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut
}
}]
);
+ storageService.store(HAS_PROMPTED_FOR_AUTOMATIC_TASKS, true, StorageScope.WORKSPACE, StorageTarget.MACHINE);
});
}
-
}
export class ManageAutomaticTaskRunning extends Action2 {
@@ -203,14 +207,13 @@ export class ManageAutomaticTaskRunning extends Action2 {
public async run(accessor: ServicesAccessor): Promise<any> {
const quickInputService = accessor.get(IQuickInputService);
- const storageService = accessor.get(IStorageService);
+ const configurationService = accessor.get(IConfigurationService);
const allowItem: IQuickPickItem = { label: nls.localize('workbench.action.tasks.allowAutomaticTasks', "Allow Automatic Tasks in Folder") };
const disallowItem: IQuickPickItem = { label: nls.localize('workbench.action.tasks.disallowAutomaticTasks', "Disallow Automatic Tasks in Folder") };
const value = await quickInputService.pick([allowItem, disallowItem], { canPickMany: false });
if (!value) {
return;
}
-
- storageService.store(ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE, value === allowItem, StorageScope.WORKSPACE, StorageTarget.MACHINE);
+ configurationService.updateValue(ALLOW_AUTOMATIC_TASKS, value === allowItem, ConfigurationTarget.WORKSPACE);
}
}
diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts
index eff66ddba5f..00c85294b7b 100644
--- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts
+++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts
@@ -20,7 +20,7 @@ import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatus
import { IOutputChannelRegistry, Extensions as OutputExt } from 'vs/workbench/services/output/common/output';
-import { ITaskEvent, TaskEventKind, TaskGroup, TASKS_CATEGORY, TASK_RUNNING_STATE } from 'vs/workbench/contrib/tasks/common/tasks';
+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 { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution } from 'vs/workbench/common/contributions';
@@ -431,7 +431,7 @@ configurationRegistry.registerConfiguration({
title: nls.localize('tasksConfigurationTitle', "Tasks"),
type: 'object',
properties: {
- 'task.problemMatchers.neverPrompt': {
+ [TaskSettingId.ProblemMatchersNeverPrompt]: {
markdownDescription: nls.localize('task.problemMatchers.neverPrompt', "Configures whether to show the problem matcher prompt when running a task. Set to `true` to never prompt, or use a dictionary of task types to turn off prompting only for specific task types."),
'oneOf': [
{
@@ -453,13 +453,13 @@ configurationRegistry.registerConfiguration({
],
default: false
},
- 'task.autoDetect': {
+ [TaskSettingId.AutoDetect]: {
markdownDescription: nls.localize('task.autoDetect', "Controls enablement of `provideTasks` for all task provider extension. If the Tasks: Run Task command is slow, disabling auto detect for task providers may help. Individual extensions may also provide settings that disable auto detection."),
type: 'string',
enum: ['on', 'off'],
default: 'on'
},
- 'task.slowProviderWarning': {
+ [TaskSettingId.SlowProviderWarning]: {
markdownDescription: nls.localize('task.slowProviderWarning', "Configures whether a warning is shown when a provider is slow"),
'oneOf': [
{
@@ -476,32 +476,44 @@ configurationRegistry.registerConfiguration({
],
default: true
},
- 'task.quickOpen.history': {
+ [TaskSettingId.QuickOpenHistory]: {
markdownDescription: nls.localize('task.quickOpen.history', "Controls the number of recent items tracked in task quick open dialog."),
type: 'number',
default: 30, minimum: 0, maximum: 30
},
- 'task.quickOpen.detail': {
+ [TaskSettingId.QuickOpenDetail]: {
markdownDescription: nls.localize('task.quickOpen.detail', "Controls whether to show the task detail for tasks that have a detail in task quick picks, such as Run Task."),
type: 'boolean',
default: true
},
- 'task.quickOpen.skip': {
+ [TaskSettingId.QuickOpenSkip]: {
type: 'boolean',
description: nls.localize('task.quickOpen.skip', "Controls whether the task quick pick is skipped when there is only one task to pick from."),
default: false
},
- 'task.quickOpen.showAll': {
+ [TaskSettingId.QuickOpenShowAll]: {
type: 'boolean',
description: nls.localize('task.quickOpen.showAll', "Causes the Tasks: Run Task command to use the slower \"show all\" behavior instead of the faster two level picker where tasks are grouped by provider."),
default: false
},
- 'task.showDecorations': {
+ [TaskSettingId.AllowAutomaticTasks]: {
+ type: 'string',
+ enum: ['on', 'auto', 'off'],
+ enumDescriptions: [
+ nls.localize('ttask.allowAutomaticTasks.on', "Always"),
+ nls.localize('task.allowAutomaticTasks.auto', "Prompt for permission for each folder"),
+ nls.localize('task.allowAutomaticTasks.off', "Never"),
+ ],
+ description: nls.localize('task.allowAutomaticTasks', "Enable automatic tasks in the folder."),
+ default: 'auto',
+ restricted: true
+ },
+ [TaskSettingId.ShowDecorations]: {
type: 'boolean',
description: nls.localize('task.showDecorations', "Shows decorations at points of interest in the terminal buffer such as the first problem found via a watch task. Note that this will only take effect for future tasks."),
default: true
},
- 'task.saveBeforeRun': {
+ [TaskSettingId.SaveBeforeRun]: {
markdownDescription: nls.localize(
'task.saveBeforeRun',
'Save all dirty editors before running a task.'
diff --git a/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts b/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts
index 77dc6f60769..a2cc123e381 100644
--- a/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts
+++ b/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts
@@ -24,7 +24,6 @@ import { TaskQuickPickEntryType } from 'vs/workbench/contrib/tasks/browser/abstr
export const QUICKOPEN_DETAIL_CONFIG = 'task.quickOpen.detail';
export const QUICKOPEN_SKIP_CONFIG = 'task.quickOpen.skip';
-
export function isWorkspaceFolder(folder: IWorkspace | IWorkspaceFolder): folder is IWorkspaceFolder {
return 'uri' in folder;
}
@@ -108,7 +107,9 @@ export class TaskQuickPick extends Disposable {
groupLabel: string, extraButtons: IQuickInputButton[] = []) {
entries.push({ type: 'separator', label: groupLabel });
tasks.forEach(task => {
- entries.push(this._createTaskEntry(task, extraButtons));
+ if (!task.configurationProperties.hide) {
+ entries.push(this._createTaskEntry(task, extraButtons));
+ }
});
}
@@ -304,7 +305,7 @@ export class TaskQuickPick extends Disposable {
private async _doPickerSecondLevel(picker: IQuickPick<ITaskTwoLevelQuickPickEntry>, type: string) {
picker.busy = true;
if (type === SHOW_ALL) {
- const items = (await this._taskService.tasks()).sort((a, b) => this._sorter.compare(a, b)).map(task => this._createTaskEntry(task));
+ const items = (await this._taskService.tasks()).filter(t => !t.configurationProperties.hide).sort((a, b) => this._sorter.compare(a, b)).map(task => this._createTaskEntry(task));
items.push(...TaskQuickPick.allSettingEntries(this._configurationService));
picker.items = items;
} else {
@@ -353,9 +354,13 @@ export class TaskQuickPick extends Disposable {
private async _getEntriesForProvider(type: string): Promise<QuickPickInput<ITaskTwoLevelQuickPickEntry>[]> {
const tasks = (await this._taskService.tasks({ type })).sort((a, b) => this._sorter.compare(a, b));
- let taskQuickPickEntries: QuickPickInput<ITaskTwoLevelQuickPickEntry>[];
+ let taskQuickPickEntries: QuickPickInput<ITaskTwoLevelQuickPickEntry>[] = [];
if (tasks.length > 0) {
- taskQuickPickEntries = tasks.map(task => this._createTaskEntry(task));
+ for (const task of tasks) {
+ if (!task.configurationProperties.hide) {
+ taskQuickPickEntries.push(this._createTaskEntry(task));
+ }
+ }
taskQuickPickEntries.push({
type: 'separator'
}, {
diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts
index d1dd693f370..7cfab363b6d 100644
--- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts
+++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts
@@ -31,7 +31,7 @@ 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
+ 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,
@@ -209,10 +209,10 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
private readonly _onDidStateChange: Emitter<ITaskEvent>;
get taskShellIntegrationStartSequence(): string {
- return this._configurationService.getValue('task.showDecorations') ? VSCodeSequence(VSCodeOscPt.PromptStart) + VSCodeSequence(VSCodeOscPt.Property, `${VSCodeOscProperty.Task}=True`) + VSCodeSequence(VSCodeOscPt.CommandStart) : '';
+ return this._configurationService.getValue(TaskSettingId.ShowDecorations) ? VSCodeSequence(VSCodeOscPt.PromptStart) + VSCodeSequence(VSCodeOscPt.Property, `${VSCodeOscProperty.Task}=True`) + VSCodeSequence(VSCodeOscPt.CommandStart) : '';
}
get taskShellIntegrationOutputSequence(): string {
- return this._configurationService.getValue('task.showDecorations') ? VSCodeSequence(VSCodeOscPt.CommandExecuted) : '';
+ return this._configurationService.getValue(TaskSettingId.ShowDecorations) ? VSCodeSequence(VSCodeOscPt.CommandExecuted) : '';
}
constructor(
@@ -516,12 +516,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
for (const dependency of task.configurationProperties.dependsOn) {
const dependencyTask = await resolver.resolve(dependency.uri, dependency.task!);
if (dependencyTask) {
- if (dependencyTask.configurationProperties.icon) {
- dependencyTask.configurationProperties.icon.id ||= task.configurationProperties.icon?.id;
- dependencyTask.configurationProperties.icon.color ||= task.configurationProperties.icon?.color;
- } else {
- dependencyTask.configurationProperties.icon = task.configurationProperties.icon;
- }
+ this._adoptConfigurationForDependencyTask(dependencyTask, task);
const key = dependencyTask.getMapKey();
let promise = this._activeTasks[key] ? this._getDependencyPromise(this._activeTasks[key]) : undefined;
if (!promise) {
@@ -590,6 +585,21 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
});
}
+ private _adoptConfigurationForDependencyTask(dependencyTask: Task, task: Task): void {
+ if (dependencyTask.configurationProperties.icon) {
+ dependencyTask.configurationProperties.icon.id ||= task.configurationProperties.icon?.id;
+ dependencyTask.configurationProperties.icon.color ||= task.configurationProperties.icon?.color;
+ } else {
+ dependencyTask.configurationProperties.icon = task.configurationProperties.icon;
+ }
+
+ if (dependencyTask.configurationProperties.hide) {
+ dependencyTask.configurationProperties.hide ||= task.configurationProperties.hide;
+ } else {
+ dependencyTask.configurationProperties.hide = task.configurationProperties.hide;
+ }
+ }
+
private async _getDependencyPromise(task: IActiveTerminalData): Promise<ITaskSummary> {
if (!task.task.configurationProperties.isBackground) {
return task.promise;
@@ -1147,7 +1157,10 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
}, 'Executing task: {0}', commandLine), { excludeLeadingNewLine: true }) + this.taskShellIntegrationOutputSequence;
}
} else {
- shellLaunchConfig.initialText = this.taskShellIntegrationStartSequence + this.taskShellIntegrationOutputSequence;
+ shellLaunchConfig.initialText = {
+ text: this.taskShellIntegrationStartSequence + this.taskShellIntegrationOutputSequence,
+ trailingNewLine: false
+ };
}
} else {
const commandExecutable = (task.command.runtime !== RuntimeType.CustomExecution) ? CommandString.value(command) : undefined;
@@ -1187,7 +1200,10 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem {
}, 'Executing task: {0}', `${shellLaunchConfig.executable} ${getArgsToEcho(shellLaunchConfig.args)}`), { excludeLeadingNewLine: true }) + this.taskShellIntegrationOutputSequence;
}
} else {
- shellLaunchConfig.initialText = this.taskShellIntegrationStartSequence + this.taskShellIntegrationOutputSequence;
+ shellLaunchConfig.initialText = {
+ text: this.taskShellIntegrationStartSequence + this.taskShellIntegrationOutputSequence,
+ trailingNewLine: false
+ };
}
}
diff --git a/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts b/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts
index 077bd89a60e..fa67f8ecf65 100644
--- a/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts
+++ b/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts
@@ -45,6 +45,13 @@ const shellCommand: IJSONSchema = {
deprecationMessage: nls.localize('JsonSchema.tasks.isShellCommand.deprecated', 'The property isShellCommand is deprecated. Use the type property of the task and the shell property in the options instead. See also the 1.14 release notes.')
};
+
+const hide: IJSONSchema = {
+ type: 'boolean',
+ description: nls.localize('JsonSchema.hide', 'Hide this task from the run task quick pick'),
+ default: true
+};
+
const taskIdentifier: IJSONSchema = {
type: 'object',
additionalProperties: true,
@@ -407,6 +414,7 @@ const taskConfiguration: IJSONSchema = {
},
presentation: Objects.deepClone(presentation),
icon: Objects.deepClone(icon),
+ hide: Objects.deepClone(hide),
options: options,
problemMatcher: {
$ref: '#/definitions/problemMatcherType',
@@ -479,6 +487,7 @@ taskDescriptionProperties.command = Objects.deepClone(command);
taskDescriptionProperties.args = Objects.deepClone(args);
taskDescriptionProperties.isShellCommand = Objects.deepClone(shellCommand);
taskDescriptionProperties.dependsOn = dependsOn;
+taskDescriptionProperties.hide = Objects.deepClone(hide);
taskDescriptionProperties.dependsOrder = dependsOrder;
taskDescriptionProperties.identifier = Objects.deepClone(identifier);
taskDescriptionProperties.type = Objects.deepClone(taskType);
diff --git a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts
index c5d4058bcb0..0bb00f57620 100644
--- a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts
+++ b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts
@@ -362,6 +362,11 @@ export interface IConfigurationProperties {
* The icon's color in the terminal tabs list
*/
color?: string;
+
+ /**
+ * Do not show this task in the run task quickpick
+ */
+ hide?: boolean;
}
export interface ICustomTask extends ICommandProperties, IConfigurationProperties {
@@ -1322,7 +1327,8 @@ namespace ConfigurationProperties {
{ property: 'presentation', type: CommandConfiguration.PresentationOptions },
{ property: 'problemMatchers' },
{ property: 'options' },
- { property: 'icon' }
+ { property: 'icon' },
+ { property: 'hide' }
];
export function from(this: void, external: IConfigurationProperties & { [key: string]: any }, context: IParseContext,
@@ -1350,7 +1356,7 @@ namespace ConfigurationProperties {
result.identifier = external.identifier;
}
result.icon = external.icon;
-
+ result.hide = external.hide;
if (external.isBackground !== undefined) {
result.isBackground = !!external.isBackground;
}
@@ -1483,7 +1489,7 @@ namespace ConfiguringTask {
type,
taskIdentifier,
RunOptions.fromConfiguration(external.runOptions),
- {}
+ { hide: external.hide }
);
const configuration = ConfigurationProperties.from(external, context, true, source, typeDeclaration.properties);
result.addTaskLoadMessages(configuration.errors);
@@ -1635,7 +1641,8 @@ namespace CustomTask {
{
name: configuredProps.configurationProperties.name || contributedTask.configurationProperties.name,
identifier: configuredProps.configurationProperties.identifier || contributedTask.configurationProperties.identifier,
- icon: configuredProps.configurationProperties.icon
+ icon: configuredProps.configurationProperties.icon,
+ hide: configuredProps.configurationProperties.hide
},
);
@@ -2119,7 +2126,7 @@ class ConfigurationParser {
identifier: name,
group: Tasks.TaskGroup.Build,
isBackground: isBackground,
- problemMatchers: matchers,
+ problemMatchers: matchers
}
);
const taskGroupKind = GroupKind.from(fileConfig.group);
diff --git a/src/vs/workbench/contrib/tasks/common/tasks.ts b/src/vs/workbench/contrib/tasks/common/tasks.ts
index 1b52c33d02a..5877beb6437 100644
--- a/src/vs/workbench/contrib/tasks/common/tasks.ts
+++ b/src/vs/workbench/contrib/tasks/common/tasks.ts
@@ -549,6 +549,11 @@ export interface IConfigurationProperties {
* The icon for this task in the terminal tabs list
*/
icon?: { id?: string; color?: string };
+
+ /**
+ * Do not show this task in the run task quickpick
+ */
+ hide?: boolean;
}
export enum RunOnOptions {
@@ -914,6 +919,11 @@ export class ContributedTask extends CommonTask {
*/
icon: { id?: string; color?: string } | undefined;
+ /**
+ * Don't show the task in the run task quickpick
+ */
+ hide?: boolean;
+
public constructor(id: string, source: IExtensionTaskSource, label: string, type: string | undefined, defines: KeyedTaskIdentifier,
command: ICommandConfiguration, hasDefinedMatchers: boolean, runOptions: IRunOptions,
configurationProperties: IConfigurationProperties) {
@@ -922,6 +932,7 @@ export class ContributedTask extends CommonTask {
this.hasDefinedMatchers = hasDefinedMatchers;
this.command = command;
this.icon = configurationProperties.icon;
+ this.hide = configurationProperties.hide;
}
public override clone(): ContributedTask {
@@ -1179,6 +1190,30 @@ export namespace KeyedTaskIdentifier {
}
}
+export const enum TaskSettingId {
+ AutoDetect = 'task.autoDetect',
+ SaveBeforeRun = 'task.saveBeforeRun',
+ ShowDecorations = 'task.showDecorations',
+ ProblemMatchersNeverPrompt = 'task.problemMatchers.neverPrompt',
+ SlowProviderWarning = 'task.slowProviderWarning',
+ QuickOpenHistory = 'task.quickOpen.history',
+ QuickOpenDetail = 'task.quickOpen.detail',
+ QuickOpenSkip = 'task.quickOpen.skip',
+ QuickOpenShowAll = 'task.quickOpen.showAll',
+ AllowAutomaticTasks = 'task.allowAutomaticTasks'
+}
+
+export const enum TasksSchemaProperties {
+ Tasks = 'tasks',
+ SuppressTaskName = 'tasks.suppressTaskName',
+ Windows = 'tasks.windows',
+ Osx = 'tasks.osx',
+ Linux = 'tasks.linux',
+ ShowOutput = 'tasks.showOutput',
+ IsShellCommand = 'tasks.isShellCommand',
+ ServiceTestSetting = 'tasks.service.testSetting',
+}
+
export namespace TaskDefinition {
export function createTaskIdentifier(external: ITaskIdentifier, reporter: { error(message: string): void }): KeyedTaskIdentifier | undefined {
const definition = TaskDefinitionRegistry.get(external.type);
diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts
index bfc044ba133..6fc5f4eb043 100644
--- a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts
+++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts
@@ -189,9 +189,23 @@ export class TerminalSearchLinkOpener implements ITerminalLinkOpener {
// Try open as an absolute link
let resourceMatch: IResourceMatch | undefined;
if (absolutePath) {
- const slashNormalizedPath = this._os === OperatingSystem.Windows ? absolutePath.replace(/\\/g, '/') : absolutePath;
- const scheme = this._workbenchEnvironmentService.remoteAuthority ? Schemas.vscodeRemote : Schemas.file;
- const uri = URI.from({ scheme, path: slashNormalizedPath });
+ let normalizedAbsolutePath: string = absolutePath;
+ if (this._os === OperatingSystem.Windows) {
+ normalizedAbsolutePath = absolutePath.replace(/\\/g, '/');
+ if (normalizedAbsolutePath.match(/[a-z]:/i)) {
+ normalizedAbsolutePath = `/${normalizedAbsolutePath}`;
+ }
+ }
+ let uri: URI;
+ if (this._workbenchEnvironmentService.remoteAuthority) {
+ uri = URI.from({
+ scheme: Schemas.vscodeRemote,
+ authority: this._workbenchEnvironmentService.remoteAuthority,
+ path: normalizedAbsolutePath
+ });
+ } else {
+ uri = URI.file(normalizedAbsolutePath);
+ }
try {
const fileStat = await this._fileService.stat(uri);
resourceMatch = { uri, isDirectory: fileStat.isDirectory };
@@ -218,6 +232,16 @@ export class TerminalSearchLinkOpener implements ITerminalLinkOpener {
private async _tryOpenExactLink(text: string, link: ITerminalSimpleLink): Promise<boolean> {
const sanitizedLink = text.replace(/:\d+(:\d+)?$/, '');
+ // For links made up of only a file name (no folder), disallow exact link matching. For
+ // example searching for `foo.txt` when there is no cwd information available (ie. only the
+ // initial cwd) should NOT search as it's ambiguous if there are multiple matches.
+ //
+ // However, for a link like `src/foo.txt`, if there's an exact match for `src/foo.txt` in
+ // any folder we want to take it, even if there are partial matches like `src2/foo.txt`
+ // available.
+ if (!sanitizedLink.match(/[\\/]/)) {
+ return false;
+ }
try {
const result = await this._getExactMatch(sanitizedLink);
if (result) {
diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts
index 48e5c0659bc..cec80cf90b1 100644
--- a/src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts
+++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts
@@ -49,10 +49,11 @@ export const winLocalLinkClause = '((' + winPathPrefix + '|(' + winExcludedPathC
/** As xterm reads from DOM, space in that case is nonbreaking char ASCII code - 160,
replacing space with nonBreakningSpace or space ASCII code - 32. */
export const lineAndColumnClause = [
+ '(([^:\\s\\(\\)<>\'\"\\[\\]]*) ((\\d+))(:(\\d+)))', // (file path) 336:9 [see #140780]
'((\\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*):line ((\\d+)(, column (\\d+))?))', // (file path):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?[\\(\\[](\\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/shellIntegration-bash.sh b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh
index b0c525a298d..a2f4afa1400 100755
--- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh
+++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh
@@ -39,13 +39,6 @@ if [[ "$PROMPT_COMMAND" =~ .*(' '.*\;)|(\;.*' ').* ]]; then
builtin return
fi
-# Disable shell integration if HISTCONTROL is set to erase duplicate entries as the exit code
-# reporting relies on the duplicates existing
-if [[ "$HISTCONTROL" =~ .*erasedups.* ]]; then
- builtin unset VSCODE_SHELL_INTEGRATION
- builtin return
-fi
-
if [ -z "$VSCODE_SHELL_INTEGRATION" ]; then
builtin return
fi
@@ -56,7 +49,7 @@ __vsc_original_PS2="$PS2"
__vsc_custom_PS1=""
__vsc_custom_PS2=""
__vsc_in_command_execution="1"
-__vsc_last_history_id=$(history 1 | awk '{print $1;}')
+__vsc_current_command=""
__vsc_prompt_start() {
builtin printf "\033]633;A\007"
@@ -72,6 +65,7 @@ __vsc_update_cwd() {
__vsc_command_output_start() {
builtin printf "\033]633;C\007"
+ builtin printf "\033]633;E;$__vsc_current_command\007"
}
__vsc_continuation_start() {
@@ -83,12 +77,10 @@ __vsc_continuation_end() {
}
__vsc_command_complete() {
- local __vsc_history_id=$(builtin history 1 | awk '{print $1;}')
- if [[ "$__vsc_history_id" == "$__vsc_last_history_id" ]]; then
+ if [ "$__vsc_current_command" = "" ]; then
builtin printf "\033]633;D\007"
else
builtin printf "\033]633;D;%s\007" "$__vsc_status"
- __vsc_last_history_id=$__vsc_history_id
fi
__vsc_update_cwd
}
@@ -113,22 +105,27 @@ __vsc_update_prompt() {
__vsc_precmd() {
__vsc_command_complete "$__vsc_status"
+ __vsc_current_command=""
__vsc_update_prompt
}
__vsc_preexec() {
- if [ "$__vsc_in_command_execution" = "0" ]; then
- __vsc_initialized=1
- __vsc_in_command_execution="1"
- __vsc_command_output_start
+ __vsc_initialized=1
+ if [[ ! "$BASH_COMMAND" =~ ^__vsc_prompt* ]]; then
+ __vsc_current_command=$BASH_COMMAND
+ else
+ __vsc_current_command=""
fi
+ __vsc_command_output_start
}
# Debug trapping/preexec inspired by starship (ISC)
if [[ -n "${bash_preexec_imported:-}" ]]; then
__vsc_preexec_only() {
- __vsc_status="$?"
- __vsc_preexec
+ if [ "$__vsc_in_command_execution" = "0" ]; then
+ __vsc_in_command_execution="1"
+ __vsc_preexec
+ fi
}
precmd_functions+=(__vsc_prompt_cmd)
preexec_functions+=(__vsc_preexec_only)
@@ -136,15 +133,19 @@ else
__vsc_dbg_trap="$(trap -p DEBUG | cut -d' ' -f3 | tr -d \')"
if [[ -z "$__vsc_dbg_trap" ]]; then
__vsc_preexec_only() {
- __vsc_status="$?"
- __vsc_preexec
+ if [ "$__vsc_in_command_execution" = "0" ]; then
+ __vsc_in_command_execution="1"
+ __vsc_preexec
+ fi
}
trap '__vsc_preexec_only "$_"' DEBUG
elif [[ "$__vsc_dbg_trap" != '__vsc_preexec "$_"' && "$__vsc_dbg_trap" != '__vsc_preexec_all "$_"' ]]; then
__vsc_preexec_all() {
- __vsc_status="$?"
- builtin eval ${__vsc_dbg_trap}
- __vsc_preexec
+ if [ "$__vsc_in_command_execution" = "0" ]; then
+ __vsc_in_command_execution="1"
+ builtin eval ${__vsc_dbg_trap}
+ __vsc_preexec
+ fi
}
trap '__vsc_preexec_all "$_"' DEBUG
fi
@@ -153,6 +154,7 @@ fi
__vsc_update_prompt
__vsc_prompt_cmd_original() {
+ __vsc_status="$?"
if [[ ${IFS+set} ]]; then
__vsc_original_ifs="$IFS"
fi
diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh
index a94e7c11c71..7db2583a817 100644
--- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh
+++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh
@@ -21,6 +21,10 @@ if [[ "$VSCODE_INJECTION" == "1" ]]; then
. $USER_ZDOTDIR/.zshrc
ZDOTDIR=$VSCODE_ZDOTDIR
fi
+
+ if [[ -f $USER_ZDOTDIR/.zsh_history ]]; then
+ HISTFILE=$USER_ZDOTDIR/.zsh_history
+ fi
fi
# Shell integration was disabled by the shell, exit without warning assuming either the shell has
@@ -29,9 +33,8 @@ if [ -z "$VSCODE_SHELL_INTEGRATION" ]; then
builtin return
fi
-__vsc_initialized="0"
__vsc_in_command_execution="1"
-__vsc_last_history_id=0
+__vsc_current_command=""
__vsc_prompt_start() {
builtin printf "\033]633;A\007"
@@ -47,6 +50,7 @@ __vsc_update_cwd() {
__vsc_command_output_start() {
builtin printf "\033]633;C\007"
+ builtin printf "\033]633;E;$__vsc_current_command\007"
}
__vsc_continuation_start() {
@@ -66,17 +70,10 @@ __vsc_right_prompt_end() {
}
__vsc_command_complete() {
- builtin local __vsc_history_id=$(builtin history | tail -n1 | awk '{print $1;}')
- # Don't write the command complete sequence for the first prompt without an associated command
- if [[ "$__vsc_initialized" == "1" ]]; then
- if [[ "$__vsc_history_id" == "$__vsc_last_history_id" ]]; then
- builtin printf "\033]633;D\007"
- else
- builtin printf "\033]633;D;%s\007" "$__vsc_status"
- __vsc_last_history_id=$__vsc_history_id
- fi
- else
+ if [[ "$__vsc_current_command" == "" ]]; then
builtin printf "\033]633;D\007"
+ else
+ builtin printf "\033]633;D;%s\007" "$__vsc_status"
fi
__vsc_update_cwd
}
@@ -108,6 +105,7 @@ __vsc_precmd() {
fi
__vsc_command_complete "$__vsc_status"
+ __vsc_current_command=""
# in command execution
if [ -n "$__vsc_in_command_execution" ]; then
@@ -121,8 +119,8 @@ __vsc_preexec() {
if [ -n "$RPROMPT" ]; then
RPROMPT="$__vsc_prior_rprompt"
fi
- __vsc_initialized="1"
__vsc_in_command_execution="1"
+ __vsc_current_command=$1
__vsc_command_output_start
}
add-zsh-hook precmd __vsc_precmd
diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1 b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1
index aceb31ab780..5d7d4436094 100644
--- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1
+++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1
@@ -72,3 +72,19 @@ if (Get-Module -Name PSReadLine) {
# Set IsWindows property
[Console]::Write("`e]633;P;IsWindows=$($IsWindows)`a")
+
+# Set always on key handlers which map to default VS Code keybindings
+function Set-MappedKeyHandler {
+ param ([string[]] $Chord, [string[]]$Sequence)
+ $Handler = $(Get-PSReadLineKeyHandler -Chord $Chord)
+ if ($Handler) {
+ Set-PSReadLineKeyHandler -Chord $Sequence -Function $Handler.Function
+ }
+}
+function Set-MappedKeyHandlers {
+ Set-MappedKeyHandler -Chord Ctrl+Spacebar -Sequence 'F12,a'
+ Set-MappedKeyHandler -Chord Alt+Spacebar -Sequence 'F12,b'
+ Set-MappedKeyHandler -Chord Shift+Enter -Sequence 'F12,c'
+ Set-MappedKeyHandler -Chord Shift+End -Sequence 'F12,d'
+}
+Set-MappedKeyHandlers
diff --git a/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts b/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts
index dbbcea77cf9..35454bcae4e 100644
--- a/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts
+++ b/src/vs/workbench/contrib/terminal/browser/remoteTerminalBackend.ts
@@ -239,6 +239,20 @@ class RemoteTerminalBackend extends BaseTerminalBackend implements ITerminalBack
return undefined;
}
+ async attachToRevivedProcess(id: number): Promise<ITerminalChildProcess | undefined> {
+ if (!this._remoteTerminalChannel) {
+ throw new Error(`Cannot create remote terminal when there is no remote!`);
+ }
+
+ try {
+ const newId = await this._remoteTerminalChannel.getRevivedPtyNewId(id) ?? id;
+ return await this.attachToProcess(newId);
+ } catch (e) {
+ this._logService.trace(`Couldn't attach to process ${e.message}`);
+ }
+ return undefined;
+ }
+
async listProcesses(): Promise<IProcessDetails[]> {
const terms = this._remoteTerminalChannel ? await this._remoteTerminalChannel.listProcesses() : [];
return terms.map(termDto => {
diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts
index d01b958ae13..a62e8b54d38 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts
@@ -177,6 +177,33 @@ if (isWindows) {
});
}
+// Map certain keybindings in pwsh to unused keys which get handled by PSReadLine handlers in the
+// shell integration script. This allows keystrokes that cannot be sent via VT sequences to work.
+// See https://github.com/microsoft/terminal/issues/879#issuecomment-497775007
+registerSendSequenceKeybinding('\x1b[24~a', { // F12,a -> ctrl+space (MenuComplete)
+ when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, WindowsShellType.PowerShell), TerminalContextKeys.terminalShellIntegrationEnabled, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()),
+ primary: KeyMod.CtrlCmd | KeyCode.Space,
+ mac: { primary: KeyMod.WinCtrl | KeyCode.Space }
+});
+registerSendSequenceKeybinding('\x1b[24~b', { // F12,b -> alt+space (SetMark)
+ when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, WindowsShellType.PowerShell), TerminalContextKeys.terminalShellIntegrationEnabled, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()),
+ primary: KeyMod.Alt | KeyCode.Space
+});
+registerSendSequenceKeybinding('\x1b[24~c', { // F12,c -> shift+enter (AddLine)
+ when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, WindowsShellType.PowerShell), TerminalContextKeys.terminalShellIntegrationEnabled, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()),
+ primary: KeyMod.Shift | KeyCode.Enter
+});
+registerSendSequenceKeybinding('\x1b[24~d', { // F12,d -> shift+end (SelectLine) - HACK: \x1b[1;2F is supposed to work but it doesn't
+ when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, WindowsShellType.PowerShell), TerminalContextKeys.terminalShellIntegrationEnabled, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()),
+ mac: { primary: KeyMod.Shift | KeyMod.CtrlCmd | KeyCode.RightArrow }
+});
+
+// Always on pwsh keybindings
+registerSendSequenceKeybinding('\x1b[1;2H', { // Shift+home
+ when: ContextKeyExpr.and(TerminalContextKeys.focus, ContextKeyExpr.equals(TerminalContextKeyStrings.ShellType, WindowsShellType.PowerShell)),
+ mac: { primary: KeyMod.Shift | KeyMod.CtrlCmd | KeyCode.LeftArrow }
+});
+
// send ctrl+c to the iPad when the terminal is focused and ctrl+c is pressed to kill the process (work around for #114009)
if (isIOS) {
registerSendSequenceKeybinding(String.fromCharCode('C'.charCodeAt(0) - CTRL_LETTER_OFFSET), { // ctrl+c
diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts
index ddaf6f211db..aa125ac3d78 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminal.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts
@@ -11,7 +11,7 @@ import { FindReplaceState } from 'vs/editor/contrib/find/browser/findState';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IKeyMods } from 'vs/platform/quickinput/common/quickInput';
import { ITerminalCapabilityStore, ITerminalCommand } from 'vs/platform/terminal/common/capabilities/capabilities';
-import { IExtensionTerminalProfile, IProcessPropertyMap, IShellIntegration, IShellLaunchConfig, ITerminalDimensions, ITerminalLaunchError, ITerminalProfile, ITerminalTabLayoutInfoById, ProcessPropertyType, TerminalIcon, TerminalLocation, TerminalShellType, TitleEventSource } from 'vs/platform/terminal/common/terminal';
+import { IExtensionTerminalProfile, IProcessPropertyMap, IShellIntegration, IShellLaunchConfig, ITerminalDimensions, ITerminalLaunchError, ITerminalProfile, ITerminalTabLayoutInfoById, ProcessPropertyType, TerminalExitReason, TerminalIcon, TerminalLocation, TerminalShellType, TitleEventSource } from 'vs/platform/terminal/common/terminal';
import { IGenericMarkProperties } from 'vs/platform/terminal/common/terminalProcess';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
@@ -473,6 +473,11 @@ export interface ITerminalInstance {
readonly persistentProcessId: number | undefined;
/**
+ * The id of a persistent process during the shutdown process
+ */
+ shutdownPersistentProcessId: number | undefined;
+
+ /**
* Whether the process should be persisted across reloads.
*/
readonly shouldPersist: boolean;
@@ -567,6 +572,8 @@ export interface ITerminalInstance {
readonly exitCode: number | undefined;
+ readonly exitReason: TerminalExitReason | undefined;
+
readonly areLinksReady: boolean;
/**
@@ -651,17 +658,17 @@ export interface ITerminalInstance {
/**
* Dispose the terminal instance, removing it from the panel/service and freeing up resources.
*
- * @param immediate Whether the kill should be immediate or not. Immediate should only be used
- * when VS Code is shutting down or in cases where the terminal dispose was user initiated.
- * The immediate===false exists to cover an edge case where the final output of the terminal can
- * get cut off. If immediate kill any terminal processes immediately.
+ * @param reason The reason why the terminal is being disposed
*/
- dispose(immediate?: boolean): void;
+ dispose(reason?: TerminalExitReason): void;
/**
- * Inform the process that the terminal is now detached.
+ * Informs the process that the terminal is now detached and
+ * then disposes the terminal.
+ *
+ * @param reason The reason why the terminal is being disposed
*/
- detachFromProcess(): Promise<void>;
+ detachProcessAndDispose(reason: TerminalExitReason): Promise<void>;
/**
* Check if anything is selected in terminal.
@@ -673,6 +680,12 @@ export interface ITerminalInstance {
*/
copySelection(asHtml?: boolean, command?: ITerminalCommand): Promise<void>;
+
+ /**
+ * Copies the ouput of the last command
+ */
+ copyLastCommandOutput(): Promise<void>;
+
/**
* Current selection in the terminal.
*/
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts
index ed78d4a79e8..5f5bbce74dd 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts
@@ -28,7 +28,7 @@ import { IListService } from 'vs/platform/list/browser/listService';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IPickOptions, IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
-import { ITerminalProfile, TerminalLocation, TerminalSettingId, TitleEventSource } from 'vs/platform/terminal/common/terminal';
+import { ITerminalProfile, TerminalExitReason, TerminalLocation, TerminalSettingId, TitleEventSource } from 'vs/platform/terminal/common/terminal';
import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands';
import { CLOSE_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands';
@@ -307,7 +307,7 @@ export function registerTerminalActions() {
constructor() {
super({
id: TerminalCommandId.RunRecentCommand,
- title: { value: localize('workbench.action.terminal.runRecentCommand', "Run Recent Command"), original: 'Run Recent Command' },
+ title: { value: localize('workbench.action.terminal.runRecentCommand', "Run Recent Command..."), original: 'Run Recent Command...' },
f1: true,
category,
precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated)
@@ -330,8 +330,22 @@ export function registerTerminalActions() {
registerAction2(class extends Action2 {
constructor() {
super({
+ id: TerminalCommandId.CopyLastCommand,
+ title: { value: localize('workbench.action.terminal.copyLastCommand', 'Copy Last Command'), original: 'Copy Last Command' },
+ f1: true,
+ category,
+ precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated)
+ });
+ }
+ async run(accessor: ServicesAccessor): Promise<void> {
+ await accessor.get(ITerminalService).activeInstance?.copyLastCommandOutput();
+ }
+ });
+ registerAction2(class extends Action2 {
+ constructor() {
+ super({
id: TerminalCommandId.GoToRecentDirectory,
- title: { value: localize('workbench.action.terminal.goToRecentDirectory', "Go to Recent Directory"), original: 'Go to Recent Directory' },
+ title: { value: localize('workbench.action.terminal.goToRecentDirectory', "Go to Recent Directory..."), original: 'Go to Recent Directory...' },
f1: true,
category,
precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated)
@@ -1062,7 +1076,7 @@ export function registerTerminalActions() {
}
async run(accessor: ServicesAccessor) {
const terminalService = accessor.get(ITerminalService);
- await terminalService.activeInstance?.detachFromProcess();
+ await terminalService.activeInstance?.detachProcessAndDispose(TerminalExitReason.User);
}
});
registerAction2(class extends Action2 {
@@ -1709,6 +1723,7 @@ export function registerTerminalActions() {
const themeService = accessor.get(IThemeService);
const groupService = accessor.get(ITerminalGroupService);
const notificationService = accessor.get(INotificationService);
+
const picks: ITerminalQuickPickItem[] = [];
if (groupService.instances.length <= 1) {
notificationService.warn(localize('workbench.action.terminal.join.insufficientTerminals', 'Insufficient terminals for the join action'));
@@ -1718,7 +1733,7 @@ export function registerTerminalActions() {
for (const terminal of otherInstances) {
const group = groupService.getGroupForInstance(terminal);
if (group?.terminalInstances.length === 1) {
- const iconId = getIconId(terminal);
+ const iconId = getIconId(accessor, terminal);
const label = `$(${iconId}): ${terminal.title}`;
const iconClasses: string[] = [];
const colorClass = getColorClass(terminal);
@@ -2112,10 +2127,11 @@ export function registerTerminalActions() {
title: { value: localize('workbench.action.terminal.sizeToContentWidth', "Toggle Size to Content Width"), original: 'Toggle Size to Content Width' },
f1: true,
category,
- precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.isOpen, TerminalContextKeys.focus),
+ precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.isOpen),
keybinding: {
primary: KeyMod.Alt | KeyCode.KeyZ,
- weight: KeybindingWeight.WorkbenchContrib
+ weight: KeybindingWeight.WorkbenchContrib,
+ when: TerminalContextKeys.focus
}
});
}
@@ -2123,12 +2139,13 @@ export function registerTerminalActions() {
await accessor.get(ITerminalService).doWithActiveInstance(t => t.toggleSizeToContentWidth());
}
});
+
registerAction2(class extends Action2 {
constructor() {
super({
id: TerminalCommandId.SizeToContentWidthInstance,
title: terminalStrings.toggleSizeToContentWidth,
- f1: true,
+ f1: false,
category,
precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.focus)
});
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts
index b80d8fae41c..55032336cd5 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts
@@ -9,11 +9,11 @@ import { dispose, toDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { EditorInputCapabilities, IEditorIdentifier, IUntypedEditorInput } from 'vs/workbench/common/editor';
import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
-import { EditorInput } from 'vs/workbench/common/editor/editorInput';
+import { EditorInput, IEditorCloseHandler } from 'vs/workbench/common/editor/editorInput';
import { ITerminalInstance, ITerminalInstanceService, terminalEditorId } from 'vs/workbench/contrib/terminal/browser/terminal';
import { getColorClass, getUriClasses } from 'vs/workbench/contrib/terminal/browser/terminalIcon';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
-import { IShellLaunchConfig, TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
+import { IShellLaunchConfig, TerminalExitReason, TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { ConfirmOnKill } from 'vs/workbench/contrib/terminal/common/terminal';
@@ -23,21 +23,22 @@ import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/termin
import { ConfirmResult, IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { Emitter } from 'vs/base/common/event';
-export class TerminalEditorInput extends EditorInput {
-
- protected readonly _onDidRequestAttach = this._register(new Emitter<ITerminalInstance>());
- readonly onDidRequestAttach = this._onDidRequestAttach.event;
+export class TerminalEditorInput extends EditorInput implements IEditorCloseHandler {
static readonly ID = 'workbench.editors.terminal';
+ override readonly closeHandler = this;
+
private _isDetached = false;
private _isShuttingDown = false;
private _isReverted = false;
private _copyLaunchConfig?: IShellLaunchConfig;
private _terminalEditorFocusContextKey: IContextKey<boolean>;
-
private _group: IEditorGroup | undefined;
+ protected readonly _onDidRequestAttach = this._register(new Emitter<ITerminalInstance>());
+ readonly onDidRequestAttach = this._onDidRequestAttach.event;
+
setGroup(group: IEditorGroup | undefined) {
this._group = group;
}
@@ -64,13 +65,6 @@ export class TerminalEditorInput extends EditorInput {
}
this._terminalInstance = instance;
this._setupInstanceListeners();
-
- // Refresh dirty state when the confirm on kill setting is changed
- this._configurationService.onDidChangeConfiguration(e => {
- if (e.affectsConfiguration(TerminalSettingId.ConfirmOnKill)) {
- this._onDidChangeDirty.fire();
- }
- });
}
override copy(): EditorInput {
@@ -95,7 +89,7 @@ export class TerminalEditorInput extends EditorInput {
return this._isDetached ? undefined : this._terminalInstance;
}
- override isDirty(): boolean {
+ showConfirm(): boolean {
if (this._isReverted) {
return false;
}
@@ -106,7 +100,7 @@ export class TerminalEditorInput extends EditorInput {
return false;
}
- override 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?"),
@@ -148,12 +142,6 @@ export class TerminalEditorInput extends EditorInput {
this._terminalEditorFocusContextKey = TerminalContextKeys.editorFocus.bindTo(_contextKeyService);
- // Refresh dirty state when the confirm on kill setting is changed
- this._configurationService.onDidChangeConfiguration(e => {
- if (e.affectsConfiguration(TerminalSettingId.ConfirmOnKill)) {
- this._onDidChangeDirty.fire();
- }
- });
if (_terminalInstance) {
this._setupInstanceListeners();
}
@@ -167,7 +155,9 @@ export class TerminalEditorInput extends EditorInput {
this._register(toDisposable(() => {
if (!this._isDetached && !this._isShuttingDown) {
- instance.dispose();
+ // Will be ignored if triggered by onExit or onDisposed terminal events
+ // as disposed was already called
+ instance.dispose(TerminalExitReason.User);
}
}));
@@ -178,7 +168,6 @@ export class TerminalEditorInput extends EditorInput {
instance.onIconChanged(() => this._onDidChangeLabel.fire()),
instance.onDidFocus(() => this._terminalEditorFocusContextKey.set(true)),
instance.onDidBlur(() => this._terminalEditorFocusContextKey.reset()),
- instance.onDidChangeHasChildProcesses(() => this._onDidChangeDirty.fire()),
instance.statusList.onDidChangePrimaryStatus(() => this._onDidChangeLabel.fire())
];
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts
index aec68ee5d91..74b83274dca 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalEditorService.ts
@@ -279,7 +279,8 @@ export class TerminalEditorService extends Disposable implements ITerminalEditor
const inputKey = resource.path;
if ('pid' in deserializedInput) {
- const instance = this._terminalInstanceService.createInstance({ attachPersistentProcess: deserializedInput }, TerminalLocation.Editor);
+ const newDeserializedInput = { ...deserializedInput, findRevivedId: true };
+ const instance = this._terminalInstanceService.createInstance({ attachPersistentProcess: newDeserializedInput }, TerminalLocation.Editor);
instance.target = TerminalLocation.Editor;
const input = this._instantiationService.createInstance(TerminalEditorInput, resource, instance);
this._registerInstance(inputKey, input, instance);
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts b/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts
index b098cc3ece1..89acbedfa80 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalIcon.ts
@@ -3,14 +3,15 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { Codicon } from 'vs/base/common/codicons';
import { hash } from 'vs/base/common/hash';
import { URI } from 'vs/base/common/uri';
+import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IExtensionTerminalProfile, ITerminalProfile } from 'vs/platform/terminal/common/terminal';
import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry';
import { ColorScheme } from 'vs/platform/theme/common/theme';
import { IColorTheme, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal';
+import { ITerminalProfileResolverService } from 'vs/workbench/contrib/terminal/common/terminal';
import { ansiColorMap } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry';
@@ -116,9 +117,9 @@ export function getUriClasses(terminal: ITerminalInstance | IExtensionTerminalPr
return iconClasses;
}
-export function getIconId(terminal: ITerminalInstance | IExtensionTerminalProfile | ITerminalProfile): string {
+export function getIconId(accessor: ServicesAccessor, terminal: ITerminalInstance | IExtensionTerminalProfile | ITerminalProfile): string {
if (!terminal.icon || (terminal.icon instanceof Object && !('id' in terminal.icon))) {
- return Codicon.terminal.id;
+ return accessor.get(ITerminalProfileResolverService).getDefaultIcon().id;
}
return typeof terminal.icon === 'string' ? terminal.icon : terminal.icon.id;
}
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts
index 402e8cf18e8..1f8a4970e50 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts
@@ -46,8 +46,8 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { ITerminalCommand, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities';
import { TerminalCapabilityStoreMultiplexer } from 'vs/platform/terminal/common/capabilities/terminalCapabilityStore';
-import { IProcessDataEvent, IProcessPropertyMap, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, PosixShellType, ProcessPropertyType, TerminalIcon, TerminalLocation, TerminalSettingId, TerminalShellType, TitleEventSource, WindowsShellType } from 'vs/platform/terminal/common/terminal';
-import { escapeNonWindowsPath } from 'vs/platform/terminal/common/terminalEnvironment';
+import { IProcessDataEvent, IProcessPropertyMap, IShellLaunchConfig, ITerminalDimensionsOverride, ITerminalLaunchError, PosixShellType, ProcessPropertyType, ShellIntegrationStatus, TerminalExitReason, TerminalIcon, TerminalLocation, TerminalSettingId, TerminalShellType, TitleEventSource, WindowsShellType } from 'vs/platform/terminal/common/terminal';
+import { escapeNonWindowsPath, collapseTildePath } from 'vs/platform/terminal/common/terminalEnvironment';
import { activeContrastBorder, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground } from 'vs/platform/theme/common/colorRegistry';
import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
@@ -86,6 +86,7 @@ import type { IMarker, ITerminalAddon, Terminal as XTermTerminal } from 'xterm';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IGenericMarkProperties } from 'vs/platform/terminal/common/terminalProcess';
import { ICommandService } from 'vs/platform/commands/common/commands';
+import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry';
const enum Constants {
/**
@@ -155,6 +156,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
private readonly _processManager: ITerminalProcessManager;
private readonly _resource: URI;
+ private _shutdownPersistentProcessId: number | undefined;
// Enables disposal of the xterm onKey
// event when the CwdDetection capability
@@ -171,6 +173,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
private _isVisible: boolean;
private _isDisposed: boolean;
private _exitCode: number | undefined;
+ private _exitReason: TerminalExitReason | undefined;
private _skipTerminalCommands: string[];
private _shellType: TerminalShellType;
private _title: string = '';
@@ -276,6 +279,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
get areLinksReady(): boolean { return this._areLinksReady; }
get initialDataEvents(): string[] | undefined { return this._initialDataEvents; }
get exitCode(): number | undefined { return this._exitCode; }
+ get exitReason(): TerminalExitReason | undefined { return this._exitReason; }
get hadFocusOnExit(): boolean { return this._hadFocusOnExit; }
get isTitleSetByProcess(): boolean { return !!this._messageTitleDisposable; }
get shellLaunchConfig(): IShellLaunchConfig { return this._shellLaunchConfig; }
@@ -353,6 +357,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
private readonly _terminalHasFixedWidth: IContextKey<boolean>,
private readonly _terminalShellTypeContextKey: IContextKey<string>,
private readonly _terminalAltBufferActiveContextKey: IContextKey<boolean>,
+ private readonly _terminalInRunCommandPicker: IContextKey<boolean>,
+ private readonly _terminalShellIntegrationEnabledContextKey: IContextKey<boolean>,
private readonly _configHelper: TerminalConfigHelper,
private _shellLaunchConfig: IShellLaunchConfig,
resource: URI | undefined,
@@ -547,7 +553,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
private _getIcon(): TerminalIcon | undefined {
if (!this._icon) {
- this._icon = this._processManager.processState >= ProcessState.Launching ? Codicon.terminal : undefined;
+ this._icon = this._processManager.processState >= ProcessState.Launching
+ ? getIconRegistry().getIcon(this._configurationService.getValue(TerminalSettingId.TabsDefaultIcon))
+ : undefined;
}
return this._icon;
}
@@ -664,8 +672,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
return TerminalInstance._lastKnownCanvasDimensions;
}
- get persistentProcessId(): number | undefined { return this._processManager.persistentProcessId; }
- get shouldPersist(): boolean { return this._processManager.shouldPersist && !this.shellLaunchConfig.isTransient; }
+ set shutdownPersistentProcessId(shutdownPersistentProcessId: number | undefined) {
+ this._shutdownPersistentProcessId = shutdownPersistentProcessId;
+ }
+ get persistentProcessId(): number | undefined { return this._processManager.persistentProcessId ?? this._shutdownPersistentProcessId; }
+ get shouldPersist(): boolean { return (this._processManager.shouldPersist || this._shutdownPersistentProcessId !== undefined) && !this.shellLaunchConfig.isTransient; }
/**
* Create xterm.js instance and attach data listeners.
@@ -691,7 +702,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
// Write initial text, deferring onLineFeed listener when applicable to avoid firing
// onLineData events containing initialText
if (this._shellLaunchConfig.initialText) {
- this.xterm.raw.writeln(this._shellLaunchConfig.initialText, () => {
+ this._writeInitialText(this.xterm, () => {
lineDataEventAddon.onLineData(e => this._onLineData.fire(e));
});
} else {
@@ -814,7 +825,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
this._linkManager.openRecentLink(type);
}
- async runRecent(type: 'command' | 'cwd'): Promise<void> {
+ async runRecent(type: 'command' | 'cwd', filterMode?: 'fuzzy' | 'contiguous', value?: string): Promise<void> {
if (!this.xterm) {
return;
}
@@ -844,7 +855,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
if (label.length === 0 || commandMap.has(label)) {
continue;
}
- let description = `${entry.cwd}`;
+ let description = collapseTildePath(entry.cwd, this._userHome, this._processManager?.os === OperatingSystem.Windows ? '\\' : '/');
if (entry.exitCode) {
// Since you cannot get the last command's exit code on pwsh, just whether it failed
// or not, -1 is treated specially as simply failed
@@ -873,7 +884,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
description,
id: entry.timestamp.toString(),
command: entry,
- buttons: entry.hasOutput ? buttons : undefined
+ buttons: entry.hasOutput() ? buttons : undefined
});
commandMap.add(label);
}
@@ -961,44 +972,67 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
}
const outputProvider = this._instantiationService.createInstance(TerminalOutputProvider);
const quickPick = this._quickInputService.createQuickPick();
- quickPick.items = items;
+ const originalItems = items;
+ quickPick.items = [...originalItems];
quickPick.sortByLabel = false;
quickPick.placeholder = placeholder;
- return new Promise<void>(r => {
- quickPick.onDidTriggerItemButton(async e => {
- if (e.button === removeFromCommandHistoryButton) {
- if (type === 'command') {
- this._instantiationService.invokeFunction(getCommandHistory)?.remove(e.item.label);
- } else {
- this._instantiationService.invokeFunction(getDirectoryHistory)?.remove(e.item.label);
- }
- } else {
- const selectedCommand = (e.item as Item).command;
- const output = selectedCommand?.getOutput();
- if (output && selectedCommand?.command) {
- const textContent = await outputProvider.provideTextContent(URI.from(
- {
- scheme: TerminalOutputProvider.scheme,
- path: `${selectedCommand.command}... ${fromNow(selectedCommand.timestamp, true)}`,
- fragment: output,
- query: `terminal-output-${selectedCommand.timestamp}-${this.instanceId}`
- }));
- if (textContent) {
- await this._editorService.openEditor({
- resource: textContent.uri
- });
- }
- }
- }
+ quickPick.customButton = true;
+ quickPick.matchOnLabelMode = filterMode || 'contiguous';
+ if (filterMode === 'fuzzy') {
+ quickPick.customLabel = nls.localize('terminal.contiguousSearch', 'Use Contiguous Search');
+ quickPick.onDidCustom(() => {
quickPick.hide();
+ this.runRecent(type, 'contiguous', quickPick.value);
});
- quickPick.onDidAccept(() => {
- const result = quickPick.activeItems[0];
- this.sendText(type === 'cwd' ? `cd ${result.label}` : result.label, !quickPick.keyMods.alt);
+ } else {
+ quickPick.customLabel = nls.localize('terminal.fuzzySearch', 'Use Fuzzy Search');
+ quickPick.onDidCustom(() => {
quickPick.hide();
+ this.runRecent(type, 'fuzzy', quickPick.value);
});
+ }
+ quickPick.onDidTriggerItemButton(async e => {
+ if (e.button === removeFromCommandHistoryButton) {
+ if (type === 'command') {
+ this._instantiationService.invokeFunction(getCommandHistory)?.remove(e.item.label);
+ } else {
+ this._instantiationService.invokeFunction(getDirectoryHistory)?.remove(e.item.label);
+ }
+ } else {
+ const selectedCommand = (e.item as Item).command;
+ const output = selectedCommand?.getOutput();
+ if (output && selectedCommand?.command) {
+ const textContent = await outputProvider.provideTextContent(URI.from(
+ {
+ scheme: TerminalOutputProvider.scheme,
+ path: `${selectedCommand.command}... ${fromNow(selectedCommand.timestamp, true)}`,
+ fragment: output,
+ query: `terminal-output-${selectedCommand.timestamp}-${this.instanceId}`
+ }));
+ if (textContent) {
+ await this._editorService.openEditor({
+ resource: textContent.uri
+ });
+ }
+ }
+ }
+ quickPick.hide();
+ });
+ quickPick.onDidAccept(() => {
+ const result = quickPick.activeItems[0];
+ this.sendText(type === 'cwd' ? `cd ${result.label}` : result.label, !quickPick.keyMods.alt);
+ quickPick.hide();
+ });
+ if (value) {
+ quickPick.value = value;
+ }
+ return new Promise<void>(r => {
quickPick.show();
- quickPick.onDidHide(() => r());
+ this._terminalInRunCommandPicker.set(true);
+ quickPick.onDidHide(() => {
+ this._terminalInRunCommandPicker.set(false);
+ r();
+ });
});
}
@@ -1052,6 +1086,13 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
const screenElement = xterm.attachToElement(xtermElement);
xterm.onDidChangeFindResults((results) => this._onDidChangeFindResults.fire(results));
+ xterm.shellIntegration.onDidChangeStatus(() => {
+ if (this.hasFocus) {
+ this._setShellIntegrationContextKey();
+ } else {
+ this._terminalShellIntegrationEnabledContextKey.reset();
+ }
+ });
if (!xterm.raw.element || !xterm.raw.textarea) {
throw new Error('xterm elements not set after open');
@@ -1185,16 +1226,25 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
private _setFocus(focused?: boolean): void {
if (focused) {
this._terminalFocusContextKey.set(true);
+ this._setShellIntegrationContextKey();
this._onDidFocus.fire(this);
} else {
- this._terminalFocusContextKey.reset();
+ this.resetFocusContextKey();
this._onDidBlur.fire(this);
this._refreshSelectionContextKey();
}
}
+ private _setShellIntegrationContextKey(): void {
+ console.log('set', this.xterm?.shellIntegration.status === ShellIntegrationStatus.VSCode);
+ if (this.xterm) {
+ this._terminalShellIntegrationEnabledContextKey.set(this.xterm.shellIntegration.status === ShellIntegrationStatus.VSCode);
+ }
+ }
+
resetFocusContextKey(): void {
this._terminalFocusContextKey.reset();
+ this._terminalShellIntegrationEnabledContextKey.reset();
}
private _initDragAndDrop(container: HTMLElement) {
@@ -1235,6 +1285,21 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
}
}
+ async copyLastCommandOutput(): Promise<void> {
+ const commands = this.capabilities.get(TerminalCapability.CommandDetection)?.commands;
+ if (!commands || commands.length === 0) {
+ return;
+ }
+ const command = commands[commands.length - 1];
+ if (!command?.hasOutput()) {
+ return;
+ }
+ const output = command.getOutput();
+ if (output) {
+ await this._clipboardService.writeText(output);
+ }
+ }
+
get selection(): string | undefined {
return this.xterm && this.hasSelection() ? this.xterm.raw.getSelection() : undefined;
}
@@ -1255,6 +1320,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
}
const terminalFocused = !isFocused && (document.activeElement === this.xterm.raw.textarea || document.activeElement === this.xterm.raw.element);
this._terminalFocusContextKey.set(terminalFocused);
+ if (terminalFocused) {
+ this._setShellIntegrationContextKey();
+ } else {
+ this._terminalShellIntegrationEnabledContextKey.reset();
+ }
}
private _refreshAltBufferContextKey() {
@@ -1310,8 +1380,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
return confirmation.confirmed;
}
-
- override dispose(immediate?: boolean): void {
+ override dispose(reason?: TerminalExitReason): void {
this._logService.trace(`terminalInstance#dispose (instanceId: ${this.instanceId})`);
dispose(this._linkManager);
this._linkManager = undefined;
@@ -1335,7 +1404,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
// as 'blur' event in xterm.raw.textarea is not triggered on xterm.dispose()
// See https://github.com/microsoft/vscode/issues/138358
if (isFirefox) {
- this._terminalFocusContextKey.reset();
+ this.resetFocusContextKey();
this._terminalHasTextContextKey.reset();
this._onDidBlur.fire(this);
}
@@ -1345,7 +1414,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
this._pressAnyKeyToCloseListener = undefined;
}
- this._processManager.dispose(immediate);
+ if (this._exitReason === undefined) {
+ this._exitReason = reason ?? TerminalExitReason.Unknown;
+ }
+
+ this._processManager.dispose();
// Process manager dispose/shutdown doesn't fire process exit, trigger with undefined if it
// hasn't happened yet
this._onProcessExit(undefined);
@@ -1357,11 +1430,11 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
super.dispose();
}
- async detachFromProcess(): Promise<void> {
+ async detachProcessAndDispose(reason: TerminalExitReason): Promise<void> {
// Detach the process and dispose the instance, without the instance dispose the terminal
// won't go away
await this._processManager.detachFromProcess();
- this.dispose();
+ this.dispose(reason);
}
focus(force?: boolean): void {
@@ -1416,7 +1489,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
async sendText(text: string, addNewLine: boolean): Promise<void> {
// Normalize line endings to 'enter' press.
text = text.replace(/\r?\n/g, '\r');
- if (addNewLine && text.substr(text.length - 1) !== '\r') {
+ if (addNewLine && text[text.length - 1] !== '\r') {
text += '\r';
}
@@ -1657,7 +1730,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
const parsedExitResult = parseExitResult(exitCodeOrError, this.shellLaunchConfig, this._processManager.processState, this._initialCwd);
- if (this._usedShellIntegrationInjection && (this._processManager.processState === ProcessState.KilledDuringLaunch || this._processManager.processState === ProcessState.KilledByProcess)) {
+ if (this._usedShellIntegrationInjection && this._processManager.processState === ProcessState.KilledDuringLaunch && parsedExitResult?.code !== 0) {
this._relaunchWithShellIntegrationDisabled(parsedExitResult?.message);
this._onExit.fire(exitCodeOrError);
return;
@@ -1697,7 +1770,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
}
});
} else {
- this.dispose();
+ this.dispose(TerminalExitReason.Process);
if (exitMessage) {
const failedDuringLaunch = this._processManager.processState === ProcessState.KilledDuringLaunch;
if (failedDuringLaunch || this._configHelper.config.showExitAlert) {
@@ -1773,36 +1846,56 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
if (this._pressAnyKeyToCloseListener) {
this._pressAnyKeyToCloseListener.dispose();
this._pressAnyKeyToCloseListener = undefined;
- this.dispose();
+ this.dispose(TerminalExitReason.Process);
event.preventDefault();
}
});
}
}
+ private _writeInitialText(xterm: XtermTerminal, callback?: () => void): void {
+ if (!this._shellLaunchConfig.initialText) {
+ callback?.();
+ return;
+ }
+ const text = typeof this._shellLaunchConfig.initialText === 'string'
+ ? this._shellLaunchConfig.initialText
+ : this._shellLaunchConfig.initialText?.text;
+ if (typeof this._shellLaunchConfig.initialText === 'string') {
+ xterm.raw.writeln(text, callback);
+ } else {
+ if (this._shellLaunchConfig.initialText.trailingNewLine) {
+ xterm.raw.writeln(text, callback);
+ } else {
+ xterm.raw.write(text, callback);
+ }
+ }
+ }
+
async reuseTerminal(shell: IShellLaunchConfig, reset: boolean = false): Promise<void> {
// Unsubscribe any key listener we may have.
this._pressAnyKeyToCloseListener?.dispose();
this._pressAnyKeyToCloseListener = undefined;
- if (this.xterm) {
+ const xterm = this.xterm;
+ if (xterm) {
if (!reset) {
// Ensure new processes' output starts at start of new line
- await new Promise<void>(r => this.xterm!.raw.write('\n\x1b[G', r));
+ await new Promise<void>(r => xterm.raw.write('\n\x1b[G', r));
}
// Print initialText if specified
if (shell.initialText) {
- await new Promise<void>(r => this.xterm!.raw.writeln(shell.initialText!, r));
+ await new Promise<void>(r => this._writeInitialText(xterm, r));
}
// Clean up waitOnExit state
if (this._isExiting && this._shellLaunchConfig.waitOnExit) {
- this.xterm.raw.options.disableStdin = false;
+ xterm.raw.options.disableStdin = false;
this._isExiting = false;
}
if (reset) {
- this.xterm.clearDecorations();
+ xterm.clearDecorations();
}
}
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts
index c9da9557b27..a9feaab7aa1 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalInstanceService.ts
@@ -23,6 +23,8 @@ export class TerminalInstanceService extends Disposable implements ITerminalInst
private _terminalHasFixedWidth: IContextKey<boolean>;
private _terminalShellTypeContextKey: IContextKey<string>;
private _terminalAltBufferActiveContextKey: IContextKey<boolean>;
+ private _terminalInRunCommandPicker: IContextKey<boolean>;
+ private _terminalShellIntegrationEnabled: IContextKey<boolean>;
private _configHelper: TerminalConfigHelper;
private readonly _onDidCreateInstance = new Emitter<ITerminalInstance>();
@@ -37,6 +39,8 @@ export class TerminalInstanceService extends Disposable implements ITerminalInst
this._terminalHasFixedWidth = TerminalContextKeys.terminalHasFixedWidth.bindTo(this._contextKeyService);
this._terminalShellTypeContextKey = TerminalContextKeys.shellType.bindTo(this._contextKeyService);
this._terminalAltBufferActiveContextKey = TerminalContextKeys.altBufferActive.bindTo(this._contextKeyService);
+ this._terminalInRunCommandPicker = TerminalContextKeys.inTerminalRunCommandPicker.bindTo(this._contextKeyService);
+ this._terminalShellIntegrationEnabled = TerminalContextKeys.terminalShellIntegrationEnabled.bindTo(this._contextKeyService);
this._configHelper = _instantiationService.createInstance(TerminalConfigHelper);
}
@@ -49,6 +53,8 @@ export class TerminalInstanceService extends Disposable implements ITerminalInst
this._terminalHasFixedWidth,
this._terminalShellTypeContextKey,
this._terminalAltBufferActiveContextKey,
+ this._terminalInRunCommandPicker,
+ this._terminalShellIntegrationEnabled,
this._configHelper,
shellLaunchConfig,
resource
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts
index 5183542cb14..af2d55a2242 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalMenus.ts
@@ -417,6 +417,7 @@ export function setupTerminalMenus(): void {
order: 2,
when: ContextKeyExpr.and(
ContextKeyExpr.equals('view', TERMINAL_VIEW_ID),
+ ContextKeyExpr.notEquals(`config.${TerminalSettingId.TabsHideCondition}`, 'never'),
ContextKeyExpr.or(
ContextKeyExpr.not(`config.${TerminalSettingId.TabsEnabled}`),
ContextKeyExpr.and(
@@ -451,6 +452,7 @@ export function setupTerminalMenus(): void {
order: 3,
when: ContextKeyExpr.and(
ContextKeyExpr.equals('view', TERMINAL_VIEW_ID),
+ ContextKeyExpr.notEquals(`config.${TerminalSettingId.TabsHideCondition}`, 'never'),
ContextKeyExpr.or(
ContextKeyExpr.not(`config.${TerminalSettingId.TabsEnabled}`),
ContextKeyExpr.and(
@@ -750,11 +752,11 @@ export function getTerminalActionBarArgs(location: ITerminalLocationOptions, pro
shouldForwardArgs: true
};
if (isDefault) {
- dropdownActions.unshift(new MenuItemAction({ id: TerminalCommandId.NewWithProfile, title: localize('defaultTerminalProfile', "{0} (Default)", p.profileName), category: TerminalTabContextMenuGroup.Profile }, undefined, options, contextKeyService, commandService));
- submenuActions.unshift(new MenuItemAction({ id: TerminalCommandId.Split, title: localize('defaultTerminalProfile', "{0} (Default)", p.profileName), category: TerminalTabContextMenuGroup.Profile }, undefined, splitOptions, contextKeyService, commandService));
+ dropdownActions.unshift(new MenuItemAction({ id: TerminalCommandId.NewWithProfile, title: localize('defaultTerminalProfile', "{0} (Default)", p.profileName), category: TerminalTabContextMenuGroup.Profile }, undefined, options, undefined, contextKeyService, commandService));
+ submenuActions.unshift(new MenuItemAction({ id: TerminalCommandId.Split, title: localize('defaultTerminalProfile', "{0} (Default)", p.profileName), category: TerminalTabContextMenuGroup.Profile }, undefined, splitOptions, undefined, contextKeyService, commandService));
} else {
- dropdownActions.push(new MenuItemAction({ id: TerminalCommandId.NewWithProfile, title: p.profileName.replace(/[\n\r\t]/g, ''), category: TerminalTabContextMenuGroup.Profile }, undefined, options, contextKeyService, commandService));
- submenuActions.push(new MenuItemAction({ id: TerminalCommandId.Split, title: p.profileName.replace(/[\n\r\t]/g, ''), category: TerminalTabContextMenuGroup.Profile }, undefined, splitOptions, contextKeyService, commandService));
+ dropdownActions.push(new MenuItemAction({ id: TerminalCommandId.NewWithProfile, title: p.profileName.replace(/[\n\r\t]/g, ''), category: TerminalTabContextMenuGroup.Profile }, undefined, options, undefined, contextKeyService, commandService));
+ submenuActions.push(new MenuItemAction({ id: TerminalCommandId.Split, title: p.profileName.replace(/[\n\r\t]/g, ''), category: TerminalTabContextMenuGroup.Profile }, undefined, splitOptions, undefined, contextKeyService, commandService));
}
}
@@ -821,7 +823,8 @@ export function getTerminalActionBarArgs(location: ITerminalLocationOptions, pro
{
shouldForwardArgs: true,
arg: { location } as ICreateTerminalOptions,
- });
+ },
+ undefined);
const dropdownAction = new Action('refresh profiles', 'Launch Profile...', 'codicon-chevron-down', true);
return { primaryAction, dropdownAction, dropdownMenuActions: dropdownActions, className: `terminal-tab-actions-${terminalService.resolveLocation(location)}` };
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts
index 3e2725c70ff..ba6540e654c 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts
@@ -292,7 +292,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
}
} else {
if (shellLaunchConfig.attachPersistentProcess) {
- const result = await backend.attachToProcess(shellLaunchConfig.attachPersistentProcess.id);
+ const result = shellLaunchConfig.attachPersistentProcess.findRevivedId ? await backend.attachToRevivedProcess(shellLaunchConfig.attachPersistentProcess.id) : await backend.attachToProcess(shellLaunchConfig.attachPersistentProcess.id);
if (result) {
newProcess = result;
} else {
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts
index e715b730b66..e67654f2c70 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileQuickpick.ts
@@ -11,7 +11,7 @@ import { getUriClasses, getColorClass, getColorStyleElement } from 'vs/workbench
import { configureTerminalProfileIcon } from 'vs/workbench/contrib/terminal/browser/terminalIcons';
import * as nls from 'vs/nls';
import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
-import { ITerminalProfileService } from 'vs/workbench/contrib/terminal/common/terminal';
+import { ITerminalProfileResolverService, ITerminalProfileService } from 'vs/workbench/contrib/terminal/common/terminal';
import { IQuickPickTerminalObject, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal';
import { IPickerQuickAccessItem } from 'vs/platform/quickinput/browser/pickerQuickAccess';
import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry';
@@ -22,6 +22,7 @@ type DefaultProfileName = string;
export class TerminalProfileQuickpick {
constructor(
@ITerminalProfileService private readonly _terminalProfileService: ITerminalProfileService,
+ @ITerminalProfileResolverService private readonly _terminalProfileResolverService: ITerminalProfileResolverService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@IQuickInputService private readonly _quickInputService: IQuickInputService,
@IThemeService private readonly _themeService: IThemeService
@@ -155,7 +156,7 @@ export class TerminalProfileQuickpick {
}
}
if (!icon || !getIconRegistry().getIcon(icon.id)) {
- icon = Codicon.terminal;
+ icon = this._terminalProfileResolverService.getDefaultIcon();
}
const uriClasses = getUriClasses(contributed, this._themeService.getColorTheme().type, true);
const colorClass = getColorClass(contributed);
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts
index 0849f6676b2..f920f87442f 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts
@@ -16,6 +16,7 @@ import { IShellLaunchConfig, ITerminalProfile, ITerminalProfileObject, TerminalI
import { IShellLaunchConfigResolveOptions, ITerminalProfileResolverService, ITerminalProfileService } from 'vs/workbench/contrib/terminal/common/terminal';
import * as path from 'vs/base/common/path';
import { Codicon } from 'vs/base/common/codicons';
+import { getIconRegistry, IIconRegistry } from 'vs/platform/theme/common/iconRegistry';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { debounce } from 'vs/base/common/decorators';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
@@ -51,6 +52,8 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro
private _primaryBackendOs: OperatingSystem | undefined;
+ private readonly _iconRegistry: IIconRegistry = getIconRegistry();
+
private _defaultProfileName: string | undefined;
get defaultProfileName(): string | undefined { return this._defaultProfileName; }
@@ -94,11 +97,11 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro
resolveIcon(shellLaunchConfig: IShellLaunchConfig, os: OperatingSystem): void {
if (shellLaunchConfig.icon) {
- shellLaunchConfig.icon = this._getCustomIcon(shellLaunchConfig.icon) || Codicon.terminal;
+ shellLaunchConfig.icon = this._getCustomIcon(shellLaunchConfig.icon) || this.getDefaultIcon();
return;
}
if (shellLaunchConfig.customPtyImplementation) {
- shellLaunchConfig.icon = Codicon.terminal;
+ shellLaunchConfig.icon = this.getDefaultIcon();
return;
}
if (shellLaunchConfig.executable) {
@@ -108,6 +111,13 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro
if (defaultProfile) {
shellLaunchConfig.icon = defaultProfile.icon;
}
+ if (!shellLaunchConfig.icon) {
+ shellLaunchConfig.icon = this.getDefaultIcon();
+ }
+ }
+
+ getDefaultIcon(): TerminalIcon & ThemeIcon {
+ return this._iconRegistry.getIcon(this._configurationService.getValue(TerminalSettingId.TabsDefaultIcon)) || Codicon.terminal;
}
async resolveShellLaunchConfig(shellLaunchConfig: IShellLaunchConfig, options: IShellLaunchConfigResolveOptions): Promise<void> {
@@ -135,7 +145,9 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro
// Verify the icon is valid, and fallback correctly to the generic terminal id if there is
// an issue
- shellLaunchConfig.icon = this._getCustomIcon(shellLaunchConfig.icon) || this._getCustomIcon(resolvedProfile.icon) || Codicon.terminal;
+ shellLaunchConfig.icon = this._getCustomIcon(shellLaunchConfig.icon)
+ || this._getCustomIcon(resolvedProfile.icon)
+ || this.getDefaultIcon();
// Override the name if specified
if (resolvedProfile.overrideName) {
@@ -143,7 +155,9 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro
}
// Apply the color
- shellLaunchConfig.color = shellLaunchConfig.color || resolvedProfile.color;
+ shellLaunchConfig.color = shellLaunchConfig.color
+ || resolvedProfile.color
+ || this._configurationService.getValue(TerminalSettingId.TabsDefaultColor);
// Resolve useShellEnvironment based on the setting if it's not set
if (shellLaunchConfig.useShellEnvironment === undefined) {
@@ -232,6 +246,7 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro
if (defaultProfileName && typeof defaultProfileName === 'string') {
return this._terminalProfileService.availableProfiles.find(e => e.profileName === defaultProfileName);
}
+
return undefined;
}
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts
index 60c49b4b34b..7bdc8975d82 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileService.ts
@@ -67,7 +67,7 @@ export class TerminalProfileService implements ITerminalProfileService {
// Wait up to 5 seconds for profiles to be ready so it's assured that we know the actual
// default terminal before launching the first terminal. This isn't expected to ever take
// this long.
- this._profilesReadyBarrier = new AutoOpenBarrier(5000);
+ this._profilesReadyBarrier = new AutoOpenBarrier(20000);
this.refreshAvailableProfiles();
this._setupConfigListener();
}
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts b/src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts
index af7953a3268..ff5fbd96df8 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalQuickAccess.ts
@@ -7,7 +7,7 @@ import { localize } from 'vs/nls';
import { IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
import { IPickerQuickAccessItem, PickerQuickAccessProvider, TriggerAction } from 'vs/platform/quickinput/browser/pickerQuickAccess';
import { matchesFuzzy } from 'vs/base/common/filters';
-import { ITerminalEditorService, ITerminalGroupService, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal';
+import { ITerminalEditorService, ITerminalGroupService, ITerminalInstance, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal';
import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
@@ -16,6 +16,7 @@ import { getColorClass, getIconId, getUriClasses } from 'vs/workbench/contrib/te
import { terminalStrings } from 'vs/workbench/contrib/terminal/common/terminalStrings';
import { TerminalLocation } from 'vs/platform/terminal/common/terminal';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
+import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
let terminalPicks: Array<IPickerQuickAccessItem | IQuickPickSeparator> = [];
export class TerminalQuickAccessProvider extends PickerQuickAccessProvider<IPickerQuickAccessItem> {
@@ -24,10 +25,12 @@ export class TerminalQuickAccessProvider extends PickerQuickAccessProvider<IPick
constructor(
@IEditorService private readonly _editorService: IEditorService,
+ @ITerminalEditorService private readonly _terminalService: ITerminalService,
@ITerminalEditorService private readonly _terminalEditorService: ITerminalEditorService,
@ITerminalGroupService private readonly _terminalGroupService: ITerminalGroupService,
@ICommandService private readonly _commandService: ICommandService,
- @IThemeService private readonly _themeService: IThemeService
+ @IThemeService private readonly _themeService: IThemeService,
+ @IInstantiationService private readonly _instantiationService: IInstantiationService
) {
super(TerminalQuickAccessProvider.PREFIX, { canAcceptInBackground: true });
}
@@ -80,7 +83,7 @@ export class TerminalQuickAccessProvider extends PickerQuickAccessProvider<IPick
}
private _createPick(terminal: ITerminalInstance, terminalIndex: number, filter: string, groupInfo?: { groupIndex: number; groupSize: number }): IPickerQuickAccessItem | undefined {
- const iconId = getIconId(terminal);
+ const iconId = this._instantiationService.invokeFunction(getIconId, terminal);
const index = groupInfo
? (groupInfo.groupSize > 1
? `${groupInfo.groupIndex + 1}.${terminalIndex + 1}`
@@ -119,7 +122,7 @@ export class TerminalQuickAccessProvider extends PickerQuickAccessProvider<IPick
this._commandService.executeCommand(TerminalCommandId.Rename, terminal);
return TriggerAction.NO_ACTION;
case 1:
- terminal.dispose(true);
+ this._terminalService.safeDisposeTerminal(terminal);
return TriggerAction.REMOVE_ITEM;
}
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts
index 81d40e49f91..7ed18631566 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts
@@ -20,7 +20,8 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
import { INotificationService } from 'vs/platform/notification/common/notification';
-import { ICreateContributedTerminalProfileOptions, IShellLaunchConfig, ITerminalLaunchError, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalLocation, TerminalLocationString, TitleEventSource } from 'vs/platform/terminal/common/terminal';
+import { ICreateContributedTerminalProfileOptions, IShellLaunchConfig, ITerminalLaunchError, ITerminalsLayoutInfo, ITerminalsLayoutInfoById, TerminalExitReason, TerminalLocation, TerminalLocationString, TitleEventSource } from 'vs/platform/terminal/common/terminal';
+import { formatMessageForTerminal } from 'vs/platform/terminal/common/terminalStrings';
import { iconForeground } from 'vs/platform/theme/common/colorRegistry';
import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry';
import { ColorScheme } from 'vs/platform/theme/common/theme';
@@ -38,7 +39,6 @@ import { getInstanceFromResource, getTerminalUri, parseTerminalUri } from 'vs/wo
import { TerminalViewPane } from 'vs/workbench/contrib/terminal/browser/terminalView';
import { IRemoteTerminalAttachTarget, IStartExtensionTerminalRequest, ITerminalBackend, ITerminalConfigHelper, ITerminalProcessExtHostProxy, ITerminalProfileService, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal';
import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey';
-import { formatMessageForTerminal } from 'vs/platform/terminal/common/terminalStrings';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
@@ -282,7 +282,7 @@ export class TerminalService implements ITerminalService {
} else {
this._terminalGroupService.getGroupForInstance(instanceToDetach)?.removeInstance(instanceToDetach);
}
- await instanceToDetach.detachFromProcess();
+ await instanceToDetach.detachProcessAndDispose(TerminalExitReason.User);
await this._primaryBackend?.acceptDetachInstanceReply(e.requestId, persistentProcessId);
} else {
// will get rejected without a persistentProcessId to attach to
@@ -371,7 +371,7 @@ export class TerminalService implements ITerminalService {
}
return new Promise<void>(r => {
instance.onExit(() => r());
- instance.dispose();
+ instance.dispose(TerminalExitReason.User);
});
}
@@ -615,14 +615,19 @@ export class TerminalService implements ITerminalService {
const shouldPersistTerminals = this._configHelper.config.enablePersistentSessions && e.reason === ShutdownReason.RELOAD;
if (shouldPersistTerminals) {
for (const instance of this.instances) {
- instance.detachFromProcess();
+ instance.detachProcessAndDispose(TerminalExitReason.Shutdown);
}
return;
}
+
// Force dispose of all terminal instances
+ const shouldPersistTerminalsForEvent = this._shouldReviveProcesses(e.reason);
for (const instance of this.instances) {
- instance.dispose();
+ if (shouldPersistTerminalsForEvent) {
+ instance.shutdownPersistentProcessId = instance.persistentProcessId;
+ }
+ instance.dispose(TerminalExitReason.Shutdown);
}
// Clear terminal layout info only when not persisting
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts
index d9fc17533ed..14890b1b9bf 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts
@@ -309,7 +309,7 @@ class TerminalTabsRenderer implements IListRenderer<ITerminalInstance, ITerminal
}
const shellIntegrationString = getShellIntegrationTooltip(instance, true, this._configurationService);
- const iconId = getIconId(instance);
+ const iconId = this._instantiationService.invokeFunction(getIconId, instance);
const hasActionbar = !this.shouldHideActionBar();
let label: string = '';
if (!hasText) {
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts
index f7fc381a88f..9dc9017d20f 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts
@@ -45,6 +45,7 @@ import { withNullAsUndefined } from 'vs/base/common/types';
import { getTerminalActionBarArgs } from 'vs/workbench/contrib/terminal/browser/terminalMenus';
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';
export class TerminalViewPane extends ViewPane {
private _actions: IAction[] | undefined;
@@ -372,14 +373,15 @@ class SingleTerminalTabActionViewItem extends MenuEntryActionViewItem {
@IThemeService themeService: IThemeService,
@ITerminalService private readonly _terminalService: ITerminalService,
@ITerminalGroupService private readonly _terminalGroupService: ITerminalGroupService,
- @IContextMenuService private readonly _contextMenuService: IContextMenuService,
+ @IContextMenuService contextMenuService: IContextMenuService,
@ICommandService private readonly _commandService: ICommandService,
- @IConfigurationService configurationService: IConfigurationService
+ @IConfigurationService configurationService: IConfigurationService,
+ @IInstantiationService private readonly _instantiationService: IInstantiationService,
) {
super(new MenuItemAction(
{
id: action.id,
- title: getSingleTabLabel(_terminalGroupService.activeInstance, _terminalService.configHelper.config.tabs.separator),
+ title: _instantiationService.invokeFunction(getSingleTabLabel, _terminalGroupService.activeInstance, _terminalService.configHelper.config.tabs.separator),
tooltip: getSingleTabTooltip(_terminalGroupService.activeInstance, _terminalService.configHelper.config.tabs.separator, configurationService)
},
{
@@ -388,11 +390,12 @@ class SingleTerminalTabActionViewItem extends MenuEntryActionViewItem {
icon: Codicon.splitHorizontal
},
undefined,
+ undefined,
contextKeyService,
_commandService
), {
draggable: true
- }, keybindingService, notificationService, contextKeyService, themeService);
+ }, keybindingService, notificationService, contextKeyService, themeService, contextMenuService);
// Register listeners to update the tab
this._register(this._terminalService.onDidChangeInstancePrimaryStatus(e => this.updateLabel(e)));
@@ -473,7 +476,7 @@ class SingleTerminalTabActionViewItem extends MenuEntryActionViewItem {
}
}
label.style.color = colorStyle;
- dom.reset(label, ...renderLabelWithIcons(getSingleTabLabel(instance, this._terminalService.configHelper.config.tabs.separator, ThemeIcon.isThemeIcon(this._commandAction.item.icon) ? this._commandAction.item.icon : undefined)));
+ dom.reset(label, ...renderLabelWithIcons(this._instantiationService.invokeFunction(getSingleTabLabel, instance, this._terminalService.configHelper.config.tabs.separator, ThemeIcon.isThemeIcon(this._commandAction.item.icon) ? this._commandAction.item.icon : undefined)));
if (this._altCommand) {
label.classList.remove(this._altCommand);
@@ -515,13 +518,13 @@ class SingleTerminalTabActionViewItem extends MenuEntryActionViewItem {
}
}
-function getSingleTabLabel(instance: ITerminalInstance | undefined, separator: string, icon?: ThemeIcon) {
+function getSingleTabLabel(accessor: ServicesAccessor, instance: ITerminalInstance | undefined, separator: string, icon?: ThemeIcon) {
// Don't even show the icon if there is no title as the icon would shift around when the title
// is added
if (!instance || !instance.title) {
return '';
}
- const iconClass = ThemeIcon.isThemeIcon(instance.icon) ? instance.icon?.id : Codicon.terminal.id;
+ const iconClass = ThemeIcon.isThemeIcon(instance.icon) ? instance.icon.id : accessor.get(ITerminalProfileResolverService).getDefaultIcon();
const label = `$(${icon?.id || iconClass}) ${getSingleTabTitle(instance, separator)}`;
const primaryStatus = instance.statusList.primary;
diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/commandNavigationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/commandNavigationAddon.ts
index 08174e3b53c..83de46d909f 100644
--- a/src/vs/workbench/contrib/terminal/browser/xterm/commandNavigationAddon.ts
+++ b/src/vs/workbench/contrib/terminal/browser/xterm/commandNavigationAddon.ts
@@ -34,9 +34,9 @@ export class CommandNavigationAddon extends Disposable implements ICommandTracke
activate(terminal: Terminal): void {
this._terminal = terminal;
- this._terminal.onData(() => {
+ this._register(this._terminal.onData(() => {
this._currentMarker = Boundary.Bottom;
- });
+ }));
}
constructor(
diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts
index 0d033529f59..45b820a935c 100644
--- a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts
+++ b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts
@@ -12,7 +12,7 @@ import { CommandInvalidationReason, ITerminalCapabilityStore, TerminalCapability
import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IHoverService } from 'vs/workbench/services/hover/browser/hover';
-import { IAction } from 'vs/base/common/actions';
+import { IAction, Separator } from 'vs/base/common/actions';
import { Emitter } from 'vs/base/common/event';
import { MarkdownString } from 'vs/base/common/htmlContent';
import { localize } from 'vs/nls';
@@ -353,24 +353,39 @@ export class DecorationAddon extends Disposable implements ITerminalAddon {
private async _getCommandActions(command: ITerminalCommand): Promise<IAction[]> {
const actions: IAction[] = [];
- if (command.hasOutput) {
+ if (command.command !== '') {
+ const labelRun = localize("terminal.rerunCommand", 'Rerun Command');
actions.push({
- class: 'copy-output', tooltip: 'Copy Output', dispose: () => { }, id: 'terminal.copyOutput', label: localize("terminal.copyOutput", 'Copy Output'), enabled: true,
- run: () => this._clipboardService.writeText(command.getOutput()!)
+ class: undefined, tooltip: labelRun, dispose: () => { }, id: 'terminal.rerunCommand', label: labelRun, enabled: true,
+ run: () => this._onDidRequestRunCommand.fire({ command })
});
+ const labelCopy = localize("terminal.copyCommand", 'Copy Command');
actions.push({
- class: 'copy-output', tooltip: 'Copy Output as HTML', dispose: () => { }, id: 'terminal.copyOutputAsHtml', label: localize("terminal.copyOutputAsHtml", 'Copy Output as HTML'), enabled: true,
- run: () => this._onDidRequestRunCommand.fire({ command, copyAsHtml: true })
+ class: undefined, tooltip: labelCopy, dispose: () => { }, id: 'terminal.copyCommand', label: labelCopy, enabled: true,
+ run: () => this._clipboardService.writeText(command.command)
});
}
- if (command.command !== '') {
+ if (command.hasOutput()) {
+ if (actions.length > 0) {
+ actions.push(new Separator());
+ }
+ const labelText = localize("terminal.copyOutput", 'Copy Output');
actions.push({
- class: 'rerun-command', tooltip: 'Rerun Command', dispose: () => { }, id: 'terminal.rerunCommand', label: localize("terminal.rerunCommand", 'Rerun Command'), enabled: true,
- run: () => this._onDidRequestRunCommand.fire({ command })
+ class: undefined, tooltip: labelText, dispose: () => { }, id: 'terminal.copyOutput', label: labelText, enabled: true,
+ run: () => this._clipboardService.writeText(command.getOutput()!)
+ });
+ const labelHtml = localize("terminal.copyOutputAsHtml", 'Copy Output as HTML');
+ actions.push({
+ class: undefined, tooltip: labelHtml, dispose: () => { }, id: 'terminal.copyOutputAsHtml', label: labelHtml, enabled: true,
+ run: () => this._onDidRequestRunCommand.fire({ command, copyAsHtml: true })
});
}
+ if (actions.length > 0) {
+ actions.push(new Separator());
+ }
+ const label = localize("terminal.learnShellIntegration", 'Learn About Shell Integration');
actions.push({
- class: 'how-does-this-work', tooltip: 'How does this work?', dispose: () => { }, id: 'terminal.howDoesThisWork', label: localize("terminal.howDoesThisWork", 'How does this work?'), enabled: true,
+ class: undefined, tooltip: label, dispose: () => { }, id: 'terminal.learnShellIntegration', label, enabled: true,
run: () => this._openerService.open('https://code.visualstudio.com/docs/editor/integrated-terminal#_shell-integration')
});
return actions;
diff --git a/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts b/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts
index e8c7da68932..1b62d1e9cb3 100644
--- a/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts
+++ b/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts
@@ -299,6 +299,10 @@ export class RemoteTerminalChannelClient implements IPtyHostController {
return this._channel.call('$reviveTerminalProcesses', [state, dateTimeFormatLocate]);
}
+ getRevivedPtyNewId(id: number): Promise<number | undefined> {
+ return this._channel.call('$getRevivedPtyNewId', [id]);
+ }
+
serializeTerminalState(ids: number[]): Promise<string> {
return this._channel.call('$serializeTerminalState', [ids]);
}
diff --git a/src/vs/workbench/contrib/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts
index 829b45a66d0..e935775fcd3 100644
--- a/src/vs/workbench/contrib/terminal/common/terminal.ts
+++ b/src/vs/workbench/contrib/terminal/common/terminal.ts
@@ -15,6 +15,7 @@ import { URI } from 'vs/base/common/uri';
import { IGenericMarkProperties, IProcessDetails, ISerializedCommandDetectionCapability } from 'vs/platform/terminal/common/terminalProcess';
import { Registry } from 'vs/platform/registry/common/platform';
import { ITerminalCapabilityStore, IXtermMarker } from 'vs/platform/terminal/common/capabilities/capabilities';
+import { ThemeIcon } from 'vs/platform/theme/common/themeService';
export const TERMINAL_VIEW_ID = 'terminal';
@@ -54,6 +55,7 @@ export interface ITerminalProfileResolverService {
getDefaultProfile(options: IShellLaunchConfigResolveOptions): Promise<ITerminalProfile>;
getDefaultShell(options: IShellLaunchConfigResolveOptions): Promise<string>;
getDefaultShellArgs(options: IShellLaunchConfigResolveOptions): Promise<string | string[]>;
+ getDefaultIcon(): TerminalIcon & ThemeIcon;
getEnvironment(remoteAuthority: string | undefined): Promise<IProcessEnvironment>;
createProfileFromShellAndShellArgs(shell?: unknown, shellArgs?: unknown): Promise<ITerminalProfile | string>;
}
@@ -116,6 +118,7 @@ export interface ITerminalBackend {
onDidRequestDetach: Event<{ requestId: number; workspaceId: string; instanceId: number }>;
attachToProcess(id: number): Promise<ITerminalChildProcess | undefined>;
+ attachToRevivedProcess(id: number): Promise<ITerminalChildProcess | undefined>;
listProcesses(): Promise<IProcessDetails[]>;
getDefaultSystemShell(osOverride?: OperatingSystem): Promise<string>;
getProfiles(profiles: unknown, defaultProfile: unknown, includeDetectedProfiles?: boolean): Promise<ITerminalProfile[]>;
@@ -340,7 +343,7 @@ export interface ITerminalCommand {
cwd?: string;
exitCode?: number;
marker?: IXtermMarker;
- hasOutput: boolean;
+ hasOutput(): boolean;
getOutput(): string | undefined;
genericMarkProperties?: IGenericMarkProperties;
}
@@ -475,6 +478,7 @@ export const enum TerminalCommandId {
OpenFileLink = 'workbench.action.terminal.openFileLink',
OpenWebLink = 'workbench.action.terminal.openUrlLink',
RunRecentCommand = 'workbench.action.terminal.runRecentCommand',
+ CopyLastCommand = 'workbench.action.terminal.copyLastCommand',
GoToRecentDirectory = 'workbench.action.terminal.goToRecentDirectory',
CopySelection = 'workbench.action.terminal.copySelection',
CopySelectionAsHtml = 'workbench.action.terminal.copySelectionAsHtml',
@@ -573,6 +577,7 @@ export const DEFAULT_COMMANDS_TO_SKIP_SHELL: string[] = [
TerminalCommandId.Clear,
TerminalCommandId.CopySelection,
TerminalCommandId.CopySelectionAsHtml,
+ TerminalCommandId.CopyLastCommand,
TerminalCommandId.DeleteToLineStart,
TerminalCommandId.DeleteWordLeft,
TerminalCommandId.DeleteWordRight,
diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts
index efe3aa79bd3..3c5513e7c90 100644
--- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts
+++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts
@@ -9,6 +9,8 @@ import { DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, TerminalCursorStyle, DEFAU
import { TerminalLocationString, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
import { isMacintosh, isWindows } from 'vs/base/common/platform';
import { Registry } from 'vs/platform/registry/common/platform';
+import { Codicon } from 'vs/base/common/codicons';
+import { terminalColorSchema, terminalIconSchema } from 'vs/platform/terminal/common/terminalPlatformConfiguration';
const terminalDescriptors = '\n- ' + [
'`\${cwd}`: ' + localize("cwd", "the terminal's current working directory"),
@@ -34,10 +36,19 @@ const terminalConfiguration: IConfigurationNode = {
type: 'object',
properties: {
[TerminalSettingId.SendKeybindingsToShell]: {
- markdownDescription: localize('terminal.integrated.sendKeybindingsToShell', "Dispatches most keybindings to the terminal instead of the workbench, overriding `#terminal.integrated.commandsToSkipShell#`, which can be used alternatively for fine tuning."),
+ markdownDescription: localize('terminal.integrated.sendKeybindingsToShell', "Dispatches most keybindings to the terminal instead of the workbench, overriding {0}, which can be used alternatively for fine tuning.", '`#terminal.integrated.commandsToSkipShell#`'),
type: 'boolean',
default: false
},
+ [TerminalSettingId.TabsDefaultColor]: {
+ description: localize('terminal.integrated.tabs.defaultColor', "A theme color ID to associate with terminal icons by default."),
+ ...terminalColorSchema
+ },
+ [TerminalSettingId.TabsDefaultIcon]: {
+ description: localize('terminal.integrated.tabs.defaultIcon', "A codicon ID to associate with terminal icons by default."),
+ ...terminalIconSchema,
+ default: Codicon.terminal.id,
+ },
[TerminalSettingId.TabsEnabled]: {
description: localize('terminal.integrated.tabs.enabled', 'Controls whether terminal tabs display as a list to the side of the terminal. When this is disabled a dropdown will display instead.'),
type: 'boolean',
@@ -106,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 `''` to hide the icon or disable decorations with `#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 `''` to hide the icon or disable decorations with `#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 `''` to hide the icon or disable decorations with `#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',
@@ -139,7 +150,7 @@ const terminalConfiguration: IConfigurationNode = {
default: false
},
[TerminalSettingId.AltClickMovesCursor]: {
- markdownDescription: localize('terminal.integrated.altClickMovesCursor', "If enabled, alt/option + click will reposition the prompt cursor to underneath the mouse when `#editor.multiCursorModifier#` is set to `'alt'` (the default value). This may not work reliably depending on your shell."),
+ markdownDescription: localize('terminal.integrated.altClickMovesCursor', "If enabled, alt/option + click will reposition the prompt cursor to underneath the mouse when {0} is set to {1} (the default value). This may not work reliably depending on your shell.", '`#editor.multiCursorModifier#`', '`\'alt\'`'),
type: 'boolean',
default: true
},
@@ -159,7 +170,7 @@ const terminalConfiguration: IConfigurationNode = {
default: true
},
[TerminalSettingId.FontFamily]: {
- markdownDescription: localize('terminal.integrated.fontFamily', "Controls the font family of the terminal, this defaults to `#editor.fontFamily#`'s value."),
+ markdownDescription: localize('terminal.integrated.fontFamily', "Controls the font family of the terminal, this defaults to {0}'s value.", '`#editor.fontFamily#`'),
type: 'string'
},
// TODO: Support font ligatures
@@ -254,7 +265,7 @@ const terminalConfiguration: IConfigurationNode = {
default: TerminalCursorStyle.BLOCK
},
[TerminalSettingId.CursorWidth]: {
- markdownDescription: localize('terminal.integrated.cursorWidth', "Controls the width of the cursor when `#terminal.integrated.cursorStyle#` is set to `line`."),
+ markdownDescription: localize('terminal.integrated.cursorWidth', "Controls the width of the cursor when {0} is set to {1}.", '`#terminal.integrated.cursorStyle#`', '`line`'),
type: 'number',
default: 1
},
@@ -354,7 +365,8 @@ const terminalConfiguration: IConfigurationNode = {
'terminal.integrated.commandsToSkipShell',
"A set of command IDs whose keybindings will not be sent to the shell but instead always be handled by VS Code. This allows keybindings that would normally be consumed by the shell to act instead the same as when the terminal is not focused, for example `Ctrl+P` to launch Quick Open.\n\n&nbsp;\n\nMany commands are skipped by default. To override a default and pass that command's keybinding to the shell instead, add the command prefixed with the `-` character. For example add `-workbench.action.quickOpen` to allow `Ctrl+P` to reach the shell.\n\n&nbsp;\n\nThe following list of default skipped commands is truncated when viewed in Settings Editor. To see the full list, {1} and search for the first command from the list below.\n\n&nbsp;\n\nDefault Skipped Commands:\n\n{0}",
DEFAULT_COMMANDS_TO_SKIP_SHELL.sort().map(command => `- ${command}`).join('\n'),
- `[${localize('openDefaultSettingsJson', "open the default settings JSON")}](command:workbench.action.openRawDefaultSettings '${localize('openDefaultSettingsJson.capitalized', "Open Default Settings (JSON)")}')`
+ `[${localize('openDefaultSettingsJson', "open the default settings JSON")}](command:workbench.action.openRawDefaultSettings '${localize('openDefaultSettingsJson.capitalized', "Open Default Settings (JSON)")}')`,
+
),
type: 'array',
items: {
@@ -363,7 +375,7 @@ const terminalConfiguration: IConfigurationNode = {
default: []
},
[TerminalSettingId.AllowChords]: {
- markdownDescription: localize('terminal.integrated.allowChords', "Whether or not to allow chord keybindings in the terminal. Note that when this is true and the keystroke results in a chord it will bypass `#terminal.integrated.commandsToSkipShell#`, setting this to false is particularly useful when you want ctrl+k to go to your shell (not VS Code)."),
+ markdownDescription: localize('terminal.integrated.allowChords', "Whether or not to allow chord keybindings in the terminal. Note that when this is true and the keystroke results in a chord it will bypass {0}, setting this to false is particularly useful when you want ctrl+k to go to your shell (not VS Code).", '`#terminal.integrated.commandsToSkipShell#`'),
type: 'boolean',
default: true
},
@@ -464,7 +476,7 @@ const terminalConfiguration: IConfigurationNode = {
default: 30,
},
[TerminalSettingId.LocalEchoEnabled]: {
- markdownDescription: localize('terminal.integrated.localEchoEnabled', "When local echo should be enabled. This will override `#terminal.integrated.localEchoLatencyThreshold#`"),
+ markdownDescription: localize('terminal.integrated.localEchoEnabled', "When local echo should be enabled. This will override {0}", '`#terminal.integrated.localEchoLatencyThreshold#`'),
type: 'string',
enum: ['on', 'off', 'auto'],
enumDescriptions: [
@@ -536,7 +548,7 @@ const terminalConfiguration: IConfigurationNode = {
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."),
type: 'boolean',
- default: false
+ default: true
},
[TerminalSettingId.ShellIntegrationDecorationsEnabled]: {
restricted: true,
diff --git a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts
index 72f1792fc71..b97b332ea27 100644
--- a/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts
+++ b/src/vs/workbench/contrib/terminal/common/terminalContextKey.ts
@@ -31,6 +31,8 @@ export const enum TerminalContextKeyStrings {
TabsSingularSelection = 'terminalTabsSingularSelection',
SplitTerminal = 'terminalSplitTerminal',
ShellType = 'terminalShellType',
+ InTerminalRunCommandPicker = 'inTerminalRunCommandPicker',
+ TerminalShellIntegrationEnabled = 'terminalShellIntegrationEnabled'
}
export namespace TerminalContextKeys {
@@ -119,4 +121,10 @@ export namespace TerminalContextKeys {
/** Whether the focused tab's terminal is a split terminal. */
export const splitTerminal = new RawContextKey<boolean>(TerminalContextKeyStrings.SplitTerminal, false, localize('isSplitTerminalContextKey', "Whether the focused tab's terminal is a split terminal."));
+
+ /** Whether the terminal run command picker is currently open. */
+ export const inTerminalRunCommandPicker = new RawContextKey<boolean>(TerminalContextKeyStrings.InTerminalRunCommandPicker, false, localize('inTerminalRunCommandPickerContextKey', "Whether the terminal run command picker is currently open."));
+
+ /** Whether shell integration is enabled in the active terminal. This only considers full VS Code shell integration. */
+ export const terminalShellIntegrationEnabled = new RawContextKey<boolean>(TerminalContextKeyStrings.TerminalShellIntegrationEnabled, false, localize('terminalShellIntegrationEnabled', "Whether shell integration is enabled in the active terminal"));
}
diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts
index b2f01c3e853..d61acd4c4a4 100644
--- a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts
+++ b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts
@@ -168,6 +168,16 @@ class LocalTerminalBackend extends BaseTerminalBackend implements ITerminalBacke
return undefined;
}
+ async attachToRevivedProcess(id: number): Promise<ITerminalChildProcess | undefined> {
+ try {
+ const newId = await this._localPtyService.getRevivedPtyNewId(id) ?? id;
+ return await this.attachToProcess(newId);
+ } catch (e) {
+ this._logService.trace(`Couldn't attach to process ${e.message}`);
+ }
+ return undefined;
+ }
+
async listProcesses(): Promise<IProcessDetails[]> {
return this._localPtyService.listProcesses();
}
diff --git a/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts b/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts
index dcbdef59e2c..225dd72bbdc 100644
--- a/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts
+++ b/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts
@@ -97,10 +97,21 @@ suite('Workbench - TerminalLinkOpeners', () => {
capabilities.add(TerminalCapability.CommandDetection, commandDetection);
});
- test('should open single exact match against cwd when searching if it exists', async () => {
+ test('should open single exact match against cwd when searching if it exists when command detection cwd is available', async () => {
localFileOpener = instantiationService.createInstance(TerminalLocalFileLinkOpener, OperatingSystem.Linux);
const localFolderOpener = instantiationService.createInstance(TerminalLocalFolderInWorkspaceLinkOpener);
opener = instantiationService.createInstance(TerminalSearchLinkOpener, capabilities, Promise.resolve('/initial/cwd'), localFileOpener, localFolderOpener, OperatingSystem.Linux);
+ // Set a fake detected command starting as line 0 to establish the cwd
+ commandDetection.setCommands([{
+ command: '',
+ cwd: '/initial/cwd',
+ timestamp: 0,
+ getOutput() { return undefined; },
+ marker: {
+ line: 0
+ } as Partial<IXtermMarker> as any,
+ hasOutput() { return true; }
+ }]);
fileService.setFiles([
URI.from({ scheme: Schemas.file, path: '/initial/cwd/foo/bar.txt' }),
URI.from({ scheme: Schemas.file, path: '/initial/cwd/foo2/bar.txt' })
@@ -116,6 +127,44 @@ suite('Workbench - TerminalLinkOpeners', () => {
});
});
+ test('should open single exact match against cwd for paths containing a separator when searching if it exists, even when command detection isn\'t available', async () => {
+ localFileOpener = instantiationService.createInstance(TerminalLocalFileLinkOpener, OperatingSystem.Linux);
+ const localFolderOpener = instantiationService.createInstance(TerminalLocalFolderInWorkspaceLinkOpener);
+ opener = instantiationService.createInstance(TerminalSearchLinkOpener, capabilities, Promise.resolve('/initial/cwd'), localFileOpener, localFolderOpener, OperatingSystem.Linux);
+ fileService.setFiles([
+ URI.from({ scheme: Schemas.file, path: '/initial/cwd/foo/bar.txt' }),
+ URI.from({ scheme: Schemas.file, path: '/initial/cwd/foo2/bar.txt' })
+ ]);
+ await opener.open({
+ text: 'foo/bar.txt',
+ bufferRange: { start: { x: 1, y: 1 }, end: { x: 8, y: 1 } },
+ type: TerminalBuiltinLinkType.Search
+ });
+ deepStrictEqual(activationResult, {
+ link: 'file:///initial/cwd/foo/bar.txt',
+ source: 'editor'
+ });
+ });
+
+ test('should not open single exact match for paths not containing a when command detection isn\'t available', async () => {
+ localFileOpener = instantiationService.createInstance(TerminalLocalFileLinkOpener, OperatingSystem.Linux);
+ const localFolderOpener = instantiationService.createInstance(TerminalLocalFolderInWorkspaceLinkOpener);
+ opener = instantiationService.createInstance(TerminalSearchLinkOpener, capabilities, Promise.resolve('/initial/cwd'), localFileOpener, localFolderOpener, OperatingSystem.Linux);
+ fileService.setFiles([
+ URI.from({ scheme: Schemas.file, path: '/initial/cwd/foo/bar.txt' }),
+ URI.from({ scheme: Schemas.file, path: '/initial/cwd/foo2/bar.txt' })
+ ]);
+ await opener.open({
+ text: 'bar.txt',
+ bufferRange: { start: { x: 1, y: 1 }, end: { x: 8, y: 1 } },
+ type: TerminalBuiltinLinkType.Search
+ });
+ deepStrictEqual(activationResult, {
+ link: 'bar.txt',
+ source: 'search'
+ });
+ });
+
suite('macOS/Linux', () => {
setup(() => {
localFileOpener = instantiationService.createInstance(TerminalLocalFileLinkOpener, OperatingSystem.Linux);
@@ -139,7 +188,7 @@ suite('Workbench - TerminalLinkOpeners', () => {
marker: {
line: 0
} as Partial<IXtermMarker> as any,
- hasOutput: true
+ hasOutput() { return true; }
}]);
await opener.open({
text: 'file.txt',
@@ -188,7 +237,7 @@ suite('Workbench - TerminalLinkOpeners', () => {
marker: {
line: 0
} as Partial<IXtermMarker> as any,
- hasOutput: true
+ hasOutput() { return true; }
}]);
await opener.open({
text: 'file.txt',
diff --git a/src/vs/workbench/contrib/terminal/test/browser/links/terminalLocalLinkDetector.test.ts b/src/vs/workbench/contrib/terminal/test/browser/links/terminalLocalLinkDetector.test.ts
index 8ac3607d6e8..93ff1ce3687 100644
--- a/src/vs/workbench/contrib/terminal/test/browser/links/terminalLocalLinkDetector.test.ts
+++ b/src/vs/workbench/contrib/terminal/test/browser/links/terminalLocalLinkDetector.test.ts
@@ -58,6 +58,8 @@ const supportedLinkFormats: LinkFormatInfo[] = [
{ urlFormat: '{0} on line {1}, column {2}', line: '5', column: '3' },
{ urlFormat: '{0}:line {1}', line: '5' },
{ urlFormat: '{0}:line {1}, column {2}', line: '5', column: '3' },
+ { urlFormat: '{0}: line {1}', line: '5' },
+ { urlFormat: '{0}: line {1}, col {2}', line: '5', column: '3' },
{ urlFormat: '{0}({1})', line: '5' },
{ urlFormat: '{0} ({1})', line: '5' },
{ urlFormat: '{0}({1},{2})', line: '5', column: '3' },
@@ -66,6 +68,7 @@ const supportedLinkFormats: LinkFormatInfo[] = [
{ urlFormat: '{0} ({1}, {2})', line: '5', column: '3' },
{ urlFormat: '{0}:{1}', line: '5' },
{ urlFormat: '{0}:{1}:{2}', line: '5', column: '3' },
+ { urlFormat: '{0} {1}:{2}', line: '5', column: '3' },
{ urlFormat: '{0}[{1}]', line: '5' },
{ urlFormat: '{0} [{1}]', line: '5' },
{ urlFormat: '{0}[{1},{2}]', line: '5', column: '3' },
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 80802218586..3ea312e1e1e 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
@@ -56,21 +56,21 @@ suite('DecorationAddon', () => {
suite('registerDecoration', async () => {
test('should throw when command has no marker', async () => {
- throws(() => decorationAddon.registerCommandDecoration({ command: 'cd src', timestamp: Date.now(), hasOutput: false } as ITerminalCommand));
+ throws(() => decorationAddon.registerCommandDecoration({ command: 'cd src', timestamp: Date.now(), hasOutput: () => false } as ITerminalCommand));
});
test('should return undefined when marker has been disposed of', async () => {
const marker = xterm.registerMarker(1);
marker?.dispose();
- strictEqual(decorationAddon.registerCommandDecoration({ command: 'cd src', marker, timestamp: Date.now(), hasOutput: false } as ITerminalCommand), undefined);
+ strictEqual(decorationAddon.registerCommandDecoration({ command: 'cd src', marker, timestamp: Date.now(), hasOutput: () => false } as ITerminalCommand), undefined);
});
test('should return undefined when command is just empty chars', async () => {
const marker = xterm.registerMarker(1);
marker?.dispose();
- strictEqual(decorationAddon.registerCommandDecoration({ command: ' ', marker, timestamp: Date.now(), hasOutput: false } as ITerminalCommand), undefined);
+ strictEqual(decorationAddon.registerCommandDecoration({ command: ' ', marker, timestamp: Date.now(), hasOutput: () => false } as ITerminalCommand), undefined);
});
test('should return decoration when marker has not been disposed of', async () => {
const marker = xterm.registerMarker(2);
- notEqual(decorationAddon.registerCommandDecoration({ command: 'cd src', marker, timestamp: Date.now(), hasOutput: false } as ITerminalCommand), undefined);
+ notEqual(decorationAddon.registerCommandDecoration({ command: 'cd src', marker, timestamp: Date.now(), hasOutput: () => false } as ITerminalCommand), undefined);
});
});
});
diff --git a/src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts b/src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts
index a35b293a2a9..99bc8e0b095 100644
--- a/src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts
+++ b/src/vs/workbench/contrib/terminal/test/browser/xterm/xtermTerminal.test.ts
@@ -80,7 +80,7 @@ const defaultTerminalConfig: Partial<ITerminalConfiguration> = {
scrollback: 1000,
fastScrollSensitivity: 2,
mouseWheelScrollSensitivity: 1,
- unicodeVersion: '11'
+ unicodeVersion: '6'
};
suite('XtermTerminal', () => {
diff --git a/src/vs/workbench/contrib/testing/browser/explorerProjections/nodeHelper.ts b/src/vs/workbench/contrib/testing/browser/explorerProjections/nodeHelper.ts
index 0f0d26be6b4..6a74365ede4 100644
--- a/src/vs/workbench/contrib/testing/browser/explorerProjections/nodeHelper.ts
+++ b/src/vs/workbench/contrib/testing/browser/explorerProjections/nodeHelper.ts
@@ -6,11 +6,11 @@
import { IIdentityProvider } from 'vs/base/browser/ui/list/list';
import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree';
import { ITreeElement } from 'vs/base/browser/ui/tree/tree';
-import { IActionableTestTreeElement, TestExplorerTreeElement, TestItemTreeElement } from 'vs/workbench/contrib/testing/browser/explorerProjections/index';
+import { IActionableTestTreeElement, TestExplorerTreeElement, TestItemTreeElement, TestTreeErrorMessage } from 'vs/workbench/contrib/testing/browser/explorerProjections/index';
-export const testIdentityProvider: IIdentityProvider<TestItemTreeElement> = {
+export const testIdentityProvider: IIdentityProvider<TestExplorerTreeElement> = {
getId(element) {
- return element.treeId + '\0' + element.test.expand;
+ return element.treeId + '\0' + (element instanceof TestTreeErrorMessage ? 'error' : element.test.expand);
}
};
diff --git a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts
index db687ec08b6..556f7f4e65d 100644
--- a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts
+++ b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts
@@ -406,7 +406,7 @@ export class CancelTestRunAction extends Action2 {
constructor() {
super({
id: TestCommandId.CancelTestRunAction,
- title: localize('testing.cancelRun', "Cancel Test Run"),
+ title: { value: localize('testing.cancelRun', "Cancel Test Run"), original: 'Cancel Test Run' },
icon: icons.testingCancelIcon,
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
@@ -443,7 +443,7 @@ export class TestingViewAsListAction extends ViewAction<TestingExplorerView> {
super({
id: TestCommandId.TestingViewAsListAction,
viewId: Testing.ExplorerViewId,
- title: localize('testing.viewAsList', "View as List"),
+ title: { value: localize('testing.viewAsList', "View as List"), original: 'View as List' },
toggled: TestingContextKeys.viewMode.isEqualTo(TestExplorerViewMode.List),
menu: {
id: MenuId.ViewTitle,
@@ -467,7 +467,7 @@ export class TestingViewAsTreeAction extends ViewAction<TestingExplorerView> {
super({
id: TestCommandId.TestingViewAsTreeAction,
viewId: Testing.ExplorerViewId,
- title: localize('testing.viewAsTree', "View as Tree"),
+ title: { value: localize('testing.viewAsTree', "View as Tree"), original: 'View as Tree' },
toggled: TestingContextKeys.viewMode.isEqualTo(TestExplorerViewMode.Tree),
menu: {
id: MenuId.ViewTitle,
@@ -492,7 +492,7 @@ export class TestingSortByStatusAction extends ViewAction<TestingExplorerView> {
super({
id: TestCommandId.TestingSortByStatusAction,
viewId: Testing.ExplorerViewId,
- title: localize('testing.sortByStatus', "Sort by Status"),
+ title: { value: localize('testing.sortByStatus', "Sort by Status"), original: 'Sort by Status' },
toggled: TestingContextKeys.viewSorting.isEqualTo(TestExplorerViewSorting.ByStatus),
menu: {
id: MenuId.ViewTitle,
@@ -516,7 +516,7 @@ export class TestingSortByLocationAction extends ViewAction<TestingExplorerView>
super({
id: TestCommandId.TestingSortByLocationAction,
viewId: Testing.ExplorerViewId,
- title: localize('testing.sortByLocation', "Sort by Location"),
+ title: { value: localize('testing.sortByLocation', "Sort by Location"), original: 'Sort by Location' },
toggled: TestingContextKeys.viewSorting.isEqualTo(TestExplorerViewSorting.ByLocation),
menu: {
id: MenuId.ViewTitle,
@@ -540,7 +540,7 @@ export class TestingSortByDurationAction extends ViewAction<TestingExplorerView>
super({
id: TestCommandId.TestingSortByDurationAction,
viewId: Testing.ExplorerViewId,
- title: localize('testing.sortByDuration', "Sort by Duration"),
+ title: { value: localize('testing.sortByDuration', "Sort by Duration"), original: 'Sort by Duration' },
toggled: TestingContextKeys.viewSorting.isEqualTo(TestExplorerViewSorting.ByDuration),
menu: {
id: MenuId.ViewTitle,
@@ -563,7 +563,7 @@ export class ShowMostRecentOutputAction extends Action2 {
constructor() {
super({
id: TestCommandId.ShowMostRecentOutputAction,
- title: localize('testing.showMostRecentOutput', "Show Output"),
+ title: { value: localize('testing.showMostRecentOutput', "Show Output"), original: 'Show Output' },
category,
icon: Codicon.terminal,
keybinding: {
@@ -594,7 +594,7 @@ export class CollapseAllAction extends ViewAction<TestingExplorerView> {
super({
id: TestCommandId.CollapseAllAction,
viewId: Testing.ExplorerViewId,
- title: localize('testing.collapseAll', "Collapse All Tests"),
+ title: { value: localize('testing.collapseAll', "Collapse All Tests"), original: 'Collapse All Tests' },
icon: Codicon.collapseAll,
menu: {
id: MenuId.ViewTitle,
@@ -617,7 +617,7 @@ export class ClearTestResultsAction extends Action2 {
constructor() {
super({
id: TestCommandId.ClearTestResultsAction,
- title: localize('testing.clearResults', "Clear All Results"),
+ title: { value: localize('testing.clearResults', "Clear All Results"), original: 'Clear All Results' },
category,
icon: Codicon.trash,
menu: [{
@@ -646,7 +646,7 @@ export class GoToTest extends Action2 {
constructor() {
super({
id: TestCommandId.GoToTest,
- title: localize('testing.editFocusedTest', "Go to Test"),
+ title: { value: localize('testing.editFocusedTest', "Go to Test"), original: 'Go to Test' },
icon: Codicon.goToFile,
menu: testItemInlineAndInContext(ActionOrder.GoToTest, TestingContextKeys.testItemHasUri.isEqualTo(true)),
keybinding: {
@@ -742,7 +742,7 @@ export class RunAtCursor extends ExecuteTestAtCursor {
constructor() {
super({
id: TestCommandId.RunAtCursor,
- title: localize('testing.runAtCursor', "Run Test at Cursor"),
+ title: { value: localize('testing.runAtCursor', "Run Test at Cursor"), original: 'Run Test at Cursor' },
category,
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
@@ -757,7 +757,7 @@ export class DebugAtCursor extends ExecuteTestAtCursor {
constructor() {
super({
id: TestCommandId.DebugAtCursor,
- title: localize('testing.debugAtCursor', "Debug Test at Cursor"),
+ title: { value: localize('testing.debugAtCursor', "Debug Test at Cursor"), original: 'Debug Test at Cursor' },
category,
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
@@ -824,7 +824,7 @@ export class RunCurrentFile extends ExecuteTestsInCurrentFile {
constructor() {
super({
id: TestCommandId.RunCurrentFile,
- title: localize('testing.runCurrentFile', "Run Tests in Current File"),
+ title: { value: localize('testing.runCurrentFile', "Run Tests in Current File"), original: 'Run Tests in Current File' },
category,
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
@@ -840,7 +840,7 @@ export class DebugCurrentFile extends ExecuteTestsInCurrentFile {
constructor() {
super({
id: TestCommandId.DebugCurrentFile,
- title: localize('testing.debugCurrentFile', "Debug Tests in Current File"),
+ title: { value: localize('testing.debugCurrentFile', "Debug Tests in Current File"), original: 'Debug Tests in Current File' },
category,
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
@@ -948,7 +948,7 @@ export class ReRunFailedTests extends RunOrDebugFailedTests {
constructor() {
super({
id: TestCommandId.ReRunFailedTests,
- title: localize('testing.reRunFailTests', "Rerun Failed Tests"),
+ title: { value: localize('testing.reRunFailTests', "Rerun Failed Tests"), original: 'Rerun Failed Tests' },
category,
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
@@ -969,7 +969,7 @@ export class DebugFailedTests extends RunOrDebugFailedTests {
constructor() {
super({
id: TestCommandId.DebugFailedTests,
- title: localize('testing.debugFailTests', "Debug Failed Tests"),
+ title: { value: localize('testing.debugFailTests', "Debug Failed Tests"), original: 'Debug Failed Tests' },
category,
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
@@ -990,7 +990,7 @@ export class ReRunLastRun extends RunOrDebugLastRun {
constructor() {
super({
id: TestCommandId.ReRunLastRun,
- title: localize('testing.reRunLastRun', "Rerun Last Run"),
+ title: { value: localize('testing.reRunLastRun', "Rerun Last Run"), original: 'Rerun Last Run' },
category,
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
@@ -1011,7 +1011,7 @@ export class DebugLastRun extends RunOrDebugLastRun {
constructor() {
super({
id: TestCommandId.DebugLastRun,
- title: localize('testing.debugLastRun', "Debug Last Run"),
+ title: { value: localize('testing.debugLastRun', "Debug Last Run"), original: 'Debug Last Run' },
category,
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
@@ -1032,7 +1032,7 @@ export class SearchForTestExtension extends Action2 {
constructor() {
super({
id: TestCommandId.SearchForTestExtension,
- title: localize('testing.searchForTestExtension', "Search for Test Extension"),
+ title: { value: localize('testing.searchForTestExtension', "Search for Test Extension"), original: 'Search for Test Extension' },
});
}
@@ -1048,7 +1048,7 @@ export class OpenOutputPeek extends Action2 {
constructor() {
super({
id: TestCommandId.OpenOutputPeek,
- title: localize('testing.openOutputPeek', "Peek Output"),
+ title: { value: localize('testing.openOutputPeek', "Peek Output"), original: 'Peek Output' },
category,
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
@@ -1070,7 +1070,7 @@ export class ToggleInlineTestOutput extends Action2 {
constructor() {
super({
id: TestCommandId.ToggleInlineTestOutput,
- title: localize('testing.toggleInlineTestOutput', "Toggle Inline Test Output"),
+ title: { value: localize('testing.toggleInlineTestOutput', "Toggle Inline Test Output"), original: 'Toggle Inline Test Output' },
category,
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
@@ -1119,7 +1119,7 @@ export class RefreshTestsAction extends Action2 {
constructor() {
super({
id: TestCommandId.RefreshTestsAction,
- title: localize('testing.refreshTests', "Refresh Tests"),
+ title: { value: localize('testing.refreshTests', "Refresh Tests"), original: 'Refresh Tests' },
category,
icon: icons.testingRefreshTests,
keybinding: {
@@ -1155,7 +1155,7 @@ export class CancelTestRefreshAction extends Action2 {
constructor() {
super({
id: TestCommandId.CancelTestRefreshAction,
- title: localize('testing.cancelTestRefresh', "Cancel Test Refresh"),
+ title: { value: localize('testing.cancelTestRefresh', "Cancel Test Refresh"), original: 'Cancel Test Refresh' },
category,
icon: icons.testingCancelRefreshTests,
menu: refreshMenus(true),
diff --git a/src/vs/workbench/contrib/testing/browser/testing.contribution.ts b/src/vs/workbench/contrib/testing/browser/testing.contribution.ts
index 3605c75ec80..f6580294960 100644
--- a/src/vs/workbench/contrib/testing/browser/testing.contribution.ts
+++ b/src/vs/workbench/contrib/testing/browser/testing.contribution.ts
@@ -87,7 +87,6 @@ viewsRegistry.registerViews([{
name: localize('testExplorer', "Test Explorer"),
ctorDescriptor: new SyncDescriptor(TestingExplorerView),
canToggleVisibility: true,
- workspace: true,
canMoveView: true,
weight: 80,
order: -999,
diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts
index 4d1af363aa7..e9d136fdc9f 100644
--- a/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts
+++ b/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts
@@ -35,7 +35,7 @@ const testFilterDescriptions: { [K in TestFilterTerm]: string } = {
export class TestingExplorerFilter extends BaseActionViewItem {
private input!: SuggestEnabledInputWithHistory;
private wrapper!: HTMLDivElement;
- private readonly history: StoredValue<string[]> = this.instantiationService.createInstance(StoredValue, {
+ private readonly history: StoredValue<{ values: string[]; lastValue: string } | string[]> = this.instantiationService.createInstance(StoredValue, {
key: 'testing.filterHistory2',
scope: StorageScope.WORKSPACE,
target: StorageTarget.USER
@@ -65,9 +65,12 @@ export class TestingExplorerFilter extends BaseActionViewItem {
const wrapper = this.wrapper = dom.$('.testing-filter-wrapper');
container.appendChild(wrapper);
- const history = this.history.get([]);
- if (history.length) {
- this.state.setText(history[history.length - 1]);
+ let history = this.history.get({ lastValue: '', values: [] });
+ if (history instanceof Array) {
+ history = { lastValue: '', values: history };
+ }
+ if (history.lastValue) {
+ this.state.setText(history.lastValue);
}
const input = this.input = this._register(this.instantiationService.createInstance(ContextScopedSuggestEnabledInputWithHistory, {
@@ -94,7 +97,7 @@ export class TestingExplorerFilter extends BaseActionViewItem {
value: this.state.text.value,
placeholderText: localize('testExplorerFilter', "Filter (e.g. text, !exclude, @tag)"),
},
- history
+ history: history.values
}));
this._register(attachSuggestEnabledInputBoxStyler(input, this.themeService));
@@ -145,12 +148,7 @@ export class TestingExplorerFilter extends BaseActionViewItem {
* Persists changes to the input history.
*/
public saveState() {
- const history = this.input.getHistory();
- if (history.length) {
- this.history.store(history);
- } else {
- this.history.delete();
- }
+ this.history.store({ lastValue: this.input.getValue(), values: this.input.getHistory() });
}
/**
@@ -252,7 +250,7 @@ registerAction2(class extends Action2 {
constructor() {
super({
id: TestCommandId.FilterAction,
- title: localize('filter', "Filter"),
+ title: { value: localize('filter', "Filter"), original: 'Filter' },
});
}
async run(): Promise<void> { }
diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts
index 0c639a29c53..66ffbef2595 100644
--- a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts
+++ b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts
@@ -337,7 +337,7 @@ export class TestingExplorerView extends ViewPane {
icon: group === TestRunProfileBitset.Run
? icons.testingRunAllIcon
: icons.testingDebugAllIcon,
- }, undefined, undefined);
+ }, undefined, undefined, undefined);
const dropdownAction = new Action('selectRunConfig', 'Select Configuration...', 'codicon-chevron-down', true);
diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts
index c3452de68dd..988ad294015 100644
--- a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts
+++ b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts
@@ -1666,7 +1666,7 @@ export class GoToNextMessageAction extends EditorAction2 {
super({
id: GoToNextMessageAction.ID,
f1: true,
- title: localize('testing.goToNextMessage', "Go to Next Test Failure"),
+ title: { value: localize('testing.goToNextMessage', "Go to Next Test Failure"), original: 'Go to Next Test Failure' },
icon: Codicon.arrowDown,
category: CATEGORIES.Test,
keybinding: {
@@ -1696,7 +1696,7 @@ export class GoToPreviousMessageAction extends EditorAction2 {
super({
id: GoToPreviousMessageAction.ID,
f1: true,
- title: localize('testing.goToPreviousMessage', "Go to Previous Test Failure"),
+ title: { value: localize('testing.goToPreviousMessage', "Go to Previous Test Failure"), original: 'Go to Previous Test Failure' },
icon: Codicon.arrowUp,
category: CATEGORIES.Test,
keybinding: {
@@ -1726,7 +1726,7 @@ export class OpenMessageInEditorAction extends EditorAction2 {
super({
id: OpenMessageInEditorAction.ID,
f1: false,
- title: localize('testing.openMessageInEditor', "Open in Editor"),
+ title: { value: localize('testing.openMessageInEditor', "Open in Editor"), original: 'Open in Editor' },
icon: Codicon.linkExternal,
category: CATEGORIES.Test,
menu: [{ id: MenuId.TestPeekTitle }],
@@ -1744,7 +1744,7 @@ export class ToggleTestingPeekHistory extends EditorAction2 {
super({
id: ToggleTestingPeekHistory.ID,
f1: true,
- title: localize('testing.toggleTestingPeekHistory', "Toggle Test History in Peek"),
+ title: { value: localize('testing.toggleTestingPeekHistory', "Toggle Test History in Peek"), original: 'Toggle Test History in Peek' },
icon: Codicon.history,
category: CATEGORIES.Test,
menu: [{
diff --git a/src/vs/workbench/contrib/update/browser/update.contribution.ts b/src/vs/workbench/contrib/update/browser/update.contribution.ts
index 5d22660c689..1ff72278d33 100644
--- a/src/vs/workbench/contrib/update/browser/update.contribution.ts
+++ b/src/vs/workbench/contrib/update/browser/update.contribution.ts
@@ -34,8 +34,8 @@ class DownloadUpdateAction extends Action2 {
constructor() {
super({
id: 'update.downloadUpdate',
- title: localize('downloadUpdate', "Download Update"),
- category: product.nameShort,
+ title: { value: localize('downloadUpdate', "Download Update"), original: 'Download Update' },
+ category: { value: product.nameShort, original: product.nameShort },
f1: true,
precondition: CONTEXT_UPDATE_STATE.isEqualTo(StateType.AvailableForDownload)
});
@@ -50,8 +50,8 @@ class InstallUpdateAction extends Action2 {
constructor() {
super({
id: 'update.installUpdate',
- title: localize('installUpdate', "Install Update"),
- category: product.nameShort,
+ title: { value: localize('installUpdate', "Install Update"), original: 'Install Update' },
+ category: { value: product.nameShort, original: product.nameShort },
f1: true,
precondition: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Downloaded)
});
@@ -66,8 +66,8 @@ class RestartToUpdateAction extends Action2 {
constructor() {
super({
id: 'update.restartToUpdate',
- title: localize('restartToUpdate', "Restart to Update"),
- category: product.nameShort,
+ title: { value: localize('restartToUpdate', "Restart to Update"), original: 'Restart to Update' },
+ category: { value: product.nameShort, original: product.nameShort },
f1: true,
precondition: CONTEXT_UPDATE_STATE.isEqualTo(StateType.Ready)
});
diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts
index 8a75af540d3..0c2b7990a49 100644
--- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts
+++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts
@@ -6,7 +6,6 @@
import { Codicon } from 'vs/base/common/codicons';
import { Event } from 'vs/base/common/event';
import { Disposable, DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
-import { isWeb } from 'vs/base/common/platform';
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { localize } from 'vs/nls';
import { Action2, ISubmenuItem, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions';
@@ -15,6 +14,7 @@ import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from '
import { IProductService } from 'vs/platform/product/common/productService';
import { Registry } from 'vs/platform/registry/common/platform';
import { registerColor } from 'vs/platform/theme/common/colorRegistry';
+import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
import { themeColorFromId } from 'vs/platform/theme/common/themeService';
import { IUserDataProfile, IUserDataProfilesService, PROFILES_ENABLEMENT_CONFIG } from 'vs/platform/userDataProfile/common/userDataProfile';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
@@ -25,6 +25,8 @@ import { IUserDataProfileManagementService, IUserDataProfileService, ManageProfi
const CONTEXT_CURRENT_PROFILE = new RawContextKey<string>('currentUserDataProfile', '');
+export const userDataProfilesIcon = registerIcon('settingsProfiles-icon', Codicon.settings, localize('settingsProfilesIcon', 'Icon for Settings Profiles.'));
+
export class UserDataProfilesWorkbenchContribution extends Disposable implements IWorkbenchContribution {
private readonly currentProfileContext: IContextKey<string>;
@@ -53,7 +55,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
}
private registerConfiguration(): void {
- if (!isWeb && this.productService.quality !== 'stable') {
+ if (this.productService.quality !== 'stable') {
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).registerConfiguration({
...workbenchConfigurationNodeBase,
'properties': {
@@ -61,7 +63,8 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
'type': 'boolean',
'default': false,
'description': localize('workbench.experimental.settingsProfiles.enabled', "Controls whether to enable the Settings Profiles preview feature."),
- scope: ConfigurationScope.APPLICATION
+ scope: ConfigurationScope.APPLICATION,
+ ignoreSync: true
}
}
});
@@ -115,7 +118,6 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
id: `workbench.profiles.actions.profileEntry.${profile.id}`,
title: profile.name,
toggled: ContextKeyExpr.equals(CONTEXT_CURRENT_PROFILE.key, profile.id),
- precondition: ContextKeyExpr.notEquals(CONTEXT_CURRENT_PROFILE.key, profile.id),
menu: [
{
id: ManageProfilesSubMenu,
@@ -126,19 +128,21 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements
});
}
async run(accessor: ServicesAccessor) {
- return that.userDataProfileManagementService.switchProfile(profile);
+ if (that.userDataProfileService.currentProfile.id !== profile.id) {
+ return that.userDataProfileManagementService.switchProfile(profile);
+ }
}
});
}
private profileStatusAccessor: IStatusbarEntryAccessor | undefined;
private updateStatus(): void {
- if (this.userDataProfilesService.profiles.length) {
+ if (this.userDataProfilesService.profiles.length > 1) {
const statusBarEntry: IStatusbarEntry = {
name: PROFILES_CATEGORY,
command: 'workbench.profiles.actions.switchProfile',
ariaLabel: localize('currentProfile', "Current Settings Profile is {0}", this.userDataProfileService.currentProfile.name),
- text: `$(${Codicon.multipleWindows.id}) ${this.userDataProfileService.currentProfile.name!}`,
+ text: `$(${userDataProfilesIcon.id}) ${this.userDataProfileService.currentProfile.name!}`,
tooltip: localize('profileTooltip', "{0}: {1}", PROFILES_CATEGORY, this.userDataProfileService.currentProfile.name),
color: themeColorFromId(STATUS_BAR_SETTINGS_PROFILE_FOREGROUND),
backgroundColor: themeColorFromId(STATUS_BAR_SETTINGS_PROFILE_BACKGROUND)
diff --git a/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts b/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts
index e796f1f897e..01a3ef3d346 100644
--- a/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts
+++ b/src/vs/workbench/contrib/userDataProfile/common/userDataProfileActions.ts
@@ -7,8 +7,8 @@ import { CancellationToken } from 'vs/base/common/cancellation';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { joinPath } from 'vs/base/common/resources';
import { localize } from 'vs/nls';
-import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
-import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
+import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
+import { IDialogService, IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IFileService } from 'vs/platform/files/common/files';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { INotificationService } from 'vs/platform/notification/common/notification';
@@ -19,6 +19,7 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile
import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile';
import { CATEGORIES } from 'vs/workbench/common/actions';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
+import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
registerAction2(class CreateFromCurrentProfileAction extends Action2 {
constructor() {
@@ -116,12 +117,17 @@ registerAction2(class RemoveProfileAction extends Action2 {
const userDataProfileService = accessor.get(IUserDataProfileService);
const userDataProfilesService = accessor.get(IUserDataProfilesService);
const userDataProfileManagementService = accessor.get(IUserDataProfileManagementService);
+ const notificationService = accessor.get(INotificationService);
const profiles = userDataProfilesService.profiles.filter(p => p.id !== userDataProfileService.currentProfile.id && !p.isDefault);
if (profiles.length) {
const pick = await quickInputService.pick(profiles.map(profile => ({ label: profile.name, profile })), { placeHolder: localize('pick profile', "Select Settings Profile") });
if (pick) {
- await userDataProfileManagementService.removeProfile(pick.profile);
+ try {
+ await userDataProfileManagementService.removeProfile(pick.profile);
+ } catch (error) {
+ notificationService.error(error);
+ }
}
}
}
@@ -196,14 +202,14 @@ registerAction2(class ExportProfileAction extends Action2 {
original: 'Export Settings Profile...'
},
category: PROFILES_CATEGORY,
- f1: true,
- precondition: PROFILES_ENABLEMENT_CONTEXT,
menu: [
{
id: ManageProfilesSubMenu,
group: '3_import_export_profiles',
when: PROFILES_ENABLEMENT_CONTEXT,
order: 1
+ }, {
+ id: MenuId.CommandPalette
}
]
});
@@ -241,14 +247,14 @@ registerAction2(class ImportProfileAction extends Action2 {
original: 'Import Settings Profile...'
},
category: PROFILES_CATEGORY,
- f1: true,
- precondition: PROFILES_ENABLEMENT_CONTEXT,
menu: [
{
id: ManageProfilesSubMenu,
group: '3_import_export_profiles',
when: PROFILES_ENABLEMENT_CONTEXT,
order: 2
+ }, {
+ id: MenuId.CommandPalette
}
]
});
@@ -260,6 +266,19 @@ registerAction2(class ImportProfileAction extends Action2 {
const fileService = accessor.get(IFileService);
const requestService = accessor.get(IRequestService);
const userDataProfileImportExportService = accessor.get(IUserDataProfileImportExportService);
+ const dialogService = accessor.get(IDialogService);
+ const contextKeyService = accessor.get(IContextKeyService);
+
+ const isSettingProfilesEnabled = contextKeyService.contextMatchesRules(PROFILES_ENABLEMENT_CONTEXT);
+
+ if (!isSettingProfilesEnabled) {
+ if (!(await dialogService.confirm({
+ title: localize('import profile title', "Import Settings from a Profile"),
+ message: localize('confiirmation message', "This will replace your current settings. Are you sure you want to continue?"),
+ })).confirmed) {
+ return;
+ }
+ }
const disposables = new DisposableStore();
const quickPick = disposables.add(quickInputService.createQuickPick());
@@ -278,7 +297,11 @@ registerAction2(class ImportProfileAction extends Action2 {
quickPick.hide();
const profile = quickPick.selectedItems[0].description ? await this.getProfileFromURL(quickPick.value, requestService) : await this.getProfileFromFileSystem(fileDialogService, fileService);
if (profile) {
- await userDataProfileImportExportService.importProfile(profile);
+ if (isSettingProfilesEnabled) {
+ await userDataProfileImportExportService.importProfile(profile);
+ } else {
+ await userDataProfileImportExportService.setProfile(profile);
+ }
}
}));
disposables.add(quickPick.onDidHide(() => disposables.dispose()));
diff --git a/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html b/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html
index ec2b7281a65..6469cf31e74 100644
--- a/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html
+++ b/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html
@@ -919,14 +919,21 @@
sandboxRules.add('allow-forms');
}
newFrame.setAttribute('sandbox', Array.from(sandboxRules).join(' '));
- if (!isFirefox) {
- newFrame.setAttribute('allow', options.allowScripts ? 'clipboard-read; clipboard-write;' : '');
+
+ const allowRules = ['cross-origin-isolated;']
+ if(!isFirefox && options.allowScripts) {
+ allowRules.push('clipboard-read;','clipboard-write;')
}
+ newFrame.setAttribute('allow', allowRules.join(' '));
// We should just be able to use srcdoc, but I wasn't
// seeing the service worker applying properly.
// Fake load an empty on the correct origin and then write real html
// into it to get around this.
- newFrame.src = `./fake.html?id=${ID}`;
+ const fakeUrlParams = new URLSearchParams({id: ID});
+ if(globalThis.crossOriginIsolated) {
+ fakeUrlParams.set('vscode-coi', '3') /*COOP+COEP*/
+ }
+ newFrame.src = `./fake.html?${fakeUrlParams.toString()}`;
newFrame.style.cssText = 'display: block; margin: 0; overflow: hidden; position: absolute; width: 100%; height: 100%; visibility: hidden';
document.body.appendChild(newFrame);
diff --git a/src/vs/workbench/contrib/webview/browser/pre/index.html b/src/vs/workbench/contrib/webview/browser/pre/index.html
index 326a076c677..965b90ace22 100644
--- a/src/vs/workbench/contrib/webview/browser/pre/index.html
+++ b/src/vs/workbench/contrib/webview/browser/pre/index.html
@@ -5,7 +5,7 @@
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy"
- content="default-src 'none'; script-src 'sha256-v9xEHcwDE5dc/lU7HYs5bG3LpPWGmQe0w/Vz6kmdd60=' 'self'; frame-src 'self'; style-src 'unsafe-inline';">
+ content="default-src 'none'; script-src 'sha256-vGloSX/Mg/JYMjFOA5bYxbKTao1iYLW/tlq9ME/cEOo=' 'self'; frame-src 'self'; style-src 'unsafe-inline';">
<!-- Disable pinch zooming -->
<meta name="viewport"
@@ -920,14 +920,21 @@
sandboxRules.add('allow-forms');
}
newFrame.setAttribute('sandbox', Array.from(sandboxRules).join(' '));
- if (!isFirefox) {
- newFrame.setAttribute('allow', options.allowScripts ? 'clipboard-read; clipboard-write;' : '');
+
+ const allowRules = ['cross-origin-isolated;']
+ if(!isFirefox && options.allowScripts) {
+ allowRules.push('clipboard-read;','clipboard-write;')
}
+ newFrame.setAttribute('allow', allowRules.join(' '));
// We should just be able to use srcdoc, but I wasn't
// seeing the service worker applying properly.
// Fake load an empty on the correct origin and then write real html
// into it to get around this.
- newFrame.src = `./fake.html?id=${ID}`;
+ const fakeUrlParams = new URLSearchParams({id: ID});
+ if(globalThis.crossOriginIsolated) {
+ fakeUrlParams.set('vscode-coi', '3') /*COOP+COEP*/
+ }
+ newFrame.src = `./fake.html?${fakeUrlParams.toString()}`;
newFrame.style.cssText = 'display: block; margin: 0; overflow: hidden; position: absolute; width: 100%; height: 100%; visibility: hidden';
document.body.appendChild(newFrame);
diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts
index 77aaf6683f9..fcc0777e76a 100644
--- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts
+++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts
@@ -474,9 +474,14 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD
element.name = this.id;
element.className = `webview ${options.customClasses || ''}`;
element.sandbox.add('allow-scripts', 'allow-same-origin', 'allow-forms', 'allow-pointer-lock', 'allow-downloads');
+
+ const allowRules = ['cross-origin-isolated;'];
if (!isFirefox) {
- element.setAttribute('allow', 'clipboard-read; clipboard-write;');
+ allowRules.push('clipboard-read;', 'clipboard-write;');
+ element.setAttribute('allow', 'clipboard-read; clipboard-write; cross-origin-isolated;');
}
+ element.setAttribute('allow', allowRules.join(' '));
+
element.style.border = 'none';
element.style.width = '100%';
element.style.height = '100%';
@@ -508,6 +513,10 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD
params.purpose = options.purpose;
}
+ if (globalThis.crossOriginIsolated) {
+ params['vscode-coi'] = '3'; /*COOP+COEP*/
+ }
+
const queryString = new URLSearchParams(params).toString();
// Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=1754872
diff --git a/src/vs/workbench/contrib/webview/electron-sandbox/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-sandbox/webviewElement.ts
index 692eeb591ba..c119358978c 100644
--- a/src/vs/workbench/contrib/webview/electron-sandbox/webviewElement.ts
+++ b/src/vs/workbench/contrib/webview/electron-sandbox/webviewElement.ts
@@ -90,6 +90,13 @@ export class ElectronWebviewElement extends WebviewElement {
}
}
+ override dispose(): void {
+ // Make sure keyboard handler knows it closed (#71800)
+ this._webviewKeyboardHandler.didBlur();
+
+ super.dispose();
+ }
+
protected override webviewContentEndpoint(iframeId: string): string {
return `${Schemas.vscodeWebview}://${iframeId}`;
}
diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts
index f119f79c69d..497dfb1165c 100644
--- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts
+++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts
@@ -70,6 +70,7 @@ import { Codicon } from 'vs/base/common/codicons';
import { restoreWalkthroughsConfigurationKey, RestoreWalkthroughsConfigurationValue } from 'vs/workbench/contrib/welcomeGettingStarted/browser/startupPage';
import { GettingStartedDetailsRenderer } from 'vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedDetailsRenderer';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
+import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';
const SLIDE_TRANSITION_TIME_MS = 250;
const configurationKey = 'workbench.startupEditor';
@@ -968,7 +969,7 @@ export class GettingStartedPage extends EditorPane {
if (category.isFeatured) {
reset(featuredBadge, $('.featured', {}, $('span.featured-icon.codicon.codicon-star-empty')));
- reset(descriptionContent, category.description);
+ reset(descriptionContent, ...renderLabelWithIcons(category.description));
}
return $('button.getting-started-category' + (category.isFeatured ? '.featured' : ''),
@@ -1237,7 +1238,7 @@ export class GettingStartedPage extends EditorPane {
this.iconWidgetFor(category),
$('.category-description-container', {},
$('h2.category-title.max-lines-3', { 'x-category-title-for': category.id }, category.title),
- $('.category-description.description.max-lines-3', { 'x-category-description-for': category.id }, category.description)));
+ $('.category-description.description.max-lines-3', { 'x-category-description-for': category.id }, ...renderLabelWithIcons(category.description))));
const stepListContainer = $('.step-list-container');
diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts
index 74b541681a6..dcbddfc4097 100644
--- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts
+++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts
@@ -691,8 +691,8 @@ registerAction2(class extends Action2 {
constructor() {
super({
id: 'resetGettingStartedProgress',
- category: 'Developer',
- title: 'Reset Welcome Page Walkthrough Progress',
+ category: { original: 'Developer', value: localize('developer', "Developer") },
+ title: { original: 'Reset Welcome Page Walkthrough Progress', value: localize('resetWelcomePageWalkthroughProgress', "Reset Welcome Page Walkthrough Progress") },
f1: true
});
}
diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css b/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css
index eb269e524fe..60016371f6d 100644
--- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css
+++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css
@@ -284,6 +284,11 @@
margin-left: 28px;
}
+.monaco-workbench .part.editor>.content .gettingStartedContainer .gettingStartedSlide .getting-started-category .description-content > .codicon {
+ padding-right: 1px;
+ font-size: 16px;
+}
+
.monaco-workbench .part.editor>.content .gettingStartedContainer .gettingStartedSlide .getting-started-category .description-content:not(:empty){
margin-bottom: 8px;
}
@@ -368,7 +373,7 @@
flex: 150px 1 1000
}
-.monaco-workbench .part.editor>.content .gettingStartedContainer .gettingStartedSlideDetails .getting-started-category .codicon {
+.monaco-workbench .part.editor>.content .gettingStartedContainer .gettingStartedSlideDetails .gettingStartedDetailsContent>.getting-started-category>.codicon-getting-started-setup {
margin-right: 8px;
font-size: 28px;
}