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

github.com/microsoft/vscode.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohannes Rieken <johannes.rieken@gmail.com>2022-05-04 09:42:43 +0300
committerGitHub <noreply@github.com>2022-05-04 09:42:43 +0300
commit12c535e2bd7f261a94a5aa1267fa940055706f8c (patch)
tree63456f645a98a08889b97032c5312237f95f5d4d /src/vs/workbench/contrib
parent4ef3ed3ce8d7ab1857d41454449d32f946d3ac8c (diff)
parent9556854c8fc9199b4ffd06b4e17140e8fb78d0f4 (diff)
Merge branch 'main' into joh/cellUri
Diffstat (limited to 'src/vs/workbench/contrib')
-rw-r--r--src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts2
-rw-r--r--src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts4
-rw-r--r--src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.css12
-rw-r--r--src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts92
-rw-r--r--src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts7
-rw-r--r--src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts10
-rw-r--r--src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts2
-rw-r--r--src/vs/workbench/contrib/codeEditor/browser/untitledTextEditorHint.ts44
-rw-r--r--src/vs/workbench/contrib/comments/browser/commentColors.ts19
-rw-r--r--src/vs/workbench/contrib/comments/browser/commentGlyphWidget.ts2
-rw-r--r--src/vs/workbench/contrib/comments/browser/commentNode.ts7
-rw-r--r--src/vs/workbench/contrib/comments/browser/commentReply.ts1
-rw-r--r--src/vs/workbench/contrib/comments/browser/commentService.ts17
-rw-r--r--src/vs/workbench/contrib/comments/browser/commentThreadBody.ts10
-rw-r--r--src/vs/workbench/contrib/comments/browser/commentThreadRangeDecorator.ts99
-rw-r--r--src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts43
-rw-r--r--src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts33
-rw-r--r--src/vs/workbench/contrib/comments/browser/comments.contribution.ts6
-rw-r--r--src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts236
-rw-r--r--src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts34
-rw-r--r--src/vs/workbench/contrib/comments/browser/commentsView.ts6
-rw-r--r--src/vs/workbench/contrib/comments/browser/media/panel.css8
-rw-r--r--src/vs/workbench/contrib/comments/browser/media/review.css6
-rw-r--r--src/vs/workbench/contrib/comments/common/commentModel.ts8
-rw-r--r--src/vs/workbench/contrib/comments/common/commentsConfiguration.ts2
-rw-r--r--src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts2
-rw-r--r--src/vs/workbench/contrib/debug/browser/baseDebugView.ts8
-rw-r--r--src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts43
-rw-r--r--src/vs/workbench/contrib/debug/browser/breakpointWidget.ts12
-rw-r--r--src/vs/workbench/contrib/debug/browser/callStackView.ts269
-rw-r--r--src/vs/workbench/contrib/debug/browser/debug.contribution.ts25
-rw-r--r--src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts16
-rw-r--r--src/vs/workbench/contrib/debug/browser/debugColors.ts7
-rw-r--r--src/vs/workbench/contrib/debug/browser/debugCommands.ts13
-rw-r--r--src/vs/workbench/contrib/debug/browser/debugEditorActions.ts22
-rw-r--r--src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts6
-rw-r--r--src/vs/workbench/contrib/debug/browser/debugService.ts12
-rw-r--r--src/vs/workbench/contrib/debug/browser/debugSession.ts19
-rw-r--r--src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts10
-rw-r--r--src/vs/workbench/contrib/debug/browser/debugToolBar.ts121
-rw-r--r--src/vs/workbench/contrib/debug/browser/debugViewlet.ts52
-rw-r--r--src/vs/workbench/contrib/debug/browser/media/debug.contribution.css17
-rw-r--r--src/vs/workbench/contrib/debug/browser/rawDebugSession.ts23
-rw-r--r--src/vs/workbench/contrib/debug/browser/repl.ts2
-rw-r--r--src/vs/workbench/contrib/debug/common/breakpoints.ts27
-rw-r--r--src/vs/workbench/contrib/debug/common/debug.ts17
-rw-r--r--src/vs/workbench/contrib/debug/common/debugModel.ts74
-rw-r--r--src/vs/workbench/contrib/debug/common/debugSchemas.ts15
-rw-r--r--src/vs/workbench/contrib/debug/common/debugViewModel.ts11
-rw-r--r--src/vs/workbench/contrib/debug/common/debugger.ts48
-rw-r--r--src/vs/workbench/contrib/debug/node/debugAdapter.ts2
-rw-r--r--src/vs/workbench/contrib/debug/node/terminals.ts9
-rw-r--r--src/vs/workbench/contrib/debug/test/browser/mockDebug.ts2
-rw-r--r--src/vs/workbench/contrib/dropIntoEditor/browser/dropIntoEditor.contibution.ts151
-rw-r--r--src/vs/workbench/contrib/emmet/browser/emmetActions.ts2
-rw-r--r--src/vs/workbench/contrib/experiments/common/experimentService.ts8
-rw-r--r--src/vs/workbench/contrib/extensions/browser/extensionEditor.ts5
-rw-r--r--src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts18
-rw-r--r--src/vs/workbench/contrib/extensions/browser/extensionsActions.ts7
-rw-r--r--src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts8
-rw-r--r--src/vs/workbench/contrib/extensions/browser/extensionsViews.ts8
-rw-r--r--src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts6
-rw-r--r--src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts38
-rw-r--r--src/vs/workbench/contrib/extensions/test/electron-browser/extension.test.ts8
-rw-r--r--src/vs/workbench/contrib/feedback/browser/feedback.ts103
-rw-r--r--src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts4
-rw-r--r--src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts44
-rw-r--r--src/vs/workbench/contrib/files/browser/explorerService.ts59
-rw-r--r--src/vs/workbench/contrib/files/browser/fileActions.contribution.ts11
-rw-r--r--src/vs/workbench/contrib/files/browser/fileActions.ts16
-rw-r--r--src/vs/workbench/contrib/files/browser/fileImportExport.ts31
-rw-r--r--src/vs/workbench/contrib/files/browser/files.contribution.ts27
-rw-r--r--src/vs/workbench/contrib/files/browser/files.ts1
-rw-r--r--src/vs/workbench/contrib/files/browser/views/explorerView.ts32
-rw-r--r--src/vs/workbench/contrib/files/browser/views/explorerViewer.ts10
-rw-r--r--src/vs/workbench/contrib/files/browser/views/openEditorsView.ts8
-rw-r--r--src/vs/workbench/contrib/files/common/explorerModel.ts17
-rw-r--r--src/vs/workbench/contrib/files/common/files.ts11
-rw-r--r--src/vs/workbench/contrib/files/electron-sandbox/fileActions.contribution.ts2
-rw-r--r--src/vs/workbench/contrib/files/electron-sandbox/textFileEditor.ts34
-rw-r--r--src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts110
-rw-r--r--src/vs/workbench/contrib/keybindings/browser/keybindings.contribution.ts2
-rw-r--r--src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts148
-rw-r--r--src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts40
-rw-r--r--src/vs/workbench/contrib/languageStatus/browser/media/languageStatus.css33
-rw-r--r--src/vs/workbench/contrib/localHistory/browser/localHistory.ts16
-rw-r--r--src/vs/workbench/contrib/localHistory/browser/localHistoryCommands.ts2
-rw-r--r--src/vs/workbench/contrib/localHistory/browser/localHistoryTimeline.ts6
-rw-r--r--src/vs/workbench/contrib/logs/browser/logs.contribution.ts34
-rw-r--r--src/vs/workbench/contrib/logs/common/logs.contribution.ts67
-rw-r--r--src/vs/workbench/contrib/logs/electron-sandbox/logs.contribution.ts36
-rw-r--r--src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts2
-rw-r--r--src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/statusBarProviders.ts82
-rw-r--r--src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts2
-rw-r--r--src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts23
-rw-r--r--src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts18
-rw-r--r--src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFind.ts135
-rw-r--r--src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts6
-rw-r--r--src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts (renamed from src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts)179
-rw-r--r--src/vs/workbench/contrib/notebook/browser/controller/apiActions.ts6
-rw-r--r--src/vs/workbench/contrib/notebook/browser/controller/editActions.ts69
-rw-r--r--src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts9
-rw-r--r--src/vs/workbench/contrib/notebook/browser/media/notebook.css2
-rw-r--r--src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts19
-rw-r--r--src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts4
-rw-r--r--src/vs/workbench/contrib/notebook/browser/notebookEditor.ts7
-rw-r--r--src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts34
-rw-r--r--src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts41
-rw-r--r--src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts11
-rw-r--r--src/vs/workbench/contrib/notebook/browser/view/cellParts/cellContextKeys.ts11
-rw-r--r--src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDnd.ts13
-rw-r--r--src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDragRenderer.ts2
-rw-r--r--src/vs/workbench/contrib/notebook/browser/view/cellParts/cellExecution.ts6
-rw-r--r--src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts13
-rw-r--r--src/vs/workbench/contrib/notebook/browser/view/cellParts/markdownCell.ts3
-rw-r--r--src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts23
-rw-r--r--src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts3
-rw-r--r--src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts8
-rw-r--r--src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts4
-rw-r--r--src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts26
-rw-r--r--src/vs/workbench/contrib/notebook/common/notebookCommon.ts10
-rw-r--r--src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts2
-rw-r--r--src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts8
-rw-r--r--src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts15
-rw-r--r--src/vs/workbench/contrib/notebook/common/notebookKernelService.ts35
-rw-r--r--src/vs/workbench/contrib/notebook/common/notebookOptions.ts70
-rw-r--r--src/vs/workbench/contrib/notebook/test/browser/contrib/find.test.ts38
-rw-r--r--src/vs/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts6
-rw-r--r--src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts5
-rw-r--r--src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts5
-rw-r--r--src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts5
-rw-r--r--src/vs/workbench/contrib/output/browser/logViewer.ts3
-rw-r--r--src/vs/workbench/contrib/output/browser/output.contribution.ts3
-rw-r--r--src/vs/workbench/contrib/output/browser/outputLinkProvider.ts2
-rw-r--r--src/vs/workbench/contrib/output/browser/outputServices.ts3
-rw-r--r--src/vs/workbench/contrib/output/browser/outputView.ts3
-rw-r--r--src/vs/workbench/contrib/output/common/outputChannelModel.ts2
-rw-r--r--src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css4
-rw-r--r--src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts46
-rw-r--r--src/vs/workbench/contrib/preferences/browser/preferencesIcons.ts1
-rw-r--r--src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts6
-rw-r--r--src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts42
-rw-r--r--src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts76
-rw-r--r--src/vs/workbench/contrib/preferences/browser/settingsLayout.ts2
-rw-r--r--src/vs/workbench/contrib/preferences/browser/settingsSearchMenu.ts141
-rw-r--r--src/vs/workbench/contrib/preferences/browser/settingsTree.ts7
-rw-r--r--src/vs/workbench/contrib/preferences/common/preferences.ts2
-rw-r--r--src/vs/workbench/contrib/profiles/common/profiles.contribution.ts6
-rw-r--r--src/vs/workbench/contrib/profiles/common/profilesActions.ts136
-rw-r--r--src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts21
-rw-r--r--src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts2
-rw-r--r--src/vs/workbench/contrib/scm/browser/activity.ts21
-rw-r--r--src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts116
-rw-r--r--src/vs/workbench/contrib/scm/browser/scm.contribution.ts74
-rw-r--r--src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts18
-rw-r--r--src/vs/workbench/contrib/scm/browser/scmViewPane.ts208
-rw-r--r--src/vs/workbench/contrib/scm/browser/scmViewService.ts283
-rw-r--r--src/vs/workbench/contrib/scm/common/scm.ts13
-rw-r--r--src/vs/workbench/contrib/scm/common/scmService.ts39
-rw-r--r--src/vs/workbench/contrib/search/browser/search.contribution.ts32
-rw-r--r--src/vs/workbench/contrib/search/browser/searchResultsView.ts4
-rw-r--r--src/vs/workbench/contrib/search/browser/searchView.ts55
-rw-r--r--src/vs/workbench/contrib/search/browser/searchWidget.ts7
-rw-r--r--src/vs/workbench/contrib/search/test/electron-browser/textsearch.perf.integrationTest.ts3
-rw-r--r--src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts2
-rw-r--r--src/vs/workbench/contrib/snippets/browser/configureSnippets.ts144
-rw-r--r--src/vs/workbench/contrib/snippets/browser/insertSnippet.ts2
-rw-r--r--src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts5
-rw-r--r--src/vs/workbench/contrib/snippets/browser/snippetsFile.ts8
-rw-r--r--src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts2
-rw-r--r--src/vs/workbench/contrib/snippets/browser/tabCompletion.ts4
-rw-r--r--src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts94
-rw-r--r--src/vs/workbench/contrib/tags/electron-sandbox/workspaceTags.ts2
-rw-r--r--src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts197
-rw-r--r--src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts2
-rw-r--r--src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts4
-rw-r--r--src/vs/workbench/contrib/tasks/common/taskConfiguration.ts6
-rw-r--r--src/vs/workbench/contrib/tasks/common/tasks.ts2
-rw-r--r--src/vs/workbench/contrib/tasks/electron-sandbox/taskService.ts2
-rwxr-xr-xsrc/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh45
-rw-r--r--src/vs/workbench/contrib/terminal/browser/media/shellIntegration-env.zsh8
-rw-r--r--src/vs/workbench/contrib/terminal/browser/media/shellIntegration-profile.zsh8
-rw-r--r--src/vs/workbench/contrib/terminal/browser/media/shellIntegration.zsh39
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalFindWidget.ts24
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalInstance.ts36
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts6
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts12
-rw-r--r--src/vs/workbench/contrib/terminal/browser/terminalService.ts7
-rw-r--r--src/vs/workbench/contrib/terminal/browser/xterm/commandNavigationAddon.ts3
-rw-r--r--src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts10
-rw-r--r--src/vs/workbench/contrib/terminal/common/environmentVariable.ts3
-rw-r--r--src/vs/workbench/contrib/terminal/common/environmentVariableCollection.ts13
-rw-r--r--src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts2
-rw-r--r--src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts46
-rw-r--r--src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts4
-rw-r--r--src/vs/workbench/contrib/terminal/test/browser/terminalService.test.ts3
-rw-r--r--src/vs/workbench/contrib/terminal/test/common/environmentVariableCollection.test.ts12
-rw-r--r--src/vs/workbench/contrib/terminal/test/common/environmentVariableService.test.ts4
-rw-r--r--src/vs/workbench/contrib/terminal/test/common/terminalEnvironment.test.ts48
-rw-r--r--src/vs/workbench/contrib/testing/browser/testExplorerActions.ts16
-rw-r--r--src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts7
-rw-r--r--src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts8
-rw-r--r--src/vs/workbench/contrib/testing/browser/theme.ts2
-rw-r--r--src/vs/workbench/contrib/testing/common/constants.ts1
-rw-r--r--src/vs/workbench/contrib/testing/common/testResult.ts2
-rw-r--r--src/vs/workbench/contrib/testing/common/testTypes.ts10
-rw-r--r--src/vs/workbench/contrib/testing/common/testingContentProvider.ts15
-rw-r--r--src/vs/workbench/contrib/themes/test/electron-browser/colorRegistry.releaseTest.ts4
-rw-r--r--src/vs/workbench/contrib/timeline/browser/timelinePane.ts12
-rw-r--r--src/vs/workbench/contrib/timeline/common/timeline.ts12
-rw-r--r--src/vs/workbench/contrib/timeline/common/timelineService.ts6
-rw-r--r--src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts4
-rw-r--r--src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts2
-rw-r--r--src/vs/workbench/contrib/webview/browser/overlayWebview.ts32
-rw-r--r--src/vs/workbench/contrib/webview/browser/pre/main.js42
-rw-r--r--src/vs/workbench/contrib/webview/browser/pre/service-worker.js6
-rw-r--r--src/vs/workbench/contrib/webview/browser/webview.ts2
-rw-r--r--src/vs/workbench/contrib/webview/browser/webviewElement.ts71
-rw-r--r--src/vs/workbench/contrib/webview/browser/webviewFindWidget.ts6
-rw-r--r--src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInput.ts2
-rw-r--r--src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts22
-rw-r--r--src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts4
-rw-r--r--src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts60
-rw-r--r--src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts22
-rw-r--r--src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts32
225 files changed, 4363 insertions, 1946 deletions
diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts
index 4122bc5a7dd..ef9e3d8358a 100644
--- a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts
+++ b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts
@@ -41,7 +41,7 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).regis
'type': 'number',
'minimum': 0,
'maximum': 100,
- 'default': 50
+ 'default': 70
},
'audioCues.lineHasBreakpoint': {
'description': localize('audioCues.lineHasBreakpoint', "Plays a sound when the active line has a breakpoint."),
diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts
index c796f5e0ce4..ca1856c6312 100644
--- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts
+++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditTree.ts
@@ -227,14 +227,14 @@ export class BulkEditDataSource implements IAsyncDataSource<BulkFileOperations,
const range = Range.lift(edit.textEdit.textEdit.range);
//prefix-math
- let startTokens = textModel.getLineTokens(range.startLineNumber);
+ let startTokens = textModel.tokenization.getLineTokens(range.startLineNumber);
let prefixLen = 23; // default value for the no tokens/grammar case
for (let idx = startTokens.findTokenIndexAtOffset(range.startColumn) - 1; prefixLen < 50 && idx >= 0; idx--) {
prefixLen = range.startColumn - startTokens.getStartOffset(idx);
}
//suffix-math
- let endTokens = textModel.getLineTokens(range.endLineNumber);
+ let endTokens = textModel.tokenization.getLineTokens(range.endLineNumber);
let suffixLen = 0;
for (let idx = endTokens.findTokenIndexAtOffset(range.endColumn); suffixLen < 50 && idx < endTokens.getCount(); idx++) {
suffixLen += endTokens.getEndOffset(idx) - endTokens.getStartOffset(idx);
diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.css b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.css
index bdd496042ce..46b11d7eedf 100644
--- a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.css
+++ b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.css
@@ -15,6 +15,10 @@
padding: 0 10px 10px;
}
+.simple-find-part .monaco-inputbox > .ibwrapper > input {
+ text-overflow: clip;
+}
+
.monaco-workbench .simple-find-part {
visibility: hidden; /* Use visibility to maintain flex layout while hidden otherwise interferes with transition */
z-index: 10;
@@ -57,7 +61,11 @@
cursor: pointer;
}
-.monaco-workbench .simple-find-part .button.disabled {
- opacity: 0.3;
+.monaco-workbench div.simple-find-part div.button.disabled {
+ opacity: 0.3 !important;
cursor: default;
}
+
+div.simple-find-part-wrapper div.button {
+ border-radius: 5px;
+}
diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts
index 1e220e2de14..ff599441598 100644
--- a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts
+++ b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts
@@ -12,16 +12,19 @@ import { Delayer } from 'vs/base/common/async';
import { KeyCode } from 'vs/base/common/keyCodes';
import { FindReplaceState } from 'vs/editor/contrib/find/browser/findState';
import { IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBox';
-import { SimpleButton, findPreviousMatchIcon, findNextMatchIcon } from 'vs/editor/contrib/find/browser/findWidget';
+import { SimpleButton, findPreviousMatchIcon, findNextMatchIcon, NLS_NO_RESULTS, NLS_MATCHES_LOCATION } from 'vs/editor/contrib/find/browser/findWidget';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
-import { editorWidgetBackground, inputActiveOptionBorder, inputActiveOptionBackground, inputActiveOptionForeground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, widgetShadow, editorWidgetForeground, errorForeground } from 'vs/platform/theme/common/colorRegistry';
+import { editorWidgetBackground, inputActiveOptionBorder, inputActiveOptionBackground, inputActiveOptionForeground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, widgetShadow, editorWidgetForeground, errorForeground, toolbarHoverBackground, toolbarHoverOutline } from 'vs/platform/theme/common/colorRegistry';
import { IColorTheme, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { ContextScopedFindInput } from 'vs/platform/history/browser/contextScopedHistoryWidget';
import { widgetClose } from 'vs/platform/theme/common/iconRegistry';
+import * as strings from 'vs/base/common/strings';
+import { TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal';
+import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
const NLS_FIND_INPUT_LABEL = nls.localize('label.find', "Find");
-const NLS_FIND_INPUT_PLACEHOLDER = nls.localize('placeholder.find', "Find");
+const NLS_FIND_INPUT_PLACEHOLDER = nls.localize('placeholder.find', "Find (\u21C5 for history)");
const NLS_PREVIOUS_MATCH_BTN_LABEL = nls.localize('label.previousMatchButton', "Previous Match");
const NLS_NEXT_MATCH_BTN_LABEL = nls.localize('label.nextMatchButton', "Next Match");
const NLS_CLOSE_BTN_LABEL = nls.localize('label.closeButton', "Close");
@@ -30,6 +33,10 @@ interface IFindOptions {
showOptionButtons?: boolean;
checkImeCompletionState?: boolean;
showResultCount?: boolean;
+ appendCaseSensitiveLabel?: string;
+ appendRegexLabel?: string;
+ appendWholeWordsLabel?: string;
+ type?: 'Terminal' | 'Webview';
}
export abstract class SimpleFindWidget extends Widget {
@@ -47,14 +54,15 @@ export abstract class SimpleFindWidget extends Widget {
private _foundMatch: boolean = false;
constructor(
- @IContextViewService private readonly _contextViewService: IContextViewService,
- @IContextKeyService contextKeyService: IContextKeyService,
- private readonly _state: FindReplaceState = new FindReplaceState(),
- private readonly _options: IFindOptions
+ state: FindReplaceState = new FindReplaceState(),
+ options: IFindOptions,
+ contextViewService: IContextViewService,
+ contextKeyService: IContextKeyService,
+ private readonly _keybindingService: IKeybindingService
) {
super();
- this._findInput = this._register(new ContextScopedFindInput(null, this._contextViewService, {
+ this._findInput = this._register(new ContextScopedFindInput(null, contextViewService, {
label: NLS_FIND_INPUT_LABEL,
placeholder: NLS_FIND_INPUT_PLACEHOLDER,
validation: (value: string): InputBoxMessage | null => {
@@ -69,16 +77,19 @@ export abstract class SimpleFindWidget extends Widget {
this.updateButtons(this._foundMatch);
return { content: e.message };
}
- }
- }, contextKeyService, _options.showOptionButtons));
+ },
+ appendCaseSensitiveLabel: options.appendCaseSensitiveLabel && options.type === 'Terminal' ? this._getKeybinding(TerminalCommandId.ToggleFindCaseSensitive) : undefined,
+ appendRegexLabel: options.appendRegexLabel && options.type === 'Terminal' ? this._getKeybinding(TerminalCommandId.ToggleFindRegex) : undefined,
+ appendWholeWordsLabel: options.appendWholeWordsLabel && options.type === 'Terminal' ? this._getKeybinding(TerminalCommandId.ToggleFindWholeWord) : undefined
+ }, contextKeyService, options.showOptionButtons));
// Find History with update delayer
this._updateHistoryDelayer = new Delayer<void>(500);
this._register(this._findInput.onInput(async (e) => {
- if (!_options.checkImeCompletionState || !this._findInput.isImeSessionInProgress) {
+ if (!options.checkImeCompletionState || !this._findInput.isImeSessionInProgress) {
this._foundMatch = this._onInputChanged();
- if (this._options.showResultCount) {
+ if (options.showResultCount) {
await this.updateResultCount();
}
this.updateButtons(this._foundMatch);
@@ -87,22 +98,22 @@ export abstract class SimpleFindWidget extends Widget {
}
}));
- this._findInput.setRegex(!!this._state.isRegex);
- this._findInput.setCaseSensitive(!!this._state.matchCase);
- this._findInput.setWholeWords(!!this._state.wholeWord);
+ this._findInput.setRegex(!!state.isRegex);
+ this._findInput.setCaseSensitive(!!state.matchCase);
+ this._findInput.setWholeWords(!!state.wholeWord);
this._register(this._findInput.onDidOptionChange(() => {
- this._state.change({
+ state.change({
isRegex: this._findInput.getRegex(),
wholeWord: this._findInput.getWholeWords(),
matchCase: this._findInput.getCaseSensitive()
}, true);
}));
- this._register(this._state.onFindReplaceStateChange(() => {
- this._findInput.setRegex(this._state.isRegex);
- this._findInput.setWholeWords(this._state.wholeWord);
- this._findInput.setCaseSensitive(this._state.matchCase);
+ this._register(state.onFindReplaceStateChange(() => {
+ this._findInput.setRegex(state.isRegex);
+ this._findInput.setWholeWords(state.wholeWord);
+ this._findInput.setCaseSensitive(state.matchCase);
this.findFirst();
}));
@@ -162,7 +173,7 @@ export abstract class SimpleFindWidget extends Widget {
event.stopPropagation();
}));
- if (_options?.showResultCount) {
+ if (options?.showResultCount) {
this._domNode.classList.add('result-count');
this._register(this._findInput.onDidChange(() => {
this.updateResultCount();
@@ -209,6 +220,14 @@ export abstract class SimpleFindWidget extends Widget {
this._findInput.style(inputStyles);
}
+ private _getKeybinding(actionId: string): string {
+ let kb = this._keybindingService?.lookupKeybinding(actionId);
+ if (!kb) {
+ return '';
+ }
+ return ` (${kb.getLabel()})`;
+ }
+
override dispose() {
super.dispose();
@@ -307,9 +326,17 @@ export abstract class SimpleFindWidget extends Widget {
this._matchesCount.className = 'matchesCount';
}
this._matchesCount.innerText = '';
- const label = count === undefined || count.resultCount === 0 ? `No Results` : `${count.resultIndex + 1} of ${count.resultCount}`;
+ let label = '';
+ this._matchesCount.classList.toggle('no-results', false);
+ if (count?.resultCount && count?.resultCount <= 0) {
+ label = NLS_NO_RESULTS;
+ if (!!this.inputValue) {
+ this._matchesCount.classList.toggle('no-results', true);
+ }
+ } else if (count?.resultCount) {
+ label = strings.format(NLS_MATCHES_LOCATION, count.resultIndex + 1, count?.resultCount);
+ }
this._matchesCount.appendChild(document.createTextNode(label));
- this._matchesCount.classList.toggle('no-results', !count || count.resultCount === 0);
this._findInput?.domNode.insertAdjacentElement('afterend', this._matchesCount);
this._foundMatch = !!count && count.resultCount > 0;
}
@@ -336,4 +363,23 @@ registerThemingParticipant((theme, collector) => {
if (error) {
collector.addRule(`.no-results.matchesCount { color: ${error}; }`);
}
+
+ const toolbarHoverBackgroundColor = theme.getColor(toolbarHoverBackground);
+ if (toolbarHoverBackgroundColor) {
+ collector.addRule(`
+ div.simple-find-part-wrapper div.button:hover:not(.disabled) {
+ background-color: ${toolbarHoverBackgroundColor};
+ }
+ `);
+ }
+
+ const toolbarHoverOutlineColor = theme.getColor(toolbarHoverOutline);
+ if (toolbarHoverOutlineColor) {
+ collector.addRule(`
+ div.simple-find-part-wrapper div.button:hover:not(.disabled) {
+ outline: 1px dashed ${toolbarHoverOutlineColor};
+ outline-offset: -1px;
+ }
+ `);
+ }
});
diff --git a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts
index cec3c9c01f8..d2b4f005a8b 100644
--- a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts
+++ b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoLineQuickAccess.ts
@@ -12,7 +12,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
import { IQuickAccessRegistry, Extensions as QuickaccesExtensions } from 'vs/platform/quickinput/common/quickAccess';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor';
-import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
+import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
@@ -85,6 +85,11 @@ class GotoLineAction extends Action2 {
when: null,
primary: KeyMod.CtrlCmd | KeyCode.KeyG,
mac: { primary: KeyMod.WinCtrl | KeyCode.KeyG }
+ },
+ menu: {
+ id: MenuId.TitleMenuQuickPick,
+ group: '3/editorNav',
+ order: 2
}
});
}
diff --git a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts
index 703b333927a..a8b90ea7be3 100644
--- a/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts
+++ b/src/vs/workbench/contrib/codeEditor/browser/quickaccess/gotoSymbolQuickAccess.ts
@@ -201,7 +201,7 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess
item.highlights = undefined;
return true;
}
- const score = fuzzyScore(picker.value, picker.value.toLowerCase(), 1 /*@-character*/, item.label, item.label.toLowerCase(), 0, true);
+ const score = fuzzyScore(picker.value, picker.value.toLowerCase(), 1 /*@-character*/, item.label, item.label.toLowerCase(), 0, { firstMatchCanBeWeak: true, boostFullMatch: true });
if (!score) {
return false;
}
@@ -270,11 +270,15 @@ registerAction2(class GotoSymbolAction extends Action2 {
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyO
},
- menu: {
+ menu: [{
id: MenuId.MenubarGoMenu,
group: '4_symbol_nav',
order: 1
- }
+ }, {
+ id: MenuId.TitleMenuQuickPick,
+ group: '3/editorNav',
+ order: 1
+ }]
});
}
diff --git a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts
index 138f276bff7..9cadf75ecce 100644
--- a/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts
+++ b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts
@@ -111,7 +111,7 @@ export class SuggestEnabledInput extends Widget implements IThemable {
private readonly _onInputDidChange = new Emitter<string | undefined>();
readonly onInputDidChange: Event<string | undefined> = this._onInputDidChange.event;
- protected readonly inputWidget: CodeEditorWidget;
+ readonly inputWidget: CodeEditorWidget;
private readonly inputModel: ITextModel;
protected stylingContainer: HTMLDivElement;
private placeholderText: HTMLDivElement;
diff --git a/src/vs/workbench/contrib/codeEditor/browser/untitledTextEditorHint.ts b/src/vs/workbench/contrib/codeEditor/browser/untitledTextEditorHint.ts
index 306f9b93ce7..14c97806e09 100644
--- a/src/vs/workbench/contrib/codeEditor/browser/untitledTextEditorHint.ts
+++ b/src/vs/workbench/contrib/codeEditor/browser/untitledTextEditorHint.ts
@@ -19,6 +19,7 @@ import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { EventType as GestureEventType, Gesture } from 'vs/base/browser/touch';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
+import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
const $ = dom.$;
@@ -32,6 +33,7 @@ export class UntitledTextEditorHintContribution implements IEditorContribution {
constructor(
private editor: ICodeEditor,
+ @IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService,
@ICommandService private readonly commandService: ICommandService,
@IConfigurationService private readonly configurationService: IConfigurationService,
@IKeybindingService private readonly keybindingService: IKeybindingService,
@@ -53,7 +55,7 @@ export class UntitledTextEditorHintContribution implements IEditorContribution {
const model = this.editor.getModel();
if (model && model.uri.scheme === Schemas.untitled && model.getLanguageId() === PLAINTEXT_LANGUAGE_ID && configValue === 'text') {
- this.untitledTextHintContentWidget = new UntitledTextEditorHintContentWidget(this.editor, this.commandService, this.configurationService, this.keybindingService);
+ this.untitledTextHintContentWidget = new UntitledTextEditorHintContentWidget(this.editor, this.editorGroupsService, this.commandService, this.configurationService, this.keybindingService);
}
}
@@ -72,6 +74,7 @@ class UntitledTextEditorHintContentWidget implements IContentWidget {
constructor(
private readonly editor: ICodeEditor,
+ private readonly editorGroupsService: IEditorGroupsService,
private readonly commandService: ICommandService,
private readonly configurationService: IConfigurationService,
private readonly keybindingService: IKeybindingService,
@@ -103,6 +106,7 @@ class UntitledTextEditorHintContentWidget implements IContentWidget {
if (!this.domNode) {
this.domNode = $('.untitled-hint');
this.domNode.style.width = 'max-content';
+
const language = $('a.language-mode');
language.style.cursor = 'pointer';
language.innerText = localize('selectAlanguage2', "Select a language");
@@ -112,10 +116,31 @@ class UntitledTextEditorHintContentWidget implements IContentWidget {
language.title = localize('keyboardBindingTooltip', "{0}", languageKeybindingLabel);
}
this.domNode.appendChild(language);
+
+ const or = $('span');
+ or.innerText = localize('or', " or ",);
+ this.domNode.appendChild(or);
+
+ const editorType = $('a.editor-type');
+ editorType.style.cursor = 'pointer';
+ editorType.innerText = localize('openADifferentEditor', "open a different editor");
+ const selectEditorTypeKeyBinding = this.keybindingService.lookupKeybinding('welcome.showNewFileEntries');
+ const selectEditorTypeKeybindingLabel = selectEditorTypeKeyBinding?.getLabel();
+ if (selectEditorTypeKeybindingLabel) {
+ editorType.title = localize('keyboardBindingTooltip', "{0}", selectEditorTypeKeybindingLabel);
+ }
+ this.domNode.appendChild(editorType);
+
const toGetStarted = $('span');
- toGetStarted.innerText = localize('toGetStarted', " to get started. Start typing to dismiss, or ",);
+ toGetStarted.innerText = localize('toGetStarted', " to get started.");
this.domNode.appendChild(toGetStarted);
+ this.domNode.appendChild($('br'));
+
+ const startTyping = $('span');
+ startTyping.innerText = localize('startTyping', "Start typing to dismiss or ");
+ this.domNode.appendChild(startTyping);
+
const dontShow = $('a');
dontShow.style.cursor = 'pointer';
dontShow.innerText = localize('dontshow', "don't show");
@@ -136,6 +161,21 @@ class UntitledTextEditorHintContentWidget implements IContentWidget {
this.toDispose.push(dom.addDisposableListener(language, GestureEventType.Tap, languageOnClickOrTap));
this.toDispose.push(Gesture.addTarget(language));
+ const chooseEditorOnClickOrTap = async (e: MouseEvent) => {
+ e.stopPropagation();
+
+ const activeEditorInput = this.editorGroupsService.activeGroup.activeEditor;
+ const newEditorSelected = await this.commandService.executeCommand('welcome.showNewFileEntries', { from: 'hint' });
+
+ // Close the active editor as long as it is untitled (swap the editors out)
+ if (newEditorSelected && activeEditorInput !== null && activeEditorInput.resource?.scheme === Schemas.untitled) {
+ this.editorGroupsService.activeGroup.closeEditor(activeEditorInput, { preserveFocus: true });
+ }
+ };
+ this.toDispose.push(dom.addDisposableListener(editorType, 'click', chooseEditorOnClickOrTap));
+ this.toDispose.push(dom.addDisposableListener(editorType, GestureEventType.Tap, chooseEditorOnClickOrTap));
+ this.toDispose.push(Gesture.addTarget(editorType));
+
const dontShowOnClickOrTap = () => {
this.configurationService.updateValue(untitledTextEditorHintSetting, 'hidden');
this.dispose();
diff --git a/src/vs/workbench/contrib/comments/browser/commentColors.ts b/src/vs/workbench/contrib/comments/browser/commentColors.ts
index 3fc12217f70..127c30c86f8 100644
--- a/src/vs/workbench/contrib/comments/browser/commentColors.ts
+++ b/src/vs/workbench/contrib/comments/browser/commentColors.ts
@@ -5,12 +5,17 @@
import { Color } from 'vs/base/common/color';
import * as languages from 'vs/editor/common/languages';
+import { peekViewBorder } from 'vs/editor/contrib/peekView/browser/peekView';
import * as nls from 'vs/nls';
-import { contrastBorder, editorWarningForeground, editorWidgetForeground, registerColor } from 'vs/platform/theme/common/colorRegistry';
+import { contrastBorder, disabledForeground, registerColor, transparent } from 'vs/platform/theme/common/colorRegistry';
import { IColorTheme } from 'vs/platform/theme/common/themeService';
-export const resolvedCommentBorder = registerColor('comments.resolved.border', { dark: editorWidgetForeground, light: editorWidgetForeground, hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('resolvedCommentBorder', 'Color of borders and arrow for resolved comments.'));
-export const unresolvedCommentBorder = registerColor('comments.unresolved.border', { dark: editorWarningForeground, light: editorWarningForeground, hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('unresolvedCommentBorder', 'Color of borders and arrow for unresolved comments.'));
+const resolvedCommentBorder = registerColor('editorCommentsWidget.resolvedBorder', { dark: disabledForeground, light: disabledForeground, hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('resolvedCommentBorder', 'Color of borders and arrow for resolved comments.'));
+const unresolvedCommentBorder = registerColor('editorCommentsWidget.unresolvedBorder', { dark: peekViewBorder, light: peekViewBorder, hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('unresolvedCommentBorder', 'Color of borders and arrow for unresolved comments.'));
+export const commentThreadRangeBackground = registerColor('editorCommentsWidget.rangeBackground', { dark: transparent(unresolvedCommentBorder, .1), light: transparent(unresolvedCommentBorder, .1), hcDark: transparent(unresolvedCommentBorder, .1), hcLight: transparent(unresolvedCommentBorder, .1) }, nls.localize('commentThreadRangeBackground', 'Color of background for comment ranges.'));
+export const commentThreadRangeBorder = registerColor('editorCommentsWidget.rangeBorder', { dark: transparent(unresolvedCommentBorder, .4), light: transparent(unresolvedCommentBorder, .4), hcDark: transparent(unresolvedCommentBorder, .4), hcLight: transparent(unresolvedCommentBorder, .4) }, nls.localize('commentThreadRangeBorder', 'Color of border for comment ranges.'));
+export const commentThreadRangeActiveBackground = registerColor('editorCommentsWidget.rangeActiveBackground', { dark: transparent(unresolvedCommentBorder, .1), light: transparent(unresolvedCommentBorder, .1), hcDark: transparent(unresolvedCommentBorder, .1), hcLight: transparent(unresolvedCommentBorder, .1) }, nls.localize('commentThreadActiveRangeBackground', 'Color of background for currently selected or hovered comment range.'));
+export const commentThreadRangeActiveBorder = registerColor('editorCommentsWidget.rangeActiveBorder', { dark: transparent(unresolvedCommentBorder, .4), light: transparent(unresolvedCommentBorder, .4), hcDark: transparent(unresolvedCommentBorder, .4), hcLight: transparent(unresolvedCommentBorder, .2) }, nls.localize('commentThreadActiveRangeBorder', 'Color of border for currently selected or hovered comment range.'));
const commentThreadStateColors = new Map([
[languages.CommentThreadState.Unresolved, unresolvedCommentBorder],
@@ -18,8 +23,10 @@ const commentThreadStateColors = new Map([
]);
export const commentThreadStateColorVar = '--comment-thread-state-color';
+export const commentViewThreadStateColorVar = '--comment-view-thread-state-color';
+export const commentThreadStateBackgroundColorVar = '--comment-thread-state-background-color';
-export function getCommentThreadStateColor(thread: languages.CommentThread, theme: IColorTheme): Color | undefined {
- const colorId = thread.state !== undefined ? commentThreadStateColors.get(thread.state) : undefined;
- return colorId !== undefined ? theme.getColor(colorId) : undefined;
+export function getCommentThreadStateColor(state: languages.CommentThreadState | undefined, theme: IColorTheme): Color | undefined {
+ const colorId = (state !== undefined) ? commentThreadStateColors.get(state) : undefined;
+ return (colorId !== undefined) ? theme.getColor(colorId) : undefined;
}
diff --git a/src/vs/workbench/contrib/comments/browser/commentGlyphWidget.ts b/src/vs/workbench/contrib/comments/browser/commentGlyphWidget.ts
index 71be0f04c36..7be06df3a41 100644
--- a/src/vs/workbench/contrib/comments/browser/commentGlyphWidget.ts
+++ b/src/vs/workbench/contrib/comments/browser/commentGlyphWidget.ts
@@ -62,7 +62,7 @@ export class CommentGlyphWidget {
return {
position: {
- lineNumber: range ? range.startLineNumber : this._lineNumber,
+ lineNumber: range ? range.endLineNumber : this._lineNumber,
column: 1
},
preference: [ContentWidgetPositionPreference.EXACT]
diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts
index d99f9c41a0b..db768308374 100644
--- a/src/vs/workbench/contrib/comments/browser/commentNode.ts
+++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts
@@ -165,10 +165,11 @@ export class CommentNode<T extends IRange | ICellRange> extends Disposable {
private createHeader(commentDetailsContainer: HTMLElement): void {
const header = dom.append(commentDetailsContainer, dom.$(`div.comment-title.${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME}`));
- const author = dom.append(header, dom.$('strong.author'));
+ const infoContainer = dom.append(header, dom.$('comment-header-info'));
+ const author = dom.append(infoContainer, dom.$('strong.author'));
author.innerText = this.comment.userName;
- this.createTimestamp(header);
- this._isPendingLabel = dom.append(header, dom.$('span.isPending'));
+ this.createTimestamp(infoContainer);
+ this._isPendingLabel = dom.append(infoContainer, dom.$('span.isPending'));
if (this.comment.label) {
this._isPendingLabel.innerText = this.comment.label;
diff --git a/src/vs/workbench/contrib/comments/browser/commentReply.ts b/src/vs/workbench/contrib/comments/browser/commentReply.ts
index 6b9c90cc2ba..cf2e81a3bb9 100644
--- a/src/vs/workbench/contrib/comments/browser/commentReply.ts
+++ b/src/vs/workbench/contrib/comments/browser/commentReply.ts
@@ -275,7 +275,6 @@ export class CommentReply<T extends IRange | ICellRange> extends Disposable {
}
private hideReplyArea() {
- this.commentEditor.setValue('');
this.commentEditor.getDomNode()!.style.outline = '';
this._pendingComment = '';
this.form.classList.remove('expand');
diff --git a/src/vs/workbench/contrib/comments/browser/commentService.ts b/src/vs/workbench/contrib/comments/browser/commentService.ts
index a11e4b8fb15..eed900b49eb 100644
--- a/src/vs/workbench/contrib/comments/browser/commentService.ts
+++ b/src/vs/workbench/contrib/comments/browser/commentService.ts
@@ -67,6 +67,7 @@ export interface ICommentService {
readonly onDidUpdateCommentThreads: Event<ICommentThreadChangedEvent>;
readonly onDidUpdateNotebookCommentThreads: Event<INotebookCommentThreadChangedEvent>;
readonly onDidChangeActiveCommentThread: Event<CommentThread | null>;
+ readonly onDidChangeCurrentCommentThread: Event<CommentThread | undefined>;
readonly onDidUpdateCommentingRanges: Event<{ owner: string }>;
readonly onDidChangeActiveCommentingRange: Event<{ range: Range; commentingRangesInfo: CommentingRanges }>;
readonly onDidSetDataProvider: Event<void>;
@@ -90,6 +91,7 @@ export interface ICommentService {
hasReactionHandler(owner: string): boolean;
toggleReaction(owner: string, resource: URI, thread: CommentThread<IRange | ICellRange>, comment: Comment, reaction: CommentReaction): Promise<void>;
setActiveCommentThread(commentThread: CommentThread<IRange | ICellRange> | null): void;
+ setCurrentCommentThread(commentThread: CommentThread<IRange | ICellRange> | undefined): void;
}
export class CommentService extends Disposable implements ICommentService {
@@ -119,6 +121,9 @@ export class CommentService extends Disposable implements ICommentService {
private readonly _onDidChangeActiveCommentThread = this._register(new Emitter<CommentThread | null>());
readonly onDidChangeActiveCommentThread = this._onDidChangeActiveCommentThread.event;
+ private readonly _onDidChangeCurrentCommentThread = this._register(new Emitter<CommentThread | undefined>());
+ readonly onDidChangeCurrentCommentThread = this._onDidChangeCurrentCommentThread.event;
+
private readonly _onDidChangeActiveCommentingRange: Emitter<{
range: Range; commentingRangesInfo:
CommentingRanges;
@@ -137,6 +142,18 @@ export class CommentService extends Disposable implements ICommentService {
super();
}
+ /**
+ * The current comment thread is the thread that has focus or is being hovered.
+ * @param commentThread
+ */
+ setCurrentCommentThread(commentThread: CommentThread | undefined) {
+ this._onDidChangeCurrentCommentThread.fire(commentThread);
+ }
+
+ /**
+ * The active comment thread is the the thread that is currently being edited.
+ * @param commentThread
+ */
setActiveCommentThread(commentThread: CommentThread | null) {
this._onDidChangeActiveCommentThread.fire(commentThread);
}
diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts b/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts
index fca40378daa..59a1d57b12b 100644
--- a/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts
+++ b/src/vs/workbench/contrib/comments/browser/commentThreadBody.ts
@@ -209,8 +209,14 @@ export class CommentThreadBody<T extends IRange | ICellRange = IRange> extends D
}
private _updateAriaLabel() {
- this._commentsElement.ariaLabel = nls.localize('commentThreadAria', "Comment thread with {0} comments. {1}.",
- this._commentThread.comments?.length, this._commentThread.label);
+ if (this._commentThread.isDocumentCommentThread()) {
+ this._commentsElement.ariaLabel = nls.localize('commentThreadAria.withRange', "Comment thread with {0} comments on lines {1} through {2}. {3}.",
+ this._commentThread.comments?.length, this._commentThread.range.startLineNumber, this._commentThread.range.endLineNumber,
+ this._commentThread.label);
+ } else {
+ this._commentsElement.ariaLabel = nls.localize('commentThreadAria', "Comment thread with {0} comments. {1}.",
+ this._commentThread.comments?.length, this._commentThread.label);
+ }
}
private _setFocusedComment(value: number | undefined) {
diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadRangeDecorator.ts b/src/vs/workbench/contrib/comments/browser/commentThreadRangeDecorator.ts
new file mode 100644
index 00000000000..5af04a9fe0c
--- /dev/null
+++ b/src/vs/workbench/contrib/comments/browser/commentThreadRangeDecorator.ts
@@ -0,0 +1,99 @@
+/*---------------------------------------------------------------------------------------------
+ * 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 { ICodeEditor } from 'vs/editor/browser/editorBrowser';
+import { IRange } from 'vs/editor/common/core/range';
+import { IModelDecorationOptions, IModelDeltaDecoration } from 'vs/editor/common/model';
+import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
+import { ICommentInfo, ICommentService } from 'vs/workbench/contrib/comments/browser/commentService';
+
+class CommentThreadRangeDecoration implements IModelDeltaDecoration {
+ private _decorationId: string | undefined;
+
+ public get id(): string | undefined {
+ return this._decorationId;
+ }
+
+ public set id(id: string | undefined) {
+ this._decorationId = id;
+ }
+
+ constructor(
+ public readonly range: IRange,
+ public readonly options: ModelDecorationOptions) {
+ }
+}
+
+export class CommentThreadRangeDecorator extends Disposable {
+ private static description = 'comment-thread-range-decorator';
+ private decorationOptions: ModelDecorationOptions;
+ private activeDecorationOptions: ModelDecorationOptions;
+ private decorationIds: string[] = [];
+ private activeDecorationIds: string[] = [];
+ private editor: ICodeEditor | undefined;
+
+ constructor(commentService: ICommentService) {
+ super();
+ const decorationOptions: IModelDecorationOptions = {
+ description: CommentThreadRangeDecorator.description,
+ isWholeLine: false,
+ zIndex: 20,
+ className: 'comment-thread-range'
+ };
+
+ this.decorationOptions = ModelDecorationOptions.createDynamic(decorationOptions);
+
+ const activeDecorationOptions: IModelDecorationOptions = {
+ description: CommentThreadRangeDecorator.description,
+ isWholeLine: false,
+ zIndex: 20,
+ className: 'comment-thread-range-current'
+ };
+
+ this.activeDecorationOptions = ModelDecorationOptions.createDynamic(activeDecorationOptions);
+ this._register(commentService.onDidChangeCurrentCommentThread(thread => {
+ if (!this.editor) {
+ return;
+ }
+ let newDecoration: CommentThreadRangeDecoration[] = [];
+ if (thread) {
+ const range = thread.range;
+ if (!((range.startLineNumber === range.endLineNumber) && (range.startColumn === range.endColumn))) {
+ newDecoration.push(new CommentThreadRangeDecoration(range, this.activeDecorationOptions));
+ }
+ }
+ this.activeDecorationIds = this.editor.deltaDecorations(this.activeDecorationIds, newDecoration);
+ newDecoration.forEach((decoration, index) => decoration.id = this.decorationIds[index]);
+ }));
+ }
+
+ public update(editor: ICodeEditor, commentInfos: ICommentInfo[]) {
+ const model = editor.getModel();
+ if (!model) {
+ return;
+ }
+ this.editor = editor;
+
+ const commentThreadRangeDecorations: CommentThreadRangeDecoration[] = [];
+ for (const info of commentInfos) {
+ info.threads.forEach(thread => {
+ if (thread.isDisposed) {
+ return;
+ }
+ const range = thread.range;
+ // We only want to show a range decoration when there's the range spans either multiple lines
+ // or, when is spans multiple characters on the sample line
+ if ((range.startLineNumber === range.endLineNumber) && (range.startColumn === range.endColumn)) {
+ return;
+ }
+ commentThreadRangeDecorations.push(new CommentThreadRangeDecoration(range, this.decorationOptions));
+ });
+ }
+
+ this.decorationIds = editor.deltaDecorations(this.decorationIds, commentThreadRangeDecorations);
+ commentThreadRangeDecorations.forEach((decoration, index) => decoration.id = this.decorationIds[index]);
+ }
+}
diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts
index b4989b77691..5759d2f3783 100644
--- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts
+++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts
@@ -24,7 +24,7 @@ import { IColorTheme } from 'vs/platform/theme/common/themeService';
import { contrastBorder, focusBorder, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, textBlockQuoteBackground, textBlockQuoteBorder, textLinkActiveForeground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry';
import { PANEL_BORDER } from 'vs/workbench/common/theme';
import { IRange } from 'vs/editor/common/core/range';
-import { commentThreadStateColorVar } from 'vs/workbench/contrib/comments/browser/commentColors';
+import { commentThreadStateBackgroundColorVar, commentThreadStateColorVar } from 'vs/workbench/contrib/comments/browser/commentColors';
import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange';
import { FontInfo } from 'vs/editor/common/config/fontInfo';
@@ -109,6 +109,41 @@ export class CommentThreadWidget<T extends IRange | ICellRange = IRange> extends
if (controller) {
commentControllerKey.set(controller.contextValue);
}
+
+ this.currentThreadListeners();
+ }
+
+ private updateCurrentThread(hasMouse: boolean, hasFocus: boolean) {
+ if (hasMouse || hasFocus) {
+ this.commentService.setCurrentCommentThread(this.commentThread);
+ } else {
+ this.commentService.setCurrentCommentThread(undefined);
+ }
+ }
+
+ private currentThreadListeners() {
+ let hasMouse = false;
+ let hasFocus = false;
+ this._register(dom.addDisposableListener(this.container, dom.EventType.MOUSE_ENTER, (e) => {
+ if ((<any>e).toElement === this.container) {
+ hasMouse = true;
+ this.updateCurrentThread(hasMouse, hasFocus);
+ }
+ }, true));
+ this._register(dom.addDisposableListener(this.container, dom.EventType.MOUSE_LEAVE, (e) => {
+ if ((<any>e).fromElement === this.container) {
+ hasMouse = false;
+ this.updateCurrentThread(hasMouse, hasFocus);
+ }
+ }, true));
+ this._register(dom.addDisposableListener(this.container, dom.EventType.FOCUS_IN, () => {
+ hasFocus = true;
+ this.updateCurrentThread(hasMouse, hasFocus);
+ }, true));
+ this._register(dom.addDisposableListener(this.container, dom.EventType.FOCUS_OUT, () => {
+ hasFocus = false;
+ this.updateCurrentThread(hasMouse, hasFocus);
+ }, true));
}
updateCommentThread(commentThread: languages.CommentThread<T>) {
@@ -161,7 +196,10 @@ export class CommentThreadWidget<T extends IRange | ICellRange = IRange> extends
this._onDidResize.fire(dimension);
}
-
+ override dispose() {
+ super.dispose();
+ this.updateCurrentThread(false, false);
+ }
private _bindCommentThreadListeners() {
this._commentThreadDisposables.push(this._commentThread.onDidChangeCanReply(() => {
@@ -248,6 +286,7 @@ export class CommentThreadWidget<T extends IRange | ICellRange = IRange> extends
const content: string[] = [];
content.push(`.monaco-editor .review-widget > .body { border-top: 1px solid var(${commentThreadStateColorVar}) }`);
+ content.push(`.monaco-editor .review-widget > .head { background-color: var(${commentThreadStateBackgroundColorVar}) }`);
const linkColor = theme.getColor(textLinkForeground);
if (linkColor) {
diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts
index 151a49ffbb6..c6a047179b0 100644
--- a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts
+++ b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts
@@ -23,10 +23,10 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { CommentThreadWidget } from 'vs/workbench/contrib/comments/browser/commentThreadWidget';
import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange';
-import { commentThreadStateColorVar, getCommentThreadStateColor } from 'vs/workbench/contrib/comments/browser/commentColors';
+import { commentThreadStateBackgroundColorVar, commentThreadStateColorVar, getCommentThreadStateColor } from 'vs/workbench/contrib/comments/browser/commentColors';
import { peekViewBorder } from 'vs/editor/contrib/peekView/browser/peekView';
-export function getCommentThreadWidgetStateColor(thread: languages.CommentThread, theme: IColorTheme): Color | undefined {
+export function getCommentThreadWidgetStateColor(thread: languages.CommentThreadState | undefined, theme: IColorTheme): Color | undefined {
return getCommentThreadStateColor(thread, theme) ?? theme.getColor(peekViewBorder);
}
@@ -85,7 +85,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
private readonly _globalToDispose = new DisposableStore();
private _commentThreadDisposables: IDisposable[] = [];
private _contextKeyService: IContextKeyService;
- private _scopedInstatiationService: IInstantiationService;
+ private _scopedInstantiationService: IInstantiationService;
public get owner(): string {
return this._owner;
@@ -109,7 +109,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
super(editor, { keepEditorSelection: true });
this._contextKeyService = contextKeyService.createScoped(this.domNode);
- this._scopedInstatiationService = instantiationService.createChild(new ServiceCollection(
+ this._scopedInstantiationService = instantiationService.createChild(new ServiceCollection(
[IContextKeyService, this._contextKeyService]
));
@@ -184,16 +184,16 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
protected _fillContainer(container: HTMLElement): void {
this.setCssClass('review-widget');
- this._commentThreadWidget = this._scopedInstatiationService.createInstance(
+ this._commentThreadWidget = this._scopedInstantiationService.createInstance(
CommentThreadWidget,
container,
this._owner,
this.editor.getModel()!.uri,
this._contextKeyService,
- this._scopedInstatiationService,
+ this._scopedInstantiationService,
this._commentThread as unknown as languages.CommentThread<IRange | ICellRange>,
this._pendingComment,
- { editor: this.editor },
+ { editor: this.editor, codeBlockFontSize: '' },
this._commentOptions,
{
actionRunner: () => {
@@ -201,7 +201,17 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
let newPosition = this.getPosition();
if (newPosition) {
- this.commentService.updateCommentThreadTemplate(this.owner, this._commentThread.commentThreadHandle, new Range(newPosition.lineNumber, 1, newPosition.lineNumber, 1));
+ let range: Range;
+ const originalRange = this._commentThread.range;
+ if (newPosition.lineNumber !== originalRange.endLineNumber) {
+ // The widget could have moved as a result of editor changes.
+ // We need to try to calculate the new, more correct, range for the comment.
+ const distance = newPosition.lineNumber - this._commentThread.range.endLineNumber;
+ range = new Range(originalRange.startLineNumber + distance, originalRange.startColumn, originalRange.endLineNumber + distance, originalRange.endColumn);
+ } else {
+ range = new Range(originalRange.startLineNumber, originalRange.startColumn, originalRange.endLineNumber, originalRange.endColumn);
+ }
+ this.commentService.updateCommentThreadTemplate(this.owner, this._commentThread.commentThreadHandle, range);
}
}
},
@@ -261,7 +271,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
this._commentThreadWidget.updateCommentThread(commentThread);
// Move comment glyph widget and show position if the line has changed.
- const lineNumber = this._commentThread.range.startLineNumber;
+ const lineNumber = this._commentThread.range.endLineNumber;
let shouldMoveWidget = false;
if (this._commentGlyph) {
if (this._commentGlyph.getPosition().position!.lineNumber !== lineNumber) {
@@ -344,12 +354,13 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
this._commentThreadDisposables.push(this._commentThread.onDidChangeState(() => {
const borderColor =
- getCommentThreadWidgetStateColor(this._commentThread, this.themeService.getColorTheme()) || Color.transparent;
+ getCommentThreadWidgetStateColor(this._commentThread.state, this.themeService.getColorTheme()) || Color.transparent;
this.style({
frameColor: borderColor,
arrowColor: borderColor,
});
this.container?.style.setProperty(commentThreadStateColorVar, `${borderColor}`);
+ this.container?.style.setProperty(commentThreadStateBackgroundColorVar, `${borderColor.transparent(.1)}`);
}));
}
@@ -410,7 +421,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget
}
private _applyTheme(theme: IColorTheme) {
- const borderColor = getCommentThreadWidgetStateColor(this._commentThread, this.themeService.getColorTheme()) || Color.transparent;
+ const borderColor = getCommentThreadWidgetStateColor(this._commentThread.state, this.themeService.getColorTheme()) || Color.transparent;
this.style({
arrowColor: borderColor,
frameColor: borderColor
diff --git a/src/vs/workbench/contrib/comments/browser/comments.contribution.ts b/src/vs/workbench/contrib/comments/browser/comments.contribution.ts
index 0d515f2ae79..2ba619a7c0c 100644
--- a/src/vs/workbench/contrib/comments/browser/comments.contribution.ts
+++ b/src/vs/workbench/contrib/comments/browser/comments.contribution.ts
@@ -24,9 +24,9 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).regis
markdownDeprecationMessage: nls.localize('comments.openPanel.deprecated', "This setting is deprecated in favor of `comments.openView`.")
},
'comments.openView': {
- enum: ['never', 'file'],
- enumDescriptions: [nls.localize('comments.openView.never', "The comments view will never be opened."), nls.localize('comments.openView.file', "The comments view will open when a file with comments is active.")],
- default: 'file',
+ enum: ['never', 'file', 'firstFile'],
+ enumDescriptions: [nls.localize('comments.openView.never', "The comments view will never be opened."), nls.localize('comments.openView.file', "The comments view will open when a file with comments is active."), nls.localize('comments.openView.firstFile', "If the comments view has not been opened yet during this session it will open the first time during a session that a file with comments is active.")],
+ default: 'firstFile',
description: nls.localize('comments.openView', "Controls when the comments view should open."),
restricted: false
},
diff --git a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts
index a172f418ba0..dd4fa30d7ff 100644
--- a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts
+++ b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts
@@ -46,6 +46,10 @@ import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { Position } from 'vs/editor/common/core/position';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
+import { CommentThreadRangeDecorator } from 'vs/workbench/contrib/comments/browser/commentThreadRangeDecorator';
+import { commentThreadRangeActiveBackground, commentThreadRangeActiveBorder, commentThreadRangeBackground, commentThreadRangeBorder } from 'vs/workbench/contrib/comments/browser/commentColors';
+import { ICursorSelectionChangedEvent } from 'vs/editor/common/cursorEvents';
+import { CommentsPanel } from 'vs/workbench/contrib/comments/browser/commentsView';
export const ID = 'editor.contrib.review';
@@ -66,6 +70,13 @@ export class ReviewViewZone implements IViewZone {
}
}
+interface CommentRangeAction {
+ ownerId: string;
+ extensionId: string | undefined;
+ label: string | undefined;
+ commentingRangesInfo: languages.CommentingRanges;
+}
+
class CommentingRangeDecoration implements IModelDeltaDecoration {
private _decorationId: string | undefined;
private _startLineNumber: number;
@@ -91,7 +102,7 @@ class CommentingRangeDecoration implements IModelDeltaDecoration {
this._endLineNumber = _range.endLineNumber;
}
- public getCommentAction(): { ownerId: string; extensionId: string | undefined; label: string | undefined; commentingRangesInfo: languages.CommentingRanges } {
+ public getCommentAction(): CommentRangeAction {
return {
extensionId: this._extensionId,
label: this._label,
@@ -111,13 +122,16 @@ class CommentingRangeDecoration implements IModelDeltaDecoration {
class CommentingRangeDecorator {
public static description = 'commenting-range-decorator';
- private decorationOptions!: ModelDecorationOptions;
- private hoverDecorationOptions!: ModelDecorationOptions;
+ private decorationOptions: ModelDecorationOptions;
+ private hoverDecorationOptions: ModelDecorationOptions;
+ private multilineDecorationOptions: ModelDecorationOptions;
private commentingRangeDecorations: CommentingRangeDecoration[] = [];
private decorationIds: string[] = [];
private _editor: ICodeEditor | undefined;
private _infos: ICommentInfo[] | undefined;
private _lastHover: number = -1;
+ private _lastSelection: Range | undefined;
+ private _lastSelectionCursor: number | undefined;
private _onDidChangeDecorationsCount: Emitter<number> = new Emitter();
public readonly onDidChangeDecorationsCount = this._onDidChangeDecorationsCount.event;
@@ -137,6 +151,14 @@ class CommentingRangeDecorator {
};
this.hoverDecorationOptions = ModelDecorationOptions.createDynamic(hoverDecorationOptions);
+
+ const multilineDecorationOptions: IModelDecorationOptions = {
+ description: CommentingRangeDecorator.description,
+ isWholeLine: true,
+ linesDecorationsClassName: `comment-range-glyph comment-diff-added multiline-add`
+ };
+
+ this.multilineDecorationOptions = ModelDecorationOptions.createDynamic(multilineDecorationOptions);
}
public updateHover(hoverLine?: number) {
@@ -146,27 +168,72 @@ class CommentingRangeDecorator {
this._lastHover = hoverLine ?? -1;
}
+ public updateSelection(cursorLine: number, range: Range = new Range(0, 0, 0, 0)) {
+ this._lastSelection = range.isEmpty() ? undefined : range;
+ this._lastSelectionCursor = range.isEmpty() ? undefined : cursorLine;
+ // Some scenarios:
+ // Selection is made. Emphasis should show on the drag/selection end location.
+ // Selection is made, then user clicks elsewhere. We should still show the decoration.
+ if (this._editor && this._infos) {
+ this._doUpdate(this._editor, this._infos, cursorLine, range);
+ }
+ }
+
public update(editor: ICodeEditor, commentInfos: ICommentInfo[]) {
this._editor = editor;
this._infos = commentInfos;
this._doUpdate(editor, commentInfos);
}
- private _doUpdate(editor: ICodeEditor, commentInfos: ICommentInfo[], hoverLine: number = -1) {
+ private _doUpdate(editor: ICodeEditor, commentInfos: ICommentInfo[], emphasisLine: number = -1, selectionRange: Range | undefined = this._lastSelection) {
let model = editor.getModel();
if (!model) {
return;
}
+ // If there's still a selection, use that.
+ emphasisLine = this._lastSelectionCursor ?? emphasisLine;
+
let commentingRangeDecorations: CommentingRangeDecoration[] = [];
for (const info of commentInfos) {
info.commentingRanges.ranges.forEach(range => {
- if ((range.startLineNumber <= hoverLine) && (range.endLineNumber >= hoverLine)) {
- const beforeRange = new Range(range.startLineNumber, 1, hoverLine, 1);
- const hoverRange = new Range(hoverLine, 1, hoverLine, 1);
- const afterRange = new Range(hoverLine, 1, range.endLineNumber, 1);
+ const rangeObject = new Range(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn);
+ let intersectingSelectionRange = selectionRange ? rangeObject.intersectRanges(selectionRange) : undefined;
+ if ((selectionRange && (emphasisLine >= 0) && intersectingSelectionRange)
+ // If there's only one selection line, then just drop into the else if and show an emphasis line.
+ && !((intersectingSelectionRange.startLineNumber === intersectingSelectionRange.endLineNumber)
+ && (emphasisLine === intersectingSelectionRange.startLineNumber))) {
+ // The emphasisLine should be the within the commenting range, even if the selection range stretches
+ // outside of the commenting range.
+ // Clip the emphasis and selection ranges to the commenting range
+ let intersectingEmphasisRange: Range;
+ if (emphasisLine <= intersectingSelectionRange.startLineNumber) {
+ intersectingEmphasisRange = intersectingSelectionRange.collapseToStart();
+ intersectingSelectionRange = new Range(intersectingSelectionRange.startLineNumber + 1, 1, intersectingSelectionRange.endLineNumber, 1);
+ } else {
+ intersectingEmphasisRange = new Range(intersectingSelectionRange.endLineNumber, 1, intersectingSelectionRange.endLineNumber, 1);
+ intersectingSelectionRange = new Range(intersectingSelectionRange.startLineNumber, 1, intersectingSelectionRange.endLineNumber - 1, 1);
+ }
+ commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, intersectingSelectionRange, this.multilineDecorationOptions, info.commentingRanges, true));
+ commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, intersectingEmphasisRange, this.hoverDecorationOptions, info.commentingRanges, true));
+
+ const beforeRangeEndLine = Math.min(intersectingEmphasisRange.startLineNumber, intersectingSelectionRange.startLineNumber) - 1;
+ const hasBeforeRange = rangeObject.startLineNumber <= beforeRangeEndLine;
+ const afterRangeStartLine = Math.max(intersectingEmphasisRange.endLineNumber, intersectingSelectionRange.endLineNumber) + 1;
+ const hasAfterRange = rangeObject.endLineNumber >= afterRangeStartLine;
+ if (hasBeforeRange) {
+ const beforeRange = new Range(range.startLineNumber, 1, beforeRangeEndLine, 1);
+ commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, beforeRange, this.decorationOptions, info.commentingRanges, true));
+ }
+ if (hasAfterRange) {
+ const afterRange = new Range(afterRangeStartLine, 1, range.endLineNumber, 1);
+ commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, afterRange, this.decorationOptions, info.commentingRanges, true));
+ }
+ } else if ((rangeObject.startLineNumber <= emphasisLine) && (emphasisLine <= rangeObject.endLineNumber)) {
+ const beforeRange = new Range(range.startLineNumber, 1, emphasisLine, 1);
+ const afterRange = new Range(emphasisLine, 1, range.endLineNumber, 1);
commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, beforeRange, this.decorationOptions, info.commentingRanges, true));
- commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, hoverRange, this.hoverDecorationOptions, info.commentingRanges, true));
+ commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, new Range(emphasisLine, 1, emphasisLine, 1), this.hoverDecorationOptions, info.commentingRanges, true));
commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, afterRange, this.decorationOptions, info.commentingRanges, true));
} else {
commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, range, this.decorationOptions, info.commentingRanges));
@@ -184,28 +251,35 @@ class CommentingRangeDecorator {
}
}
- public getMatchedCommentAction(line: number) {
+ public getMatchedCommentAction(commentRange: Range): CommentRangeAction[] {
// keys is ownerId
- const foundHoverActions = new Map<string, languages.CommentingRanges>();
- let result = [];
+ const foundHoverActions = new Map<string, { range: Range; action: CommentRangeAction }>();
for (const decoration of this.commentingRangeDecorations) {
const range = decoration.getActiveRange();
- if (range && range.startLineNumber <= line && line <= range.endLineNumber) {
- // We can have 3 commenting ranges that match from the same owner because of how
- // the line hover decoration is done. We only want to use the action from 1 of them.
+ if (range && ((range.startLineNumber <= commentRange.startLineNumber) || (commentRange.endLineNumber <= range.endLineNumber))) {
+ // We can have several commenting ranges that match from the same owner because of how
+ // the line hover and selection decoration is done.
+ // The ranges must be merged so that we can see if the new commentRange fits within them.
const action = decoration.getCommentAction();
- if (decoration.isHover) {
- if (foundHoverActions.get(action.ownerId) === action.commentingRangesInfo) {
- continue;
- } else {
- foundHoverActions.set(action.ownerId, action.commentingRangesInfo);
- }
+ const alreadyFoundInfo = foundHoverActions.get(action.ownerId);
+ if (alreadyFoundInfo?.action.commentingRangesInfo === action.commentingRangesInfo) {
+ // Merge ranges.
+ const newRange = new Range(
+ range.startLineNumber < alreadyFoundInfo.range.startLineNumber ? range.startLineNumber : alreadyFoundInfo.range.startLineNumber,
+ range.startColumn < alreadyFoundInfo.range.startColumn ? range.startColumn : alreadyFoundInfo.range.startColumn,
+ range.endLineNumber > alreadyFoundInfo.range.endLineNumber ? range.endLineNumber : alreadyFoundInfo.range.endLineNumber,
+ range.endColumn > alreadyFoundInfo.range.endColumn ? range.endColumn : alreadyFoundInfo.range.endColumn
+ );
+ foundHoverActions.set(action.ownerId, { range: newRange, action });
+ } else {
+ foundHoverActions.set(action.ownerId, { range, action });
}
- result.push(action);
}
}
- return result;
+ return Array.from(foundHoverActions.values()).filter(action => {
+ return (action.range.startLineNumber <= commentRange.startLineNumber) && (commentRange.endLineNumber <= action.range.endLineNumber);
+ }).map(actions => actions.action);
}
public dispose(): void {
@@ -225,11 +299,12 @@ export class CommentController implements IEditorContribution {
private _commentWidgets: ReviewZoneWidget[];
private _commentInfos: ICommentInfo[];
private _commentingRangeDecorator!: CommentingRangeDecorator;
+ private _commentThreadRangeDecorator!: CommentThreadRangeDecorator;
private mouseDownInfo: { lineNumber: number } | null = null;
private _commentingRangeSpaceReserved = false;
private _computePromise: CancelablePromise<Array<ICommentInfo | null>> | null;
private _addInProgress!: boolean;
- private _emptyThreadsToAddQueue: [number, IEditorMouseEvent | undefined][] = [];
+ private _emptyThreadsToAddQueue: [Range, IEditorMouseEvent | undefined][] = [];
private _computeCommentingRangePromise!: CancelablePromise<ICommentInfo[]> | null;
private _computeCommentingRangeScheduler!: Delayer<Array<ICommentInfo | null>> | null;
private _pendingCommentCache: { [key: string]: { [key: string]: string } };
@@ -268,6 +343,8 @@ export class CommentController implements IEditorContribution {
}
}));
+ this.globalToDispose.add(this._commentThreadRangeDecorator = new CommentThreadRangeDecorator(this.commentService));
+
this.globalToDispose.add(this.commentService.onDidDeleteDataProvider(ownerId => {
delete this._pendingCommentCache[ownerId];
this.beginCompute();
@@ -292,6 +369,8 @@ export class CommentController implements IEditorContribution {
this._editorDisposables.push(this.editor.onMouseMove(e => this.onEditorMouseMove(e)));
this._editorDisposables.push(this.editor.onDidChangeCursorPosition(e => this.onEditorChangeCursorPosition(e.position)));
this._editorDisposables.push(this.editor.onDidFocusEditorWidget(() => this.onEditorChangeCursorPosition(this.editor.getPosition())));
+ this._editorDisposables.push(this.editor.onDidChangeCursorSelection(e => this.onEditorChangeCursorSelection(e)));
+ this._editorDisposables.push(this.editor.onDidBlurEditorWidget(() => this.onEditorChangeCursorSelection()));
}
private clearEditorListeners() {
@@ -303,6 +382,13 @@ export class CommentController implements IEditorContribution {
this._commentingRangeDecorator.updateHover(e.target.position?.lineNumber);
}
+ private onEditorChangeCursorSelection(e?: ICursorSelectionChangedEvent): void {
+ const position = this.editor.getPosition()?.lineNumber;
+ if (position) {
+ this._commentingRangeDecorator.updateSelection(position, e?.selection);
+ }
+ }
+
private onEditorChangeCursorPosition(e: Position | null) {
const decorations = e ? this.editor.getDecorationsInRange(Range.fromPositions(e, { column: -1, lineNumber: e.lineNumber })) : undefined;
let hasCommentingRange = false;
@@ -512,6 +598,13 @@ export class CommentController implements IEditorContribution {
this._commentWidgets.splice(index, 1);
matchedZone.dispose();
}
+ const infosThreads = this._commentInfos.filter(info => info.owner === e.owner)[0].threads;
+ for (let i = 0; i < infosThreads.length; i++) {
+ if (infosThreads[i] === thread) {
+ infosThreads.splice(i, 1);
+ i--;
+ }
+ }
});
changed.forEach(thread => {
@@ -538,21 +631,31 @@ export class CommentController implements IEditorContribution {
this.displayCommentThread(e.owner, thread, pendingCommentText);
this._commentInfos.filter(info => info.owner === e.owner)[0].threads.push(thread);
});
-
+ this._commentThreadRangeDecorator.update(this.editor, commentInfo);
}));
- this.beginCompute().then(() => {
- if (this._commentWidgets.length
- && (this.configurationService.getValue<ICommentsConfiguration>(COMMENTS_SECTION).openView === 'file')) {
- this.viewsService.openView(COMMENTS_VIEW_ID);
+ this.beginCompute();
+ }
+
+ private async openCommentsView() {
+ if (this._commentWidgets.length) {
+ if (this.configurationService.getValue<ICommentsConfiguration>(COMMENTS_SECTION).openView === 'file') {
+ return this.viewsService.openView(COMMENTS_VIEW_ID);
+ } else if (this.configurationService.getValue<ICommentsConfiguration>(COMMENTS_SECTION).openView === 'firstFile') {
+ const hasShownView = this.viewsService.getViewWithId<CommentsPanel>(COMMENTS_VIEW_ID)?.hasRendered;
+ if (!hasShownView) {
+ return this.viewsService.openView(COMMENTS_VIEW_ID);
+ }
}
- });
+ }
+ return undefined;
}
private displayCommentThread(owner: string, thread: languages.CommentThread, pendingComment: string | null): void {
const zoneWidget = this.instantiationService.createInstance(ReviewZoneWidget, this.editor, owner, thread, pendingComment);
- zoneWidget.display(thread.range.startLineNumber);
+ zoneWidget.display(thread.range.endLineNumber);
this._commentWidgets.push(zoneWidget);
+ this.openCommentsView();
}
private onEditorMouseDown(e: IEditorMouseEvent): void {
@@ -569,26 +672,33 @@ export class CommentController implements IEditorContribution {
if (e.target.element.className.indexOf('comment-diff-added') >= 0) {
const lineNumber = e.target.position!.lineNumber;
- this.addOrToggleCommentAtLine(lineNumber, e);
+ // Check for selection at line number.
+ let range: Range = new Range(lineNumber, 1, lineNumber, 1);
+ const selection = this.editor.getSelection();
+ if (selection && (selection.startLineNumber <= lineNumber) && (lineNumber <= selection.endLineNumber)) {
+ range = selection;
+ this.editor.setSelection(new Range(selection.endLineNumber, 1, selection.endLineNumber, 1));
+ }
+ this.addOrToggleCommentAtLine(range, e);
}
}
- public async addOrToggleCommentAtLine(lineNumber: number, e: IEditorMouseEvent | undefined): Promise<void> {
+ public async addOrToggleCommentAtLine(commentRange: Range, e: IEditorMouseEvent | undefined): Promise<void> {
// If an add is already in progress, queue the next add and process it after the current one finishes to
// prevent empty comment threads from being added to the same line.
if (!this._addInProgress) {
this._addInProgress = true;
// The widget's position is undefined until the widget has been displayed, so rely on the glyph position instead
- const existingCommentsAtLine = this._commentWidgets.filter(widget => widget.getGlyphPosition() === lineNumber);
+ const existingCommentsAtLine = this._commentWidgets.filter(widget => widget.getGlyphPosition() === commentRange.endLineNumber);
if (existingCommentsAtLine.length) {
- existingCommentsAtLine.forEach(widget => widget.toggleExpand(lineNumber));
+ existingCommentsAtLine.forEach(widget => widget.toggleExpand(commentRange.endLineNumber));
this.processNextThreadToAdd();
return;
} else {
- this.addCommentAtLine(lineNumber, e);
+ this.addCommentAtLine(commentRange, e);
}
} else {
- this._emptyThreadsToAddQueue.push([lineNumber, e]);
+ this._emptyThreadsToAddQueue.push([commentRange, e]);
}
}
@@ -600,8 +710,8 @@ export class CommentController implements IEditorContribution {
}
}
- public addCommentAtLine(lineNumber: number, e: IEditorMouseEvent | undefined): Promise<void> {
- const newCommentInfos = this._commentingRangeDecorator.getMatchedCommentAction(lineNumber);
+ public addCommentAtLine(range: Range, e: IEditorMouseEvent | undefined): Promise<void> {
+ const newCommentInfos = this._commentingRangeDecorator.getMatchedCommentAction(range);
if (!newCommentInfos.length || !this.editor.hasModel()) {
return Promise.resolve();
}
@@ -612,7 +722,7 @@ export class CommentController implements IEditorContribution {
this.contextMenuService.showContextMenu({
getAnchor: () => anchor,
- getActions: () => this.getContextMenuActions(newCommentInfos, lineNumber),
+ getActions: () => this.getContextMenuActions(newCommentInfos, range),
getActionsContext: () => newCommentInfos.length ? newCommentInfos[0] : undefined,
onHide: () => { this._addInProgress = false; }
});
@@ -629,7 +739,7 @@ export class CommentController implements IEditorContribution {
if (commentInfos.length) {
const { ownerId } = commentInfos[0];
- this.addCommentAtLine2(lineNumber, ownerId);
+ this.addCommentAtLine2(range, ownerId);
}
}).then(() => {
this._addInProgress = false;
@@ -637,7 +747,7 @@ export class CommentController implements IEditorContribution {
}
} else {
const { ownerId } = newCommentInfos[0]!;
- this.addCommentAtLine2(lineNumber, ownerId);
+ this.addCommentAtLine2(range, ownerId);
}
return Promise.resolve();
@@ -656,7 +766,7 @@ export class CommentController implements IEditorContribution {
return picks;
}
- private getContextMenuActions(commentInfos: { ownerId: string; extensionId: string | undefined; label: string | undefined; commentingRangesInfo: languages.CommentingRanges }[], lineNumber: number): IAction[] {
+ private getContextMenuActions(commentInfos: { ownerId: string; extensionId: string | undefined; label: string | undefined; commentingRangesInfo: languages.CommentingRanges }[], commentRange: Range): IAction[] {
const actions: IAction[] = [];
commentInfos.forEach(commentInfo => {
@@ -668,7 +778,7 @@ export class CommentController implements IEditorContribution {
undefined,
true,
() => {
- this.addCommentAtLine2(lineNumber, ownerId);
+ this.addCommentAtLine2(commentRange, ownerId);
return Promise.resolve();
}
));
@@ -676,8 +786,7 @@ export class CommentController implements IEditorContribution {
return actions;
}
- public addCommentAtLine2(lineNumber: number, ownerId: string) {
- const range = new Range(lineNumber, 1, lineNumber, 1);
+ public addCommentAtLine2(range: Range, ownerId: string) {
this.commentService.createCommentThreadTemplate(ownerId, this.editor.getModel()!.uri, range);
this.processNextThreadToAdd();
return;
@@ -742,6 +851,7 @@ export class CommentController implements IEditorContribution {
});
this._commentingRangeDecorator.update(this.editor, this._commentInfos);
+ this._commentThreadRangeDecorator.update(this.editor, this._commentInfos);
}
public closeWidget(): void {
@@ -848,15 +958,15 @@ CommandsRegistry.registerCommand({
return Promise.resolve();
}
- const position = activeEditor.getPosition();
- return controller.addOrToggleCommentAtLine(position.lineNumber, undefined);
+ const position = activeEditor.getSelection();
+ return controller.addOrToggleCommentAtLine(position, undefined);
}
});
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: {
id: ADD_COMMENT_COMMAND,
- title: nls.localize('comments.addCommand', "Add Comment on Current Line"),
+ title: nls.localize('comments.addCommand', "Add Comment on Current Selection"),
category: 'Comments'
},
when: ActiveCursorHasCommentingRange
@@ -980,4 +1090,32 @@ registerThemingParticipant((theme, collector) => {
if (statusBarItemActiveBackground) {
collector.addRule(`.review-widget .body .review-comment .review-comment-contents .comment-reactions .action-item a.action-label:active { background-color: ${statusBarItemActiveBackground}; border: 1px solid transparent;}`);
}
+
+ const commentThreadRangeBackgroundColor = theme.getColor(commentThreadRangeBackground);
+ if (commentThreadRangeBackgroundColor) {
+ collector.addRule(`.monaco-editor .comment-thread-range { background-color: ${commentThreadRangeBackgroundColor};}`);
+ }
+
+ const commentThreadRangeBorderColor = theme.getColor(commentThreadRangeBorder);
+ if (commentThreadRangeBorderColor) {
+ collector.addRule(`.monaco-editor .comment-thread-range {
+ border-color: ${commentThreadRangeBorderColor};
+ border-width: 1px;
+ border-style: solid;
+ box-sizing: border-box; }`);
+ }
+
+ const commentThreadRangeActiveBackgroundColor = theme.getColor(commentThreadRangeActiveBackground);
+ if (commentThreadRangeActiveBackgroundColor) {
+ collector.addRule(`.monaco-editor .comment-thread-range-current { background-color: ${commentThreadRangeActiveBackgroundColor};}`);
+ }
+
+ const commentThreadRangeActiveBorderColor = theme.getColor(commentThreadRangeActiveBorder);
+ if (commentThreadRangeActiveBorderColor) {
+ collector.addRule(`.monaco-editor .comment-thread-range-current {
+ border-color: ${commentThreadRangeActiveBorderColor};
+ border-width: 1px;
+ border-style: solid;
+ box-sizing: border-box; }`);
+ }
});
diff --git a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts
index e291c7d32de..3166fd47195 100644
--- a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts
+++ b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts
@@ -18,12 +18,15 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { WorkbenchAsyncDataTree, IListService, IWorkbenchAsyncDataTreeOptions } from 'vs/platform/list/browser/listService';
-import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
+import { IColorTheme, IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IColorMapping } from 'vs/platform/theme/common/styler';
import { TimestampWidget } from 'vs/workbench/contrib/comments/browser/timestamp';
import { Codicon } from 'vs/base/common/codicons';
import { IMarkdownString } from 'vs/base/common/htmlContent';
+import { commentViewThreadStateColorVar, getCommentThreadStateColor } from 'vs/workbench/contrib/comments/browser/commentColors';
+import { CommentThreadState } from 'vs/editor/common/languages';
+import { Color } from 'vs/base/common/color';
export const COMMENTS_VIEW_ID = 'workbench.panel.comments';
export const COMMENTS_VIEW_TITLE = 'Comments';
@@ -50,11 +53,12 @@ interface IResourceTemplateData {
interface ICommentThreadTemplateData {
threadMetadata: {
- icon?: HTMLElement;
+ icon: HTMLElement;
userNames: HTMLSpanElement;
timestamp: TimestampWidget;
separator: HTMLElement;
commentPreview: HTMLSpanElement;
+ range: HTMLSpanElement;
};
repliesMetadata: {
container: HTMLElement;
@@ -121,7 +125,8 @@ export class CommentNodeRenderer implements IListRenderer<ITreeNode<CommentNode>
constructor(
@IOpenerService private readonly openerService: IOpenerService,
- @IConfigurationService private readonly configurationService: IConfigurationService
+ @IConfigurationService private readonly configurationService: IConfigurationService,
+ @IThemeService private themeService: IThemeService
) { }
renderTemplate(container: HTMLElement) {
@@ -134,7 +139,8 @@ export class CommentNodeRenderer implements IListRenderer<ITreeNode<CommentNode>
userNames: dom.append(metadataContainer, dom.$('.user')),
timestamp: new TimestampWidget(this.configurationService, dom.append(metadataContainer, dom.$('.timestamp-container'))),
separator: dom.append(metadataContainer, dom.$('.separator')),
- commentPreview: dom.append(metadataContainer, dom.$('.text'))
+ commentPreview: dom.append(metadataContainer, dom.$('.text')),
+ range: dom.append(metadataContainer, dom.$('.range'))
};
data.threadMetadata.separator.innerText = '\u00b7';
@@ -185,11 +191,17 @@ export class CommentNodeRenderer implements IListRenderer<ITreeNode<CommentNode>
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));
+ if (node.element.threadState !== undefined) {
+ const color = this.getCommentThreadWidgetStateColor(node.element.threadState, this.themeService.getColorTheme());
+ templateData.threadMetadata.icon.style.setProperty(commentViewThreadStateColorVar, `${color}`);
+ templateData.threadMetadata.icon.style.color = `var(${commentViewThreadStateColorVar}`;
+ }
templateData.threadMetadata.userNames.textContent = node.element.comment.userName;
templateData.threadMetadata.timestamp.setTimestamp(node.element.comment.timestamp ? new Date(node.element.comment.timestamp) : undefined);
const originalComment = node.element;
templateData.threadMetadata.commentPreview.innerText = '';
+ templateData.threadMetadata.commentPreview.style.height = '22px';
if (typeof originalComment.comment.body === 'string') {
templateData.threadMetadata.commentPreview.innerText = originalComment.comment.body;
} else {
@@ -201,6 +213,12 @@ export class CommentNodeRenderer implements IListRenderer<ITreeNode<CommentNode>
templateData.threadMetadata.commentPreview.title = renderedComment.element.textContent ?? '';
}
+ if (node.element.range.startLineNumber === node.element.range.endLineNumber) {
+ templateData.threadMetadata.range.textContent = nls.localize('commentLine', "[Ln {0}]", node.element.range.startLineNumber);
+ } else {
+ templateData.threadMetadata.range.textContent = nls.localize('commentRange', "[Ln {0}-{1}]", node.element.range.startLineNumber, node.element.range.endLineNumber);
+ }
+
if (!node.element.hasReply()) {
templateData.repliesMetadata.container.style.display = 'none';
return;
@@ -208,9 +226,13 @@ export class CommentNodeRenderer implements IListRenderer<ITreeNode<CommentNode>
templateData.repliesMetadata.container.style.display = '';
templateData.repliesMetadata.count.textContent = this.getCountString(commentCount);
- templateData.repliesMetadata.lastReplyDetail.textContent = nls.localize('lastReplyFrom', "Last reply from {0}", node.element.replies[node.element.replies.length - 1].comment.userName);
- templateData.repliesMetadata.timestamp.setTimestamp(originalComment.comment.timestamp ? new Date(originalComment.comment.timestamp) : undefined);
+ const lastComment = node.element.replies[node.element.replies.length - 1].comment;
+ templateData.repliesMetadata.lastReplyDetail.textContent = nls.localize('lastReplyFrom', "Last reply from {0}", lastComment.userName);
+ templateData.repliesMetadata.timestamp.setTimestamp(lastComment.timestamp ? new Date(lastComment.timestamp) : undefined);
+ }
+ private getCommentThreadWidgetStateColor(state: CommentThreadState | undefined, theme: IColorTheme): Color | undefined {
+ return (state !== undefined) ? getCommentThreadStateColor(state, theme) : undefined;
}
disposeTemplate(templateData: ICommentThreadTemplateData): void {
diff --git a/src/vs/workbench/contrib/comments/browser/commentsView.ts b/src/vs/workbench/contrib/comments/browser/commentsView.ts
index ad852ea3b7e..7077d35338b 100644
--- a/src/vs/workbench/contrib/comments/browser/commentsView.ts
+++ b/src/vs/workbench/contrib/comments/browser/commentsView.ts
@@ -120,7 +120,7 @@ export class CommentsPanel extends ViewPane {
const focusColor = theme.getColor(focusBorder);
if (focusColor) {
- content.push(`.comments-panel .commenst-panel-container a:focus { outline-color: ${focusColor}; }`);
+ content.push(`.comments-panel .comments-panel-container a:focus { outline-color: ${focusColor}; }`);
}
const codeTextForegroundColor = theme.getColor(textPreformatForeground);
@@ -147,6 +147,10 @@ export class CommentsPanel extends ViewPane {
}
}
+ public get hasRendered(): boolean {
+ return !!this.tree;
+ }
+
public override layoutBody(height: number, width: number): void {
super.layoutBody(height, width);
this.tree.layout(height, width);
diff --git a/src/vs/workbench/contrib/comments/browser/media/panel.css b/src/vs/workbench/contrib/comments/browser/media/panel.css
index 8b2e685cb8b..37bc9a17a45 100644
--- a/src/vs/workbench/contrib/comments/browser/media/panel.css
+++ b/src/vs/workbench/contrib/comments/browser/media/panel.css
@@ -49,8 +49,7 @@
.comments-panel .comments-panel-container .tree-container .comment-thread-container .comment-snippet-container .count,
.comments-panel .comments-panel-container .tree-container .comment-thread-container .comment-metadata-container .user {
- overflow: hidden;
- text-overflow: ellipsis;
+ min-width: fit-content;
}
.comments-panel .comments-panel-container .tree-container .comment-thread-container .comment-snippet-container .text {
@@ -74,6 +73,11 @@
text-overflow: ellipsis;
max-width: 500px;
overflow: hidden;
+ padding-right: 5px;
+}
+
+.comments-panel .comments-panel-container .tree-container .comment-thread-container .range {
+ opacity: 0.8;
}
.comments-panel .comments-panel-container .tree-container .comment-thread-container .comment-snippet-container .text code {
diff --git a/src/vs/workbench/contrib/comments/browser/media/review.css b/src/vs/workbench/contrib/comments/browser/media/review.css
index 2539c3e3960..f30e7d7109b 100644
--- a/src/vs/workbench/contrib/comments/browser/media/review.css
+++ b/src/vs/workbench/contrib/comments/browser/media/review.css
@@ -29,6 +29,11 @@
height: 21px;
}
+.review-widget .body .review-comment .comment-title .comment-header-info {
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
.review-widget .body .review-comment .comment-title {
display: flex;
width: 100%;
@@ -393,6 +398,7 @@ div.preview.inline .monaco-editor .comment-range-glyph {
.monaco-editor .margin-view-overlays > div:hover > .comment-range-glyph.comment-diff-added:before,
.monaco-editor .margin-view-overlays .comment-range-glyph.comment-diff-added.line-hover:before,
+.monaco-editor .margin-view-overlays .comment-range-glyph.comment-diff-added.multiline-add:before,
.monaco-editor .comment-range-glyph.comment-thread:before {
position: absolute;
height: 100%;
diff --git a/src/vs/workbench/contrib/comments/common/commentModel.ts b/src/vs/workbench/contrib/comments/common/commentModel.ts
index d8f97430b4b..06ec27f01c3 100644
--- a/src/vs/workbench/contrib/comments/common/commentModel.ts
+++ b/src/vs/workbench/contrib/comments/common/commentModel.ts
@@ -5,7 +5,7 @@
import { URI } from 'vs/base/common/uri';
import { IRange } from 'vs/editor/common/core/range';
-import { Comment, CommentThread, CommentThreadChangedEvent } from 'vs/editor/common/languages';
+import { Comment, CommentThread, CommentThreadChangedEvent, CommentThreadState } from 'vs/editor/common/languages';
import { groupBy } from 'vs/base/common/arrays';
import { localize } from 'vs/nls';
@@ -21,14 +21,16 @@ export class CommentNode {
replies: CommentNode[] = [];
resource: URI;
isRoot: boolean;
+ threadState?: CommentThreadState;
- constructor(owner: string, threadId: string, resource: URI, comment: Comment, range: IRange) {
+ constructor(owner: string, threadId: string, resource: URI, comment: Comment, range: IRange, threadState: CommentThreadState | undefined) {
this.owner = owner;
this.threadId = threadId;
this.comment = comment;
this.resource = resource;
this.range = range;
this.isRoot = false;
+ this.threadState = threadState;
}
hasReply(): boolean {
@@ -51,7 +53,7 @@ export class ResourceWithCommentThreads {
public static createCommentNode(owner: string, resource: URI, commentThread: CommentThread): CommentNode {
const { threadId, comments, range } = commentThread;
- const commentNodes: CommentNode[] = comments!.map(comment => new CommentNode(owner, threadId!, resource, comment, range));
+ const commentNodes: CommentNode[] = comments!.map(comment => new CommentNode(owner, threadId!, resource, comment, range, commentThread.state));
if (commentNodes.length > 1) {
commentNodes[0].replies = commentNodes.slice(1, commentNodes.length);
}
diff --git a/src/vs/workbench/contrib/comments/common/commentsConfiguration.ts b/src/vs/workbench/contrib/comments/common/commentsConfiguration.ts
index 1ef2ba67cb7..d3c180e69a4 100644
--- a/src/vs/workbench/contrib/comments/common/commentsConfiguration.ts
+++ b/src/vs/workbench/contrib/comments/common/commentsConfiguration.ts
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
export interface ICommentsConfiguration {
- openView: 'never' | 'file';
+ openView: 'never' | 'file' | 'firstFile';
useRelativeTime: boolean;
}
diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts
index 119a41914f1..81dfae861af 100644
--- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts
+++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts
@@ -124,6 +124,8 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput {
public override get capabilities(): EditorInputCapabilities {
let capabilities = EditorInputCapabilities.None;
+ capabilities |= EditorInputCapabilities.CanDropIntoEditor;
+
if (!this.customEditorService.getCustomEditorCapabilities(this.viewType)?.supportsMultipleEditorsPerDocument) {
capabilities |= EditorInputCapabilities.Singleton;
}
diff --git a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts
index acdf0b9005d..19b1e114ac0 100644
--- a/src/vs/workbench/contrib/debug/browser/baseDebugView.ts
+++ b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts
@@ -9,10 +9,12 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel';
import { IInputValidationOptions, InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
import { ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree';
+import { Codicon } from 'vs/base/common/codicons';
import { createMatches, FuzzyScore } from 'vs/base/common/filters';
import { once } from 'vs/base/common/functional';
import { KeyCode } from 'vs/base/common/keyCodes';
import { DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
+import { localize } from 'vs/nls';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { attachInputBoxStyler } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
@@ -112,7 +114,6 @@ export function renderVariable(variable: Variable, data: IVariableTemplateData,
}
data.expression.classList.toggle('lazy', !!variable.presentationHint?.lazy);
- data.lazyButton.title = variable.presentationHint?.lazy ? variable.value : '';
renderExpressionValue(variable, data.value, {
showChanged,
maxValueLength: MAX_VALUE_RENDER_LENGTH_IN_VIEWLET,
@@ -156,9 +157,10 @@ export abstract class AbstractExpressionsRenderer implements ITreeRenderer<IExpr
renderTemplate(container: HTMLElement): IExpressionTemplateData {
const expression = dom.append(container, $('.expression'));
const name = dom.append(expression, $('span.name'));
- const value = dom.append(expression, $('span.value'));
const lazyButton = dom.append(expression, $('span.lazy-button'));
- lazyButton.textContent = `(...)`;
+ lazyButton.classList.add(...Codicon.eye.classNamesArray);
+ lazyButton.title = localize('debug.lazyButton.tooltip', "Click to expand");
+ const value = dom.append(expression, $('span.value'));
const label = new HighlightedLabel(name);
diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts
index 762b74c8201..4eab6791a7f 100644
--- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts
+++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts
@@ -235,38 +235,48 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi
const breakpoints = this.debugService.getModel().getBreakpoints({ uri, lineNumber });
if (breakpoints.length) {
- // Show the dialog if there is a potential condition to be accidently lost.
- // Do not show dialog on linux due to electron issue freezing the mouse #50026
- if (!env.isLinux && breakpoints.some(bp => !!bp.condition || !!bp.logMessage || !!bp.hitCondition)) {
+ const isShiftPressed = e.event.shiftKey;
+ const enabled = breakpoints.some(bp => bp.enabled);
+
+ if (isShiftPressed) {
+ breakpoints.forEach(bp => this.debugService.enableOrDisableBreakpoints(!enabled, bp));
+ } else if (!env.isLinux && breakpoints.some(bp => !!bp.condition || !!bp.logMessage || !!bp.hitCondition)) {
+ // Show the dialog if there is a potential condition to be accidently lost.
+ // Do not show dialog on linux due to electron issue freezing the mouse #50026
const logPoint = breakpoints.every(bp => !!bp.logMessage);
const breakpointType = logPoint ? nls.localize('logPoint', "Logpoint") : nls.localize('breakpoint', "Breakpoint");
- const disable = breakpoints.some(bp => bp.enabled);
- const enabling = nls.localize('breakpointHasConditionDisabled',
+ const disabledBreakpointDialogMessage = nls.localize(
+ 'breakpointHasConditionDisabled',
"This {0} has a {1} that will get lost on remove. Consider enabling the {0} instead.",
breakpointType.toLowerCase(),
logPoint ? nls.localize('message', "message") : nls.localize('condition', "condition")
);
- const disabling = nls.localize('breakpointHasConditionEnabled',
+ const enabledBreakpointDialogMessage = nls.localize(
+ 'breakpointHasConditionEnabled',
"This {0} has a {1} that will get lost on remove. Consider disabling the {0} instead.",
breakpointType.toLowerCase(),
logPoint ? nls.localize('message', "message") : nls.localize('condition', "condition")
);
- const { choice } = await this.dialogService.show(severity.Info, disable ? disabling : enabling, [
- nls.localize('removeLogPoint', "Remove {0}", breakpointType),
- nls.localize('disableLogPoint', "{0} {1}", disable ? nls.localize('disable', "Disable") : nls.localize('enable', "Enable"), breakpointType),
- nls.localize('cancel', "Cancel")
- ], { cancelId: 2 });
+ const { choice } = await this.dialogService.show(
+ severity.Info,
+ enabled ? enabledBreakpointDialogMessage : disabledBreakpointDialogMessage,
+ [
+ nls.localize('removeLogPoint', "Remove {0}", breakpointType),
+ nls.localize('disableLogPoint', "{0} {1}", enabled ? nls.localize('disable', "Disable") : nls.localize('enable', "Enable"), breakpointType),
+ nls.localize('cancel', "Cancel")
+ ],
+ { cancelId: 2 },
+ );
if (choice === 0) {
breakpoints.forEach(bp => this.debugService.removeBreakpoints(bp.getId()));
}
if (choice === 1) {
- breakpoints.forEach(bp => this.debugService.enableOrDisableBreakpoints(!disable, bp));
+ breakpoints.forEach(bp => this.debugService.enableOrDisableBreakpoints(!enabled, bp));
}
} else {
- const enabled = breakpoints.some(bp => bp.enabled);
if (!enabled) {
breakpoints.forEach(bp => this.debugService.enableOrDisableBreakpoints(!enabled, bp));
} else {
@@ -485,7 +495,6 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi
inlineWidget
};
});
-
} finally {
this.ignoreDecorationsChangedEvent = false;
}
@@ -511,6 +520,12 @@ export class BreakpointEditorContribution implements IBreakpointEditorContributi
inlineWidget
};
});
+
+ for (const d of this.breakpointDecorations) {
+ if (d.inlineWidget) {
+ this.editor.layoutContentWidget(d.inlineWidget);
+ }
+ }
}
private async onModelDecorationsChanged(): Promise<void> {
diff --git a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts
index 4be819c4cb4..3df0cf71a74 100644
--- a/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts
+++ b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts
@@ -48,14 +48,10 @@ export interface IPrivateBreakpointWidgetService {
}
const DECORATION_KEY = 'breakpointwidgetdecoration';
-function isCurlyBracketOpen(input: IActiveCodeEditor): boolean {
+function isPositionInCurlyBracketBlock(input: IActiveCodeEditor): boolean {
const model = input.getModel();
- const prevBracket = model.bracketPairs.findPrevBracket(input.getPosition());
- if (prevBracket && prevBracket.isOpen) {
- return true;
- }
-
- return false;
+ const bracketPairs = model.bracketPairs.getBracketPairsInRange(Range.fromPositions(input.getPosition()));
+ return bracketPairs.some(p => p.openingBracketInfo.bracketText === '{');
}
function createDecorations(theme: IColorTheme, placeHolder: string): IDecorationOptions[] {
@@ -251,7 +247,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi
provideCompletionItems: (model: ITextModel, position: Position, _context: CompletionContext, token: CancellationToken): Promise<CompletionList> => {
let suggestionsPromise: Promise<CompletionList>;
const underlyingModel = this.editor.getModel();
- if (underlyingModel && (this.context === Context.CONDITION || (this.context === Context.LOG_MESSAGE && isCurlyBracketOpen(this.input)))) {
+ if (underlyingModel && (this.context === Context.CONDITION || (this.context === Context.LOG_MESSAGE && isPositionInCurlyBracketBlock(this.input)))) {
suggestionsPromise = provideSuggestionItems(this.languageFeaturesService.completionProvider, underlyingModel, new Position(this.lineNumber, 1), new CompletionOptions(undefined, new Set<CompletionItemKind>().add(CompletionItemKind.Snippet)), _context, token).then(suggestions => {
let overwriteBefore = 0;
diff --git a/src/vs/workbench/contrib/debug/browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts
index 687732063d7..6a045d3ce23 100644
--- a/src/vs/workbench/contrib/debug/browser/callStackView.ts
+++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts
@@ -3,50 +3,50 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { RunOnceScheduler } from 'vs/base/common/async';
import * as dom from 'vs/base/browser/dom';
-import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
-import { IDebugService, State, IStackFrame, IDebugSession, IThread, CONTEXT_CALLSTACK_ITEM_TYPE, IDebugModel, CALLSTACK_VIEW_ID, CONTEXT_DEBUG_STATE, getStateLabel, CONTEXT_STACK_FRAME_SUPPORTS_RESTART, CONTEXT_CALLSTACK_SESSION_IS_ATTACH, CONTEXT_CALLSTACK_ITEM_STOPPED, CONTEXT_CALLSTACK_SESSION_HAS_ONE_THREAD, IRawStoppedDetails } from 'vs/workbench/contrib/debug/common/debug';
-import { Thread, StackFrame, ThreadAndSessionIds } from 'vs/workbench/contrib/debug/common/debugModel';
+import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
+import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel';
+import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
+import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
+import { ITreeCompressionDelegate } from 'vs/base/browser/ui/tree/asyncDataTree';
+import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
+import { ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree';
+import { IAsyncDataSource, ITreeContextMenuEvent, ITreeNode } from 'vs/base/browser/ui/tree/tree';
+import { Action, IAction } from 'vs/base/common/actions';
+import { RunOnceScheduler } from 'vs/base/common/async';
+import { Codicon } from 'vs/base/common/codicons';
+import { Event } from 'vs/base/common/event';
+import { createMatches, FuzzyScore, IMatch } from 'vs/base/common/filters';
+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 { 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';
+import { ContextKeyExpr, ContextKeyExpression, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
-import { MenuId, IMenu, IMenuService, MenuItemAction, SubmenuItemAction, registerAction2, MenuRegistry } from 'vs/platform/actions/common/actions';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
-import { renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView';
-import { IAction, Action } from 'vs/base/common/actions';
-import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
-import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
-import { IContextKey, IContextKeyService, ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey';
-import { ViewPane, ViewAction } from 'vs/workbench/browser/parts/views/viewPane';
import { ILabelService } from 'vs/platform/label/common/label';
-import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
-import { createAndFillInContextMenuActions, createAndFillInActionBarActions, MenuEntryActionViewItem, SubmenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
-import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
-import { ITreeNode, ITreeContextMenuEvent, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree';
import { WorkbenchCompressibleAsyncDataTree } from 'vs/platform/list/browser/listService';
-import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel';
-import { createMatches, FuzzyScore, IMatch } from 'vs/base/common/filters';
-import { Event } from 'vs/base/common/event';
-import { dispose, IDisposable } from 'vs/base/common/lifecycle';
-import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
-import { isSessionAttach } from 'vs/workbench/contrib/debug/common/debugUtils';
-import { STOP_ID, STOP_LABEL, DISCONNECT_ID, DISCONNECT_LABEL, RESTART_SESSION_ID, RESTART_LABEL, STEP_OVER_ID, STEP_OVER_LABEL, STEP_INTO_LABEL, STEP_INTO_ID, STEP_OUT_LABEL, STEP_OUT_ID, PAUSE_ID, PAUSE_LABEL, CONTINUE_ID, CONTINUE_LABEL } from 'vs/workbench/contrib/debug/browser/debugCommands';
-import { IViewDescriptorService } from 'vs/workbench/common/views';
-import { textLinkForeground } from 'vs/platform/theme/common/colorRegistry';
-import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
+import { INotificationService } from 'vs/platform/notification/common/notification';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
+import { textLinkForeground } from 'vs/platform/theme/common/colorRegistry';
import { attachStylerCallback } from 'vs/platform/theme/common/styler';
-import { INotificationService } from 'vs/platform/notification/common/notification';
-import { commonSuffixLength } from 'vs/base/common/strings';
-import { posix } from 'vs/base/common/path';
-import { ITreeCompressionDelegate } from 'vs/base/browser/ui/tree/asyncDataTree';
-import { ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree';
-import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
+import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
+import { ViewAction, ViewPane } from 'vs/workbench/browser/parts/views/viewPane';
+import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
+import { IViewDescriptorService } from 'vs/workbench/common/views';
+import { renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView';
+import { CONTINUE_ID, CONTINUE_LABEL, DISCONNECT_ID, DISCONNECT_LABEL, PAUSE_ID, PAUSE_LABEL, RESTART_LABEL, RESTART_SESSION_ID, STEP_INTO_ID, STEP_INTO_LABEL, STEP_OUT_ID, STEP_OUT_LABEL, STEP_OVER_ID, STEP_OVER_LABEL, STOP_ID, STOP_LABEL } from 'vs/workbench/contrib/debug/browser/debugCommands';
import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons';
-import { localize } from 'vs/nls';
-import { Codicon } from 'vs/base/common/codicons';
-import { Icon } from 'vs/platform/action/common/action';
+import { createDisconnectMenuItemAction } from 'vs/workbench/contrib/debug/browser/debugToolBar';
+import { CALLSTACK_VIEW_ID, CONTEXT_CALLSTACK_ITEM_STOPPED, CONTEXT_CALLSTACK_ITEM_TYPE, CONTEXT_CALLSTACK_SESSION_HAS_ONE_THREAD, CONTEXT_CALLSTACK_SESSION_IS_ATTACH, CONTEXT_DEBUG_STATE, CONTEXT_STACK_FRAME_SUPPORTS_RESTART, getStateLabel, IDebugModel, IDebugService, IDebugSession, IRawStoppedDetails, IStackFrame, IThread, State } from 'vs/workbench/contrib/debug/common/debug';
+import { StackFrame, Thread, ThreadAndSessionIds } from 'vs/workbench/contrib/debug/common/debugModel';
+import { isSessionAttach } from 'vs/workbench/contrib/debug/common/debugUtils';
const $ = dom.$;
@@ -136,14 +136,9 @@ export class CallStackView extends ViewPane {
private needsRefresh = false;
private ignoreSelectionChangedEvent = false;
private ignoreFocusStackFrameEvent = false;
- private callStackItemType: IContextKey<string>;
- private callStackSessionIsAttach: IContextKey<boolean>;
- private callStackItemStopped: IContextKey<boolean>;
- private stackFrameSupportsRestart: IContextKey<boolean>;
- private sessionHasOneThread: IContextKey<boolean>;
+
private dataSource!: CallStackDataSource;
private tree!: WorkbenchCompressibleAsyncDataTree<IDebugModel, CallStackItem, FuzzyScore>;
- private menu: IMenu;
private autoExpandedSessions = new Set<IDebugSession>();
private selectionNeedsUpdate = false;
@@ -154,23 +149,14 @@ export class CallStackView extends ViewPane {
@IKeybindingService keybindingService: IKeybindingService,
@IInstantiationService instantiationService: IInstantiationService,
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
- @IEditorService private readonly editorService: IEditorService,
@IConfigurationService configurationService: IConfigurationService,
- @IMenuService menuService: IMenuService,
@IContextKeyService contextKeyService: IContextKeyService,
@IOpenerService openerService: IOpenerService,
@IThemeService themeService: IThemeService,
- @ITelemetryService telemetryService: ITelemetryService
+ @ITelemetryService telemetryService: ITelemetryService,
+ @IMenuService private readonly menuService: IMenuService,
) {
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService);
- this.callStackItemType = CONTEXT_CALLSTACK_ITEM_TYPE.bindTo(contextKeyService);
- this.callStackSessionIsAttach = CONTEXT_CALLSTACK_SESSION_IS_ATTACH.bindTo(contextKeyService);
- this.stackFrameSupportsRestart = CONTEXT_STACK_FRAME_SUPPORTS_RESTART.bindTo(contextKeyService);
- this.callStackItemStopped = CONTEXT_CALLSTACK_ITEM_STOPPED.bindTo(contextKeyService);
- this.sessionHasOneThread = CONTEXT_CALLSTACK_SESSION_HAS_ONE_THREAD.bindTo(contextKeyService);
-
- this.menu = menuService.createMenu(MenuId.DebugCallStackContext, contextKeyService);
- this._register(this.menu);
// Create scheduler to prevent unnecessary flashing of tree when reacting to changes
this.onCallStackChangeScheduler = this._register(new RunOnceScheduler(async () => {
@@ -239,9 +225,9 @@ export class CallStackView extends ViewPane {
this.dataSource = new CallStackDataSource(this.debugService);
this.tree = <WorkbenchCompressibleAsyncDataTree<IDebugModel, CallStackItem, FuzzyScore>>this.instantiationService.createInstance(WorkbenchCompressibleAsyncDataTree, 'CallStackView', treeContainer, new CallStackDelegate(), new CallStackCompressionDelegate(this.debugService), [
- new SessionsRenderer(this.menu, this.callStackItemType, this.callStackSessionIsAttach, this.callStackItemStopped, this.sessionHasOneThread, this.instantiationService),
- new ThreadsRenderer(this.menu, this.callStackItemType, this.callStackItemStopped),
- this.instantiationService.createInstance(StackFramesRenderer, this.callStackItemType),
+ this.instantiationService.createInstance(SessionsRenderer),
+ this.instantiationService.createInstance(ThreadsRenderer),
+ this.instantiationService.createInstance(StackFramesRenderer),
new ErrorsRenderer(),
new LoadAllRenderer(this.themeService),
new ShowMoreRenderer(this.themeService)
@@ -299,10 +285,10 @@ export class CallStackView extends ViewPane {
return;
}
- const focusStackFrame = (stackFrame: IStackFrame | undefined, thread: IThread | undefined, session: IDebugSession) => {
+ const focusStackFrame = (stackFrame: IStackFrame | undefined, thread: IThread | undefined, session: IDebugSession, options: { explicit?: boolean; preserveFocus?: boolean; sideBySide?: boolean; pinned?: boolean } = {}) => {
this.ignoreFocusStackFrameEvent = true;
try {
- this.debugService.focusStackFrame(stackFrame, thread, session, true);
+ this.debugService.focusStackFrame(stackFrame, thread, session, { ...options, ...{ explicit: true } });
} finally {
this.ignoreFocusStackFrameEvent = false;
}
@@ -310,8 +296,12 @@ export class CallStackView extends ViewPane {
const element = e.element;
if (element instanceof StackFrame) {
- focusStackFrame(element, element.thread, element.thread.session);
- element.openInEditor(this.editorService, e.editorOptions.preserveFocus, e.sideBySide, e.editorOptions.pinned);
+ const opts = {
+ preserveFocus: e.editorOptions.preserveFocus,
+ sideBySide: e.sideBySide,
+ pinned: e.editorOptions.pinned
+ };
+ focusStackFrame(element, element.thread, element.thread.session, opts);
}
if (element instanceof Thread) {
focusStackFrame(undefined, element, element.session);
@@ -455,22 +445,21 @@ export class CallStackView extends ViewPane {
private onContextMenu(e: ITreeContextMenuEvent<CallStackItem>): void {
const element = e.element;
- this.stackFrameSupportsRestart.reset();
+ let overlay: [string, any][] = [];
if (isDebugSession(element)) {
- this.callStackItemType.set('session');
+ overlay = getSessionContextOverlay(element);
} else if (element instanceof Thread) {
- this.callStackItemType.set('thread');
+ overlay = getThreadContextOverlay(element);
} else if (element instanceof StackFrame) {
- this.callStackItemType.set('stackFrame');
- this.stackFrameSupportsRestart.set(element.canRestart);
- } else {
- this.callStackItemType.reset();
+ overlay = getStackFrameContextOverlay(element);
}
const primary: IAction[] = [];
const secondary: IAction[] = [];
const result = { primary, secondary };
- const actionsDisposable = createAndFillInContextMenuActions(this.menu, { arg: getContextForContributedActions(element), shouldForwardArgs: true }, result, 'inline');
+ const contextKeyService = this.contextKeyService.createOverlay(overlay);
+ const menu = this.menuService.createMenu(MenuId.DebugCallStackContext, contextKeyService);
+ const actionsDisposable = createAndFillInContextMenuActions(menu, { arg: getContextForContributedActions(element), shouldForwardArgs: true }, result, 'inline');
this.contextMenuService.showContextMenu({
getAnchor: () => e.anchor,
@@ -487,7 +476,8 @@ interface IThreadTemplateData {
stateLabel: HTMLSpanElement;
label: HighlightedLabel;
actionBar: ActionBar;
- elementDisposable: IDisposable[];
+ elementDisposable: DisposableStore;
+ templateDisposable: IDisposable;
}
interface ISessionTemplateData {
@@ -496,7 +486,8 @@ interface ISessionTemplateData {
stateLabel: HTMLSpanElement;
label: HighlightedLabel;
actionBar: ActionBar;
- elementDisposable: IDisposable[];
+ elementDisposable: DisposableStore;
+ templateDisposable: IDisposable;
}
interface IErrorTemplateData {
@@ -515,18 +506,25 @@ interface IStackFrameTemplateData {
lineNumber: HTMLElement;
label: HighlightedLabel;
actionBar: ActionBar;
+ templateDisposable: IDisposable;
+}
+
+function getSessionContextOverlay(session: IDebugSession): [string, any][] {
+ return [
+ [CONTEXT_CALLSTACK_ITEM_TYPE.key, 'session'],
+ [CONTEXT_CALLSTACK_SESSION_IS_ATTACH.key, isSessionAttach(session)],
+ [CONTEXT_CALLSTACK_ITEM_STOPPED.key, session.state === State.Stopped],
+ [CONTEXT_CALLSTACK_SESSION_HAS_ONE_THREAD.key, session.getAllThreads().length === 1],
+ ];
}
class SessionsRenderer implements ICompressibleTreeRenderer<IDebugSession, FuzzyScore, ISessionTemplateData> {
static readonly ID = 'session';
constructor(
- private menu: IMenu,
- private callStackItemType: IContextKey<string>,
- private callStackSessionIsAttach: IContextKey<boolean>,
- private callStackItemStopped: IContextKey<boolean>,
- private sessionHasOneThread: IContextKey<boolean>,
- private readonly instantiationService: IInstantiationService
+ @IInstantiationService private readonly instantiationService: IInstantiationService,
+ @IContextKeyService private readonly contextKeyService: IContextKeyService,
+ @IMenuService private readonly menuService: IMenuService,
) { }
get templateId(): string {
@@ -539,8 +537,19 @@ class SessionsRenderer implements ICompressibleTreeRenderer<IDebugSession, Fuzzy
const name = dom.append(session, $('.name'));
const stateLabel = dom.append(session, $('span.state.label.monaco-count-badge.long'));
const label = new HighlightedLabel(name);
- const actionBar = new ActionBar(session, {
+ const templateDisposable = new DisposableStore();
+
+ const stopActionViewItemDisposables = templateDisposable.add(new DisposableStore());
+ const actionBar = templateDisposable.add(new ActionBar(session, {
actionViewItemProvider: action => {
+ if ((action.id === STOP_ID || action.id === DISCONNECT_ID) && action instanceof MenuItemAction) {
+ stopActionViewItemDisposables.clear();
+ const item = this.instantiationService.invokeFunction(accessor => createDisconnectMenuItemAction(action as MenuItemAction, stopActionViewItemDisposables, accessor));
+ if (item) {
+ return item;
+ }
+ }
+
if (action instanceof MenuItemAction) {
return this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined);
} else if (action instanceof SubmenuItemAction) {
@@ -549,9 +558,10 @@ class SessionsRenderer implements ICompressibleTreeRenderer<IDebugSession, Fuzzy
return undefined;
}
- });
+ }));
- return { session, name, stateLabel, label, actionBar, elementDisposable: [] };
+ const elementDisposable = templateDisposable.add(new DisposableStore());
+ return { session, name, stateLabel, label, actionBar, elementDisposable, templateDisposable };
}
renderElement(element: ITreeNode<IDebugSession, FuzzyScore>, _: number, data: ISessionTemplateData): void {
@@ -569,20 +579,28 @@ class SessionsRenderer implements ICompressibleTreeRenderer<IDebugSession, Fuzzy
data.label.set(session.getLabel(), matches);
const stoppedDetails = session.getStoppedDetails();
const thread = session.getAllThreads().find(t => t.stopped);
- const primary: IAction[] = [];
- const secondary: IAction[] = [];
- const result = { primary, secondary };
- this.callStackItemType.set('session');
- this.callStackItemStopped.set(session.state === State.Stopped);
- this.sessionHasOneThread.set(session.getAllThreads().length === 1);
- this.callStackSessionIsAttach.set(isSessionAttach(session));
- data.elementDisposable.push(createAndFillInActionBarActions(this.menu, { arg: getContextForContributedActions(session), shouldForwardArgs: true }, result, 'inline'));
- data.actionBar.clear();
- data.actionBar.push(primary, { icon: true, label: false });
- // We need to set our internal context on the action bar, since our commands depend on that one
- // While the external context our extensions rely on
- data.actionBar.context = getContext(session);
+ const contextKeyService = this.contextKeyService.createOverlay(getSessionContextOverlay(session));
+ const menu = data.elementDisposable.add(this.menuService.createMenu(MenuId.DebugCallStackContext, contextKeyService));
+
+ const menuDisposables = data.elementDisposable.add(new DisposableStore());
+ const setupActionBar = () => {
+ menuDisposables.clear();
+ data.actionBar.clear();
+
+ const primary: IAction[] = [];
+ const secondary: IAction[] = [];
+ const result = { primary, secondary };
+
+ menuDisposables.add(createAndFillInActionBarActions(menu, { arg: getContextForContributedActions(session), shouldForwardArgs: true }, result, 'inline'));
+ data.actionBar.push(primary, { icon: true, label: false });
+ // We need to set our internal context on the action bar, since our commands depend on that one
+ // While the external context our extensions rely on
+ data.actionBar.context = getContext(session);
+ };
+ data.elementDisposable.add(menu.onDidChange(() => setupActionBar()));
+ setupActionBar();
+
data.stateLabel.style.display = '';
if (stoppedDetails) {
@@ -600,21 +618,27 @@ class SessionsRenderer implements ICompressibleTreeRenderer<IDebugSession, Fuzzy
}
disposeTemplate(templateData: ISessionTemplateData): void {
- templateData.actionBar.dispose();
+ templateData.templateDisposable.dispose();
}
disposeElement(_element: ITreeNode<IDebugSession, FuzzyScore>, _: number, templateData: ISessionTemplateData): void {
- dispose(templateData.elementDisposable);
+ templateData.elementDisposable.clear();
}
}
+function getThreadContextOverlay(thread: IThread): [string, any][] {
+ return [
+ [CONTEXT_CALLSTACK_ITEM_TYPE.key, 'thread'],
+ [CONTEXT_CALLSTACK_ITEM_STOPPED.key, thread.stopped]
+ ];
+}
+
class ThreadsRenderer implements ICompressibleTreeRenderer<IThread, FuzzyScore, IThreadTemplateData> {
static readonly ID = 'thread';
constructor(
- private menu: IMenu,
- private callStackItemType: IContextKey<string>,
- private callStackItemStopped: IContextKey<boolean>
+ @IContextKeyService private readonly contextKeyService: IContextKeyService,
+ @IMenuService private readonly menuService: IMenuService,
) { }
get templateId(): string {
@@ -626,10 +650,13 @@ class ThreadsRenderer implements ICompressibleTreeRenderer<IThread, FuzzyScore,
const name = dom.append(thread, $('.name'));
const stateLabel = dom.append(thread, $('span.state.label.monaco-count-badge.long'));
const label = new HighlightedLabel(name);
- const actionBar = new ActionBar(thread);
- const elementDisposable: IDisposable[] = [];
- return { thread, name, stateLabel, label, actionBar, elementDisposable };
+ const templateDisposable = new DisposableStore();
+
+ const actionBar = templateDisposable.add(new ActionBar(thread));
+ const elementDisposable = templateDisposable.add(new DisposableStore());
+
+ return { thread, name, stateLabel, label, actionBar, elementDisposable, templateDisposable };
}
renderElement(element: ITreeNode<IThread, FuzzyScore>, _index: number, data: IThreadTemplateData): void {
@@ -639,13 +666,26 @@ class ThreadsRenderer implements ICompressibleTreeRenderer<IThread, FuzzyScore,
data.stateLabel.textContent = thread.stateLabel;
data.stateLabel.classList.toggle('exception', thread.stoppedDetails?.reason === 'exception');
- data.actionBar.clear();
- this.callStackItemType.set('thread');
- this.callStackItemStopped.set(thread.stopped);
- const primary: IAction[] = [];
- const result = { primary, secondary: [] };
- data.elementDisposable.push(createAndFillInActionBarActions(this.menu, { arg: getContextForContributedActions(thread), shouldForwardArgs: true }, result, 'inline'));
- data.actionBar.push(primary, { icon: true, label: false });
+ const contextKeyService = this.contextKeyService.createOverlay(getThreadContextOverlay(thread));
+ const menu = data.elementDisposable.add(this.menuService.createMenu(MenuId.DebugCallStackContext, contextKeyService));
+
+ const menuDisposables = data.elementDisposable.add(new DisposableStore());
+ const setupActionBar = () => {
+ menuDisposables.clear();
+ data.actionBar.clear();
+
+ const primary: IAction[] = [];
+ const secondary: IAction[] = [];
+ const result = { primary, secondary };
+
+ menuDisposables.add(createAndFillInActionBarActions(menu, { arg: getContextForContributedActions(thread), shouldForwardArgs: true }, result, 'inline'));
+ data.actionBar.push(primary, { icon: true, label: false });
+ // We need to set our internal context on the action bar, since our commands depend on that one
+ // While the external context our extensions rely on
+ data.actionBar.context = getContext(thread);
+ };
+ data.elementDisposable.add(menu.onDidChange(() => setupActionBar()));
+ setupActionBar();
}
renderCompressedElements(_node: ITreeNode<ICompressedTreeNode<IThread>, FuzzyScore>, _index: number, _templateData: IThreadTemplateData, _height: number | undefined): void {
@@ -653,19 +693,25 @@ class ThreadsRenderer implements ICompressibleTreeRenderer<IThread, FuzzyScore,
}
disposeElement(_element: any, _index: number, templateData: IThreadTemplateData): void {
- dispose(templateData.elementDisposable);
+ templateData.elementDisposable.clear();
}
disposeTemplate(templateData: IThreadTemplateData): void {
- templateData.actionBar.dispose();
+ templateData.templateDisposable.dispose();
}
}
+function getStackFrameContextOverlay(stackFrame: IStackFrame): [string, any][] {
+ return [
+ [CONTEXT_CALLSTACK_ITEM_TYPE.key, 'stackFrame'],
+ [CONTEXT_STACK_FRAME_SUPPORTS_RESTART.key, stackFrame.canRestart]
+ ];
+}
+
class StackFramesRenderer implements ICompressibleTreeRenderer<IStackFrame, FuzzyScore, IStackFrameTemplateData> {
static readonly ID = 'stackFrame';
constructor(
- private callStackItemType: IContextKey<string>,
@ILabelService private readonly labelService: ILabelService,
@INotificationService private readonly notificationService: INotificationService,
) { }
@@ -682,9 +728,11 @@ class StackFramesRenderer implements ICompressibleTreeRenderer<IStackFrame, Fuzz
const wrapper = dom.append(file, $('span.line-number-wrapper'));
const lineNumber = dom.append(wrapper, $('span.line-number.monaco-count-badge'));
const label = new HighlightedLabel(labelDiv);
- const actionBar = new ActionBar(stackFrame);
- return { file, fileName, label, lineNumber, stackFrame, actionBar };
+ const templateDisposable = new DisposableStore();
+ const actionBar = templateDisposable.add(new ActionBar(stackFrame));
+
+ return { file, fileName, label, lineNumber, stackFrame, actionBar, templateDisposable };
}
renderElement(element: ITreeNode<IStackFrame, FuzzyScore>, index: number, data: IStackFrameTemplateData): void {
@@ -712,7 +760,6 @@ class StackFramesRenderer implements ICompressibleTreeRenderer<IStackFrame, Fuzz
}
data.actionBar.clear();
- this.callStackItemType.set('stackFrame');
if (hasActions) {
const action = new Action('debug.callStack.restartFrame', localize('restartFrame', "Restart Frame"), ThemeIcon.asClassName(icons.debugRestartFrame), true, async () => {
try {
diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts
index 11f4ef0ce63..d595b7768d1 100644
--- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts
+++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts
@@ -16,11 +16,11 @@ import { CallStackView } from 'vs/workbench/contrib/debug/browser/callStackView'
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
import {
IDebugService, VIEWLET_ID, DEBUG_PANEL_ID, CONTEXT_IN_DEBUG_MODE, INTERNAL_CONSOLE_OPTIONS_SCHEMA,
- CONTEXT_DEBUG_STATE, VARIABLES_VIEW_ID, CALLSTACK_VIEW_ID, WATCH_VIEW_ID, BREAKPOINTS_VIEW_ID, LOADED_SCRIPTS_VIEW_ID, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_CALLSTACK_ITEM_TYPE, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_DEBUG_UX, BREAKPOINT_EDITOR_CONTRIBUTION_ID, REPL_VIEW_ID, CONTEXT_BREAKPOINTS_EXIST, EDITOR_CONTRIBUTION_ID, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_SET_VARIABLE_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT, getStateLabel, State, CONTEXT_WATCH_ITEM_TYPE, CONTEXT_STACK_FRAME_SUPPORTS_RESTART, CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED, DISASSEMBLY_VIEW_ID, CONTEXT_SET_EXPRESSION_SUPPORTED, CONTEXT_VARIABLE_IS_READONLY, CONTEXT_CAN_VIEW_MEMORY,
+ CONTEXT_DEBUG_STATE, VARIABLES_VIEW_ID, CALLSTACK_VIEW_ID, WATCH_VIEW_ID, BREAKPOINTS_VIEW_ID, LOADED_SCRIPTS_VIEW_ID, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_CALLSTACK_ITEM_TYPE, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_DEBUG_UX, BREAKPOINT_EDITOR_CONTRIBUTION_ID, REPL_VIEW_ID, CONTEXT_BREAKPOINTS_EXIST, EDITOR_CONTRIBUTION_ID, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_SET_VARIABLE_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED, CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT, getStateLabel, State, CONTEXT_WATCH_ITEM_TYPE, CONTEXT_STACK_FRAME_SUPPORTS_RESTART, CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED, CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED, DISASSEMBLY_VIEW_ID, CONTEXT_SET_EXPRESSION_SUPPORTED, CONTEXT_VARIABLE_IS_READONLY, CONTEXT_CAN_VIEW_MEMORY, CONTEXT_SUSPEND_DEBUGGEE_SUPPORTED,
} 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 } 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 } 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';
@@ -30,7 +30,7 @@ import { DebugStatusContribution } from 'vs/workbench/contrib/debug/browser/debu
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { launchSchemaId } from 'vs/workbench/services/configuration/common/configuration';
import { LoadedScriptsView } from 'vs/workbench/contrib/debug/browser/loadedScriptsView';
-import { RunToCursorAction } from 'vs/workbench/contrib/debug/browser/debugEditorActions';
+import { RunToCursorAction, SelectionToReplAction, SelectionToWatchExpressionsAction } from 'vs/workbench/contrib/debug/browser/debugEditorActions';
import { WatchExpressionsView, ADD_WATCH_LABEL, REMOVE_WATCH_EXPRESSIONS_COMMAND_ID, REMOVE_WATCH_EXPRESSIONS_LABEL, ADD_WATCH_ID } from 'vs/workbench/contrib/debug/browser/watchExpressionsView';
import { VariablesView, SET_VARIABLE_ID, COPY_VALUE_ID, BREAK_WHEN_VALUE_CHANGES_ID, COPY_EVALUATE_PATH_ID, ADD_TO_WATCH_ID, BREAK_WHEN_VALUE_IS_ACCESSED_ID, BREAK_WHEN_VALUE_IS_READ_ID, VIEW_MEMORY_ID } from 'vs/workbench/contrib/debug/browser/variablesView';
import { Repl } from 'vs/workbench/contrib/debug/browser/repl';
@@ -56,6 +56,7 @@ import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/
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 { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
const debugCategory = nls.localize('debugCategory', "Debug");
registerColors();
@@ -106,12 +107,15 @@ registerDebugCommandPaletteItem(STEP_INTO_ID, STEP_INTO_LABEL, CONTEXT_IN_DEBUG_
registerDebugCommandPaletteItem(STEP_OUT_ID, STEP_OUT_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped'));
registerDebugCommandPaletteItem(PAUSE_ID, PAUSE_LABEL, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('running'));
registerDebugCommandPaletteItem(DISCONNECT_ID, DISCONNECT_LABEL, CONTEXT_IN_DEBUG_MODE, ContextKeyExpr.or(CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED));
+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(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))));
@@ -135,6 +139,7 @@ const registerDebugViewMenuItem = (menuId: MenuId, id: string, title: string, or
};
registerDebugViewMenuItem(MenuId.DebugCallStackContext, RESTART_SESSION_ID, RESTART_LABEL, 10, CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('session'), undefined, '3_modification');
registerDebugViewMenuItem(MenuId.DebugCallStackContext, DISCONNECT_ID, DISCONNECT_LABEL, 20, CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('session'), undefined, '3_modification');
+registerDebugViewMenuItem(MenuId.DebugCallStackContext, DISCONNECT_AND_SUSPEND_ID, DISCONNECT_AND_SUSPEND_LABEL, 21, ContextKeyExpr.and(CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('session'), CONTEXT_SUSPEND_DEBUGGEE_SUPPORTED, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED), undefined, '3_modification');
registerDebugViewMenuItem(MenuId.DebugCallStackContext, STOP_ID, STOP_LABEL, 30, CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('session'), undefined, '3_modification');
registerDebugViewMenuItem(MenuId.DebugCallStackContext, PAUSE_ID, PAUSE_LABEL, 10, ContextKeyExpr.and(CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('thread'), CONTEXT_DEBUG_STATE.isEqualTo('running')));
registerDebugViewMenuItem(MenuId.DebugCallStackContext, CONTINUE_ID, CONTINUE_LABEL, 10, ContextKeyExpr.and(CONTEXT_CALLSTACK_ITEM_TYPE.isEqualTo('thread'), CONTEXT_DEBUG_STATE.isEqualTo('stopped')));
@@ -418,8 +423,8 @@ configurationRegistry.registerConfiguration({
default: false
},
'debug.inlineValues': {
- type: ['boolean', 'string'],
- 'enum': [true, false, 'auto'],
+ type: 'string',
+ 'enum': ['on', 'off', 'auto'],
description: nls.localize({ comment: ['This is the description for a setting'], key: 'inlineValues' }, "Show variable values inline in editor while debugging."),
'enumDescriptions': [
nls.localize('inlineValues.on', 'Always show variable values inline in editor while debugging.'),
@@ -506,6 +511,11 @@ configurationRegistry.registerConfiguration({
description: nls.localize('debug.focusWindowOnBreak', "Controls whether the workbench window should be focused when the debugger breaks."),
default: true
},
+ 'debug.focusEditorOnBreak': {
+ type: 'boolean',
+ description: nls.localize('debug.focusEditorOnBreak', "Controls whether the editor should be focused when the debugger breaks."),
+ default: true
+ },
'debug.onTaskErrors': {
enum: ['debugAnyway', 'showErrors', 'prompt', 'abort'],
enumDescriptions: [nls.localize('debugAnyway', "Ignore task errors and start debugging."), nls.localize('showErrors', "Show the Problems view and do not start debugging."), nls.localize('prompt', "Prompt user."), nls.localize('cancel', "Cancel debugging.")],
@@ -548,5 +558,10 @@ configurationRegistry.registerConfiguration({
default: true,
description: nls.localize('debug.disassemblyView.showSourceCode', "Show Source Code in Disassembly View.")
},
+ 'debug.autoExpandLazyVariables': {
+ type: 'boolean',
+ default: false,
+ description: nls.localize('debug.autoExpandLazyVariables', "Automatically show values for variables that are lazily resolved by the debugger, such as getters.")
+ }
}
});
diff --git a/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts b/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts
index 2a01435a6f5..24c64bc9f81 100644
--- a/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts
+++ b/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts
@@ -10,8 +10,8 @@ import Severity from 'vs/base/common/severity';
import * as strings from 'vs/base/common/strings';
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { IEditorModel } from 'vs/editor/common/editorCommon';
-import { ITextModel } from 'vs/editor/common/model';
import { ILanguageService } from 'vs/editor/common/languages/language';
+import { ITextModel } from 'vs/editor/common/model';
import * as nls from 'vs/nls';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@@ -22,6 +22,7 @@ import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/plat
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
+import { Breakpoints } from 'vs/workbench/contrib/debug/common/breakpoints';
import { CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_EXTENSION_AVAILABLE, IAdapterDescriptor, IAdapterManager, IConfig, IDebugAdapter, IDebugAdapterDescriptorFactory, IDebugAdapterFactory, IDebugConfiguration, IDebugSession, INTERNAL_CONSOLE_OPTIONS_SCHEMA } from 'vs/workbench/contrib/debug/common/debug';
import { Debugger } from 'vs/workbench/contrib/debug/common/debugger';
import { breakpointsExtPoint, debuggersExtPoint, launchSchema, presentationSchema } from 'vs/workbench/contrib/debug/common/debugSchemas';
@@ -42,7 +43,7 @@ export class AdapterManager extends Disposable implements IAdapterManager {
private debugExtensionsAvailable: IContextKey<boolean>;
private readonly _onDidRegisterDebugger = new Emitter<void>();
private readonly _onDidDebuggersExtPointRead = new Emitter<void>();
- private breakpointLanguageIdsSet = new Set<string>();
+ private breakpointContributions: Breakpoints[] = [];
private debuggerWhenKeys = new Set<string>();
constructor(
@@ -116,13 +117,8 @@ export class AdapterManager extends Disposable implements IAdapterManager {
this._onDidDebuggersExtPointRead.fire();
});
- breakpointsExtPoint.setHandler((extensions, delta) => {
- delta.removed.forEach(removed => {
- removed.value.forEach(breakpoints => this.breakpointLanguageIdsSet.delete(breakpoints.language));
- });
- delta.added.forEach(added => {
- added.value.forEach(breakpoints => this.breakpointLanguageIdsSet.add(breakpoints.language));
- });
+ breakpointsExtPoint.setHandler(extensions => {
+ this.breakpointContributions = extensions.flatMap(ext => ext.value.map(breakpoint => this.instantiationService.createInstance(Breakpoints, breakpoint)));
});
}
@@ -281,7 +277,7 @@ export class AdapterManager extends Disposable implements IAdapterManager {
return true;
}
- return this.breakpointLanguageIdsSet.has(languageId);
+ return this.breakpointContributions.some(breakpoints => breakpoints.language === languageId && breakpoints.enabled);
}
getDebugger(type: string): Debugger | undefined {
diff --git a/src/vs/workbench/contrib/debug/browser/debugColors.ts b/src/vs/workbench/contrib/debug/browser/debugColors.ts
index affb86d4e98..3ebe8126973 100644
--- a/src/vs/workbench/contrib/debug/browser/debugColors.ts
+++ b/src/vs/workbench/contrib/debug/browser/debugColors.ts
@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { registerColor, foreground, editorInfoForeground, editorWarningForeground, errorForeground, badgeBackground, badgeForeground, listDeemphasizedForeground, contrastBorder, inputBorder } from 'vs/platform/theme/common/colorRegistry';
+import { registerColor, foreground, editorInfoForeground, editorWarningForeground, errorForeground, badgeBackground, badgeForeground, listDeemphasizedForeground, contrastBorder, inputBorder, toolbarHoverBackground } from 'vs/platform/theme/common/colorRegistry';
import { registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { Color } from 'vs/base/common/color';
import { localize } from 'vs/nls';
@@ -125,6 +125,7 @@ export function registerColors() {
const debugViewStateLabelForegroundColor = theme.getColor(debugViewStateLabelForeground)!;
const debugViewStateLabelBackgroundColor = theme.getColor(debugViewStateLabelBackground)!;
const debugViewValueChangedHighlightColor = theme.getColor(debugViewValueChangedHighlight)!;
+ const toolbarHoverBackgroundColor = theme.getColor(toolbarHoverBackground);
collector.addRule(`
/* Text colour of the call stack row's filename */
@@ -182,6 +183,10 @@ export function registerColors() {
animation-duration: 1s;
animation-fill-mode: forwards;
}
+
+ .monaco-list-row .expression .lazy-button:hover {
+ background-color: ${toolbarHoverBackgroundColor}
+ }
`);
const contrastBorderColor = theme.getColor(contrastBorder);
diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts
index 4a022554117..3042792f8e8 100644
--- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts
+++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts
@@ -44,6 +44,7 @@ export const STEP_INTO_ID = 'workbench.action.debug.stepInto';
export const STEP_OUT_ID = 'workbench.action.debug.stepOut';
export const PAUSE_ID = 'workbench.action.debug.pause';
export const DISCONNECT_ID = 'workbench.action.debug.disconnect';
+export const DISCONNECT_AND_SUSPEND_ID = 'workbench.action.debug.disconnectAndSuspend';
export const STOP_ID = 'workbench.action.debug.stop';
export const RESTART_FRAME_ID = 'workbench.action.debug.restartFrame';
export const CONTINUE_ID = 'workbench.action.debug.continue';
@@ -64,6 +65,7 @@ export const STEP_INTO_LABEL = nls.localize('stepIntoDebug', "Step Into");
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");
@@ -313,7 +315,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
}
});
-async function stopHandler(accessor: ServicesAccessor, _: string, context: CallStackContext | unknown, disconnect: boolean): Promise<void> {
+async function stopHandler(accessor: ServicesAccessor, _: string, context: CallStackContext | unknown, disconnect: boolean, suspend?: boolean): Promise<void> {
const debugService = accessor.get(IDebugService);
let session: IDebugSession | undefined;
if (isSessionContext(context)) {
@@ -329,7 +331,7 @@ async function stopHandler(accessor: ServicesAccessor, _: string, context: CallS
session = session.parentSession;
}
- await debugService.stopSession(session, disconnect);
+ await debugService.stopSession(session, disconnect, suspend);
}
KeybindingsRegistry.registerCommandAndKeybindingRule({
@@ -340,6 +342,11 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
handler: (accessor, _, context) => stopHandler(accessor, _, context, true)
});
+CommandsRegistry.registerCommand({
+ id: DISCONNECT_AND_SUSPEND_ID,
+ handler: (accessor, _, context) => stopHandler(accessor, _, context, true, true)
+});
+
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: STOP_ID,
weight: KeybindingWeight.WorkbenchContrib,
@@ -399,7 +406,7 @@ CommandsRegistry.registerCommand({
if (stoppedChildSession && session.state !== State.Stopped) {
session = stoppedChildSession;
}
- await debugService.focusStackFrame(undefined, undefined, session, true);
+ await debugService.focusStackFrame(undefined, undefined, session, { explicit: true });
const stackFrame = debugService.getViewModel().focusedStackFrame;
if (stackFrame) {
await stackFrame.openInEditor(editorService, true);
diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts
index bcb3748c6a1..4264520e56a 100644
--- a/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts
+++ b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts
@@ -234,13 +234,16 @@ export class RunToCursorAction extends EditorAction {
}
}
-class SelectionToReplAction extends EditorAction {
+export class SelectionToReplAction extends EditorAction {
+
+ public static readonly ID = 'editor.debug.action.selectionToRepl';
+ public static readonly LABEL = nls.localize('evaluateInDebugConsole', "Evaluate in Debug Console");
constructor() {
super({
- id: 'editor.debug.action.selectionToRepl',
- label: nls.localize('evaluateInDebugConsole', "Evaluate in Debug Console"),
- alias: 'Evaluate',
+ id: SelectionToReplAction.ID,
+ label: SelectionToReplAction.LABEL,
+ alias: 'Debug: Evaluate in Console',
precondition: ContextKeyExpr.and(EditorContextKeys.hasNonEmptySelection, CONTEXT_IN_DEBUG_MODE, EditorContextKeys.editorTextFocus),
contextMenuOpts: {
group: 'debug',
@@ -264,13 +267,16 @@ class SelectionToReplAction extends EditorAction {
}
}
-class SelectionToWatchExpressionsAction extends EditorAction {
+export class SelectionToWatchExpressionsAction extends EditorAction {
+
+ public static readonly ID = 'editor.debug.action.selectionToWatch';
+ public static readonly LABEL = nls.localize('addToWatch', "Add to Watch");
constructor() {
super({
- id: 'editor.debug.action.selectionToWatch',
- label: nls.localize('addToWatch', "Add to Watch"),
- alias: 'Add to Watch',
+ id: SelectionToWatchExpressionsAction.ID,
+ label: SelectionToWatchExpressionsAction.LABEL,
+ alias: 'Debug: Add to Watch',
precondition: ContextKeyExpr.and(EditorContextKeys.hasNonEmptySelection, CONTEXT_IN_DEBUG_MODE, EditorContextKeys.editorTextFocus),
contextMenuOpts: {
group: 'debug',
diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts
index 81133f8fade..848242efad6 100644
--- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts
+++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts
@@ -179,8 +179,8 @@ function getWordToLineNumbersMap(model: ITextModel | null): Map<string, number[]
continue;
}
- model.forceTokenization(lineNumber);
- const lineTokens = model.getLineTokens(lineNumber);
+ model.tokenization.forceTokenization(lineNumber);
+ const lineTokens = model.tokenization.getLineTokens(lineNumber);
for (let tokenIndex = 0, tokenCount = lineTokens.getCount(); tokenIndex < tokenCount; tokenIndex++) {
const tokenType = lineTokens.getStandardTokenType(tokenIndex);
@@ -627,7 +627,7 @@ export class DebugEditorContribution implements IDebugEditorContribution {
const model = this.editor.getModel();
const inlineValuesSetting = this.configurationService.getValue<IDebugConfiguration>('debug').inlineValues;
- const inlineValuesTurnedOn = inlineValuesSetting === true || (inlineValuesSetting === 'auto' && model && this.languageFeaturesService.inlineValuesProvider.has(model));
+ const inlineValuesTurnedOn = inlineValuesSetting === true || inlineValuesSetting === 'on' || (inlineValuesSetting === 'auto' && model && this.languageFeaturesService.inlineValuesProvider.has(model));
if (!inlineValuesTurnedOn || !model || !stackFrame || model.uri.toString() !== stackFrame.source.uri.toString()) {
if (!this.removeInlineValuesScheduler.isScheduled()) {
this.removeInlineValuesScheduler.schedule();
diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts
index 39b73ddbc29..224b72fe4fa 100644
--- a/src/vs/workbench/contrib/debug/browser/debugService.ts
+++ b/src/vs/workbench/contrib/debug/browser/debugService.ts
@@ -801,9 +801,9 @@ export class DebugService implements IDebugService {
});
}
- async stopSession(session: IDebugSession | undefined, disconnect = false): Promise<any> {
+ async stopSession(session: IDebugSession | undefined, disconnect = false, suspend = false): Promise<any> {
if (session) {
- return disconnect ? session.disconnect() : session.terminate();
+ return disconnect ? session.disconnect(undefined, suspend) : session.terminate();
}
const sessions = this.model.getSessions();
@@ -815,7 +815,7 @@ export class DebugService implements IDebugService {
this.cancelTokens(undefined);
}
- return Promise.all(sessions.map(s => disconnect ? s.disconnect() : s.terminate()));
+ return Promise.all(sessions.map(s => disconnect ? s.disconnect(undefined, suspend) : s.terminate()));
}
private async substituteVariables(launch: ILaunch | undefined, config: IConfig): Promise<IConfig | undefined> {
@@ -852,11 +852,11 @@ export class DebugService implements IDebugService {
//---- focus management
- async focusStackFrame(_stackFrame: IStackFrame | undefined, _thread?: IThread, _session?: IDebugSession, explicit?: boolean): Promise<void> {
+ async focusStackFrame(_stackFrame: IStackFrame | undefined, _thread?: IThread, _session?: IDebugSession, options?: { explicit?: boolean; preserveFocus?: boolean; sideBySide?: boolean; pinned?: boolean }): Promise<void> {
const { stackFrame, thread, session } = getStackFrameThreadAndSessionToFocus(this.model, _stackFrame, _thread, _session);
if (stackFrame) {
- const editor = await stackFrame.openInEditor(this.editorService, true);
+ const editor = await stackFrame.openInEditor(this.editorService, options?.preserveFocus ?? true, options?.sideBySide, options?.pinned);
if (editor) {
if (editor.input === DisassemblyViewInput.instance) {
// Go to address is invoked via setFocus
@@ -880,7 +880,7 @@ export class DebugService implements IDebugService {
this.debugType.reset();
}
- this.viewModel.setFocus(stackFrame, thread, session, !!explicit);
+ this.viewModel.setFocus(stackFrame, thread, session, !!options?.explicit);
}
//---- watches
diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts
index 6d7ec022e42..3ff60d94d24 100644
--- a/src/vs/workbench/contrib/debug/browser/debugSession.ts
+++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts
@@ -172,6 +172,11 @@ export class DebugSession implements IDebugSession {
return this._options.debugUI?.simple ?? false;
}
+ get autoExpandLazyVariables(): boolean {
+ // This tiny helper avoids converting the entire debug model to use service injection
+ return this.configurationService.getValue<IDebugConfiguration>('debug').autoExpandLazyVariables;
+ }
+
setConfiguration(configuration: { resolved: IConfig; unresolved: IConfig | undefined }) {
this._configuration = configuration;
}
@@ -350,7 +355,7 @@ export class DebugSession implements IDebugSession {
/**
* end the current debug adapter session
*/
- async disconnect(restart = false): Promise<void> {
+ async disconnect(restart = false, suspend = false): Promise<void> {
if (!this.raw) {
// Adapter went down but it did not send a 'terminated' event, simulate like the event has been sent
this.onDidExitAdapter();
@@ -358,9 +363,10 @@ export class DebugSession implements IDebugSession {
this.cancelAllRequests();
if (this._options.lifecycleManagedByParent && this.parentSession) {
- await this.parentSession.disconnect(restart);
+ await this.parentSession.disconnect(restart, suspend);
} else if (this.raw) {
- await this.raw.disconnect({ restart, terminateDebuggee: false });
+ // TODO terminateDebuggee should be undefined by default?
+ await this.raw.disconnect({ restart, terminateDebuggee: false, suspendDebuggee: suspend });
}
if (!restart) {
@@ -957,7 +963,8 @@ export class DebugSession implements IDebugSession {
const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame;
if (!focusedStackFrame || focusedStackFrame.thread.session === this) {
// Only take focus if nothing is focused, or if the focus is already on the current session
- await this.debugService.focusStackFrame(undefined, thread);
+ const preserveFocus = !this.configurationService.getValue<IDebugConfiguration>('debug').focusEditorOnBreak;
+ await this.debugService.focusStackFrame(undefined, thread, undefined, { preserveFocus });
}
if (thread.stoppedDetails) {
@@ -1003,7 +1010,7 @@ export class DebugSession implements IDebugSession {
this.passFocusScheduler.cancel();
if (focusedThread && event.body.threadId === focusedThread.threadId) {
// De-focus the thread in case it was focused
- this.debugService.focusStackFrame(undefined, undefined, viewModel.focusedSession, false);
+ this.debugService.focusStackFrame(undefined, undefined, viewModel.focusedSession, { explicit: false });
}
}
}));
@@ -1070,7 +1077,7 @@ export class DebugSession implements IDebugSession {
// only log telemetry events from debug adapter if the debug extension provided the telemetry key
// and the user opted in telemetry
const telemetryEndpoint = this.raw.dbgr.getCustomTelemetryEndpoint();
- if (telemetryEndpoint && this.telemetryService.telemetryLevel !== TelemetryLevel.NONE) {
+ if (telemetryEndpoint && this.telemetryService.telemetryLevel.value !== TelemetryLevel.NONE) {
// __GDPR__TODO__ We're sending events in the name of the debug extension and we can not ensure that those are declared correctly.
let data = event.body.data;
if (!telemetryEndpoint.sendErrorTelemetry && event.body.data) {
diff --git a/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts b/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts
index 4c0b2af2ea0..4667fc38a3e 100644
--- a/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts
+++ b/src/vs/workbench/contrib/debug/browser/debugTaskRunner.ts
@@ -15,9 +15,12 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { withUndefinedAsNull } from 'vs/base/common/types';
import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers';
import { IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug';
-import { createErrorWithActions } from 'vs/base/common/errorMessage';
import { IViewsService } from 'vs/workbench/common/views';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
+import { createErrorWithActions } from 'vs/base/common/errorMessage';
+import { Action } from 'vs/base/common/actions';
+import { DEBUG_CONFIGURE_COMMAND_ID, DEBUG_CONFIGURE_LABEL } from 'vs/workbench/contrib/debug/browser/debugCommands';
+import { ICommandService } from 'vs/platform/commands/common/commands';
function once(match: (e: TaskEvent) => boolean, event: Event<TaskEvent>): Event<TaskEvent> {
return (listener, thisArgs = null, disposables?) => {
@@ -48,7 +51,8 @@ export class DebugTaskRunner {
@IConfigurationService private readonly configurationService: IConfigurationService,
@IViewsService private readonly viewsService: IViewsService,
@IDialogService private readonly dialogService: IDialogService,
- @IStorageService private readonly storageService: IStorageService
+ @IStorageService private readonly storageService: IStorageService,
+ @ICommandService private readonly commandService: ICommandService
) { }
cancel(): void {
@@ -158,7 +162,7 @@ export class DebugTaskRunner {
const errorMessage = typeof taskId === 'string'
? nls.localize('DebugTaskNotFoundWithTaskId', "Could not find the task '{0}'.", taskId)
: nls.localize('DebugTaskNotFound', "Could not find the specified task.");
- return Promise.reject(createErrorWithActions(errorMessage));
+ return Promise.reject(createErrorWithActions(errorMessage, [new Action(DEBUG_CONFIGURE_COMMAND_ID, DEBUG_CONFIGURE_LABEL, undefined, true, () => this.commandService.executeCommand(DEBUG_CONFIGURE_COMMAND_ID))]));
}
// If a task is missing the problem matcher the promise will never complete, so we need to have a workaround #35340
diff --git a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts
index 8bb67ae408f..13d089466e4 100644
--- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts
+++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts
@@ -3,36 +3,39 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import 'vs/css!./media/debugToolBar';
-import * as errors from 'vs/base/common/errors';
import * as browser from 'vs/base/browser/browser';
import * as dom from 'vs/base/browser/dom';
+import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
+import { ActionBar, ActionsOrientation, IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
+import { Action, IAction, IRunEvent, WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from 'vs/base/common/actions';
import * as arrays from 'vs/base/common/arrays';
+import { RunOnceScheduler } from 'vs/base/common/async';
+import * as errors from 'vs/base/common/errors';
+import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle';
+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 { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
-import { IAction, IRunEvent, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions';
-import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
-import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
-import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
-import { IDebugConfiguration, IDebugService, State, CONTEXT_DEBUG_STATE, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_MULTI_SESSION_DEBUG, VIEWLET_ID } from 'vs/workbench/contrib/debug/common/debug';
-import { FocusSessionActionViewItem } from 'vs/workbench/contrib/debug/browser/debugActionViewItems';
+import { ICommandAction } 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';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
+import { ContextKeyExpr, ContextKeyExpression, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
+import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
+import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
+import { INotificationService } from 'vs/platform/notification/common/notification';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
-import { IThemeService, Themable, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { contrastBorder, widgetShadow } from 'vs/platform/theme/common/colorRegistry';
-import { INotificationService } from 'vs/platform/notification/common/notification';
-import { RunOnceScheduler } from 'vs/base/common/async';
-import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
-import { createActionViewItem, createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
-import { IMenu, IMenuService, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
-import { IContextKeyService, ContextKeyExpression, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
-import { IDisposable, dispose } from 'vs/base/common/lifecycle';
-import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons';
+import { IThemeService, Themable, ThemeIcon } from 'vs/platform/theme/common/themeService';
+import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
+import { FocusSessionActionViewItem } from 'vs/workbench/contrib/debug/browser/debugActionViewItems';
import { debugToolBarBackground, debugToolBarBorder } from 'vs/workbench/contrib/debug/browser/debugColors';
-import { URI } from 'vs/base/common/uri';
-import { CONTINUE_LABEL, CONTINUE_ID, PAUSE_ID, STOP_ID, DISCONNECT_ID, STEP_OVER_ID, STEP_INTO_ID, RESTART_SESSION_ID, STEP_OUT_ID, STEP_BACK_ID, REVERSE_CONTINUE_ID, RESTART_LABEL, STEP_OUT_LABEL, STEP_INTO_LABEL, STEP_OVER_LABEL, DISCONNECT_LABEL, STOP_LABEL, PAUSE_LABEL, FOCUS_SESSION_ID, FOCUS_SESSION_LABEL } from 'vs/workbench/contrib/debug/browser/debugCommands';
-import { ICommandAction } from 'vs/platform/action/common/action';
+import { CONTINUE_ID, CONTINUE_LABEL, DISCONNECT_AND_SUSPEND_ID, DISCONNECT_AND_SUSPEND_LABEL, DISCONNECT_ID, DISCONNECT_LABEL, FOCUS_SESSION_ID, FOCUS_SESSION_LABEL, PAUSE_ID, PAUSE_LABEL, RESTART_LABEL, RESTART_SESSION_ID, REVERSE_CONTINUE_ID, STEP_BACK_ID, STEP_INTO_ID, STEP_INTO_LABEL, STEP_OUT_ID, STEP_OUT_LABEL, STEP_OVER_ID, STEP_OVER_LABEL, STOP_ID, STOP_LABEL } from 'vs/workbench/contrib/debug/browser/debugCommands';
+import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons';
+import { CONTEXT_DEBUG_STATE, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_MULTI_SESSION_DEBUG, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_SUSPEND_DEBUGGEE_SUPPORTED, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED, IDebugConfiguration, IDebugService, State, VIEWLET_ID } from 'vs/workbench/contrib/debug/common/debug';
+import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
const DEBUG_TOOLBAR_POSITION_KEY = 'debug.actionswidgetposition';
const DEBUG_TOOLBAR_Y_KEY = 'debug.actionswidgety';
@@ -51,6 +54,8 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution {
private isVisible = false;
private isBuilt = false;
+ private readonly stopActionViewItemDisposables = this._register(new DisposableStore());
+
constructor(
@INotificationService private readonly notificationService: INotificationService,
@ITelemetryService private readonly telemetryService: ITelemetryService,
@@ -61,7 +66,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution {
@IThemeService themeService: IThemeService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IMenuService menuService: IMenuService,
- @IContextKeyService contextKeyService: IContextKeyService
+ @IContextKeyService contextKeyService: IContextKeyService,
) {
super(themeService);
@@ -80,7 +85,14 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution {
actionViewItemProvider: (action: IAction) => {
if (action.id === FOCUS_SESSION_ID) {
return this.instantiationService.createInstance(FocusSessionActionViewItem, action, undefined);
+ } else if (action.id === STOP_ID || action.id === DISCONNECT_ID) {
+ this.stopActionViewItemDisposables.clear();
+ const item = this.instantiationService.invokeFunction(accessor => createDisconnectMenuItemAction(action as MenuItemAction, this.stopActionViewItemDisposables, accessor));
+ if (item) {
+ return item;
+ }
}
+
return createActionViewItem(this.instantiationService, action);
}
}));
@@ -256,6 +268,31 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution {
}
}
+export function createDisconnectMenuItemAction(action: MenuItemAction, disposables: DisposableStore, accessor: ServicesAccessor): IActionViewItem | undefined {
+ const menuService = accessor.get(IMenuService);
+ const contextKeyService = accessor.get(IContextKeyService);
+ const instantiationService = accessor.get(IInstantiationService);
+ const contextMenuService = accessor.get(IContextMenuService);
+
+ const menu = menuService.createMenu(MenuId.DebugToolBarStop, contextKeyService);
+ const secondary: IAction[] = [];
+ disposables.add(createAndFillInActionBarActions(menu, { shouldForwardArgs: true }, secondary));
+
+ if (!secondary.length) {
+ return undefined;
+ }
+
+ const dropdownAction = disposables.add(new Action('notebook.moreRunActions', localize('notebook.moreRunActionsLabel', "More..."), 'codicon-chevron-down', true));
+ const item = instantiationService.createInstance(DropdownWithPrimaryActionViewItem,
+ action as MenuItemAction,
+ dropdownAction,
+ secondary,
+ 'debug-stop-actions',
+ contextMenuService,
+ {});
+ return item;
+}
+
// Debug toolbar
const debugViewTitleItems: IDisposable[] = [];
@@ -303,8 +340,8 @@ MenuRegistry.onDidChangeMenu(e => {
registerDebugToolBarItem(CONTINUE_ID, CONTINUE_LABEL, 10, icons.debugContinue, CONTEXT_DEBUG_STATE.isEqualTo('stopped'));
registerDebugToolBarItem(PAUSE_ID, PAUSE_LABEL, 10, icons.debugPause, CONTEXT_DEBUG_STATE.notEqualsTo('stopped'), CONTEXT_DEBUG_STATE.isEqualTo('running'));
-registerDebugToolBarItem(STOP_ID, STOP_LABEL, 70, icons.debugStop, CONTEXT_FOCUSED_SESSION_IS_ATTACH.toNegated(), undefined, { id: DISCONNECT_ID, title: DISCONNECT_LABEL, icon: icons.debugDisconnect });
-registerDebugToolBarItem(DISCONNECT_ID, DISCONNECT_LABEL, 70, icons.debugDisconnect, CONTEXT_FOCUSED_SESSION_IS_ATTACH, undefined, { id: STOP_ID, title: STOP_LABEL, icon: icons.debugStop });
+registerDebugToolBarItem(STOP_ID, STOP_LABEL, 70, icons.debugStop, CONTEXT_FOCUSED_SESSION_IS_ATTACH.toNegated(), undefined, { id: DISCONNECT_ID, title: DISCONNECT_LABEL, icon: icons.debugDisconnect, precondition: ContextKeyExpr.and(CONTEXT_FOCUSED_SESSION_IS_ATTACH.toNegated(), CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED), });
+registerDebugToolBarItem(DISCONNECT_ID, DISCONNECT_LABEL, 70, icons.debugDisconnect, CONTEXT_FOCUSED_SESSION_IS_ATTACH, undefined, { id: STOP_ID, title: STOP_LABEL, icon: icons.debugStop, precondition: ContextKeyExpr.and(CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED), });
registerDebugToolBarItem(STEP_OVER_ID, STEP_OVER_LABEL, 20, icons.debugStepOver, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped'));
registerDebugToolBarItem(STEP_INTO_ID, STEP_INTO_LABEL, 30, icons.debugStepInto, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped'));
registerDebugToolBarItem(STEP_OUT_ID, STEP_OUT_LABEL, 40, icons.debugStepOut, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped'));
@@ -312,3 +349,39 @@ registerDebugToolBarItem(RESTART_SESSION_ID, RESTART_LABEL, 60, icons.debugResta
registerDebugToolBarItem(STEP_BACK_ID, localize('stepBackDebug', "Step Back"), 50, icons.debugStepBack, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_DEBUG_STATE.isEqualTo('stopped'));
registerDebugToolBarItem(REVERSE_CONTINUE_ID, localize('reverseContinue', "Reverse"), 55, icons.debugReverseContinue, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_DEBUG_STATE.isEqualTo('stopped'));
registerDebugToolBarItem(FOCUS_SESSION_ID, FOCUS_SESSION_LABEL, 100, undefined, CONTEXT_MULTI_SESSION_DEBUG);
+
+MenuRegistry.appendMenuItem(MenuId.DebugToolBarStop, {
+ group: 'navigation',
+ when: ContextKeyExpr.and(CONTEXT_FOCUSED_SESSION_IS_ATTACH.toNegated(), CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED),
+ order: 0,
+ command: {
+ id: DISCONNECT_ID,
+ title: DISCONNECT_LABEL,
+ icon: icons.debugDisconnect
+ }
+});
+
+MenuRegistry.appendMenuItem(MenuId.DebugToolBarStop, {
+ group: 'navigation',
+ when: ContextKeyExpr.and(CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED),
+ order: 0,
+ command: {
+ id: STOP_ID,
+ title: STOP_LABEL,
+ icon: icons.debugStop
+ }
+});
+
+MenuRegistry.appendMenuItem(MenuId.DebugToolBarStop, {
+ group: 'navigation',
+ when: ContextKeyExpr.or(
+ ContextKeyExpr.and(CONTEXT_FOCUSED_SESSION_IS_ATTACH.toNegated(), CONTEXT_SUSPEND_DEBUGGEE_SUPPORTED, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED),
+ ContextKeyExpr.and(CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_SUSPEND_DEBUGGEE_SUPPORTED),
+ ),
+ order: 0,
+ command: {
+ id: DISCONNECT_AND_SUSPEND_ID,
+ title: DISCONNECT_AND_SUSPEND_LABEL,
+ icon: icons.debugDisconnect
+ }
+});
diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts
index a90416994e1..ae38d059d88 100644
--- a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts
+++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts
@@ -3,34 +3,35 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
+import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
+import { IAction } from 'vs/base/common/actions';
+import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle';
import 'vs/css!./media/debugViewlet';
import * as nls from 'vs/nls';
-import { IAction } from 'vs/base/common/actions';
-import { IDebugService, VIEWLET_ID, State, BREAKPOINTS_VIEW_ID, CONTEXT_DEBUG_UX, CONTEXT_DEBUG_UX_KEY, REPL_VIEW_ID, CONTEXT_DEBUG_STATE, ILaunch, getStateLabel, CONTEXT_DEBUGGERS_AVAILABLE } from 'vs/workbench/contrib/debug/common/debug';
-import { StartDebugActionViewItem, FocusSessionActionViewItem } from 'vs/workbench/contrib/debug/browser/debugActionViewItems';
+import { createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
+import { Action2, MenuId, MenuItemAction, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions';
+import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
+import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
+import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
-import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IProgressService } from 'vs/platform/progress/common/progress';
-import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
-import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
+import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { IStorageService } from 'vs/platform/storage/common/storage';
+import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
-import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
-import { IDisposable, dispose } from 'vs/base/common/lifecycle';
-import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
-import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
-import { ViewPaneContainer, ViewsSubMenu } from 'vs/workbench/browser/parts/views/viewPaneContainer';
+import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { ViewPane } from 'vs/workbench/browser/parts/views/viewPane';
-import { MenuId, registerAction2, Action2, MenuRegistry } from 'vs/platform/actions/common/actions';
-import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
-import { createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
+import { ViewPaneContainer, ViewsSubMenu } from 'vs/workbench/browser/parts/views/viewPaneContainer';
+import { WorkbenchStateContext } from 'vs/workbench/common/contextkeys';
import { IViewDescriptorService, IViewsService } from 'vs/workbench/common/views';
-import { WelcomeView } from 'vs/workbench/contrib/debug/browser/welcomeView';
+import { FocusSessionActionViewItem, StartDebugActionViewItem } from 'vs/workbench/contrib/debug/browser/debugActionViewItems';
+import { DEBUG_CONFIGURE_COMMAND_ID, DEBUG_CONFIGURE_LABEL, DEBUG_START_COMMAND_ID, DEBUG_START_LABEL, DISCONNECT_ID, FOCUS_SESSION_ID, SELECT_AND_START_ID, STOP_ID } from 'vs/workbench/contrib/debug/browser/debugCommands';
import { debugConfigure } from 'vs/workbench/contrib/debug/browser/debugIcons';
-import { WorkbenchStateContext } from 'vs/workbench/common/contextkeys';
-import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
-import { FOCUS_SESSION_ID, SELECT_AND_START_ID, DEBUG_CONFIGURE_COMMAND_ID, DEBUG_CONFIGURE_LABEL, DEBUG_START_LABEL, DEBUG_START_COMMAND_ID } from 'vs/workbench/contrib/debug/browser/debugCommands';
-import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
+import { createDisconnectMenuItemAction } from 'vs/workbench/contrib/debug/browser/debugToolBar';
+import { WelcomeView } from 'vs/workbench/contrib/debug/browser/welcomeView';
+import { BREAKPOINTS_VIEW_ID, CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE, CONTEXT_DEBUG_UX, CONTEXT_DEBUG_UX_KEY, getStateLabel, IDebugService, ILaunch, REPL_VIEW_ID, State, VIEWLET_ID } from 'vs/workbench/contrib/debug/common/debug';
+import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
+import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
export class DebugViewPaneContainer extends ViewPaneContainer {
@@ -39,6 +40,8 @@ export class DebugViewPaneContainer extends ViewPaneContainer {
private breakpointView: ViewPane | undefined;
private paneListeners = new Map<string, IDisposable>();
+ private readonly stopActionViewItemDisposables = this._register(new DisposableStore());
+
constructor(
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
@ITelemetryService telemetryService: ITelemetryService,
@@ -53,7 +56,7 @@ export class DebugViewPaneContainer extends ViewPaneContainer {
@IConfigurationService configurationService: IConfigurationService,
@IContextViewService private readonly contextViewService: IContextViewService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
- @IViewDescriptorService viewDescriptorService: IViewDescriptorService
+ @IViewDescriptorService viewDescriptorService: IViewDescriptorService,
) {
super(VIEWLET_ID, { mergeViewWithContainerWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService, viewDescriptorService);
@@ -97,6 +100,15 @@ export class DebugViewPaneContainer extends ViewPaneContainer {
if (action.id === FOCUS_SESSION_ID) {
return new FocusSessionActionViewItem(action, undefined, this.debugService, this.themeService, this.contextViewService, this.configurationService);
}
+
+ if (action.id === STOP_ID || action.id === DISCONNECT_ID) {
+ this.stopActionViewItemDisposables.clear();
+ const item = this.instantiationService.invokeFunction(accessor => createDisconnectMenuItemAction(action as MenuItemAction, this.stopActionViewItemDisposables, accessor));
+ if (item) {
+ return item;
+ }
+ }
+
return createActionViewItem(this.instantiationService, action);
}
diff --git a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css
index 43865553172..577a80c532e 100644
--- a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css
+++ b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css
@@ -84,11 +84,14 @@
font-size: 11px;
}
-.monaco-workbench .monaco-list-row .expression .value,
-.monaco-workbench .monaco-list-row .expression .lazy-button {
+.monaco-workbench .monaco-list-row .expression .value {
margin-left: 6px;
}
+.monaco-workbench .monaco-list-row .expression .lazy-button {
+ margin-left: 3px;
+}
+
/* Links */
.monaco-workbench .monaco-list-row .expression .value a.link:hover {
@@ -119,20 +122,14 @@
.monaco-workbench .monaco-list-row .expression .lazy-button {
display: none;
-}
-
-.monaco-workbench .monaco-list-row .expression .lazy-button:hover {
- text-decoration: underline;
+ border-radius: 5px;
+ padding: 3px;
}
.monaco-workbench .monaco-list-row .expression.lazy .lazy-button {
display: inline;
}
-.monaco-workbench .monaco-list-row .expression.lazy .value {
- display: none;
-}
-
.monaco-workbench .debug-inline-value {
background-color: var(--vscode-editor-inlineValuesBackground);
color: var(--vscode-editor-inlineValuesForeground);
diff --git a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts
index d1b3b798d9f..848245e3978 100644
--- a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts
+++ b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts
@@ -6,7 +6,7 @@
import * as nls from 'vs/nls';
import { Event, Emitter } from 'vs/base/common/event';
import * as objects from 'vs/base/common/objects';
-import { Action } from 'vs/base/common/actions';
+import { toAction } from 'vs/base/common/actions';
import * as errors from 'vs/base/common/errors';
import { createErrorWithActions } from 'vs/base/common/errorMessage';
import { formatPII, isUri } from 'vs/workbench/contrib/debug/common/debugUtils';
@@ -284,7 +284,8 @@ export class RawDebugSession implements IDisposable {
*/
disconnect(args: DebugProtocol.DisconnectArguments): Promise<any> {
const terminateDebuggee = this.capabilities.supportTerminateDebuggee ? args.terminateDebuggee : undefined;
- return this.shutdown(undefined, args.restart, terminateDebuggee);
+ const suspendDebuggee = this.capabilities.supportTerminateDebuggee && this.capabilities.supportSuspendDebuggee ? args.suspendDebuggee : undefined;
+ return this.shutdown(undefined, args.restart, terminateDebuggee, suspendDebuggee);
}
//---- DAP requests
@@ -553,12 +554,20 @@ export class RawDebugSession implements IDisposable {
//---- private
- private async shutdown(error?: Error, restart = false, terminateDebuggee: boolean | undefined = undefined): Promise<any> {
+ private async shutdown(error?: Error, restart = false, terminateDebuggee: boolean | undefined = undefined, suspendDebuggee: boolean | undefined = undefined): Promise<any> {
if (!this.inShutdown) {
this.inShutdown = true;
if (this.debugAdapter) {
try {
- const args = typeof terminateDebuggee === 'boolean' ? { restart, terminateDebuggee } : { restart };
+ const args: DebugProtocol.DisconnectArguments = { restart };
+ if (typeof terminateDebuggee === 'boolean') {
+ args.terminateDebuggee = terminateDebuggee;
+ }
+
+ if (typeof suspendDebuggee === 'boolean') {
+ args.suspendDebuggee = suspendDebuggee;
+ }
+
this.send('disconnect', args, undefined, 2000);
} catch (e) {
// Catch the potential 'disconnect' error - no need to show it to the user since the adapter is shutting down
@@ -738,11 +747,7 @@ export class RawDebugSession implements IDisposable {
const uri = URI.parse(url);
// Use a suffixed id if uri invokes a command, so default 'Open launch.json' command is suppressed on dialog
const actionId = uri.scheme === Schemas.command ? 'debug.moreInfo.command' : 'debug.moreInfo';
- return createErrorWithActions(userMessage, {
- actions: [new Action(actionId, label, undefined, true, async () => {
- this.openerService.open(uri, { allowCommands: true });
- })]
- });
+ return createErrorWithActions(userMessage, [toAction({ id: actionId, label, run: () => this.openerService.open(uri, { allowCommands: true }) })]);
}
if (showErrors && error && error.format && error.showUser) {
this.notificationService.error(userMessage);
diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts
index cc2a06f698e..d171c967d4f 100644
--- a/src/vs/workbench/contrib/debug/browser/repl.ts
+++ b/src/vs/workbench/contrib/debug/browser/repl.ts
@@ -895,7 +895,7 @@ registerAction2(class extends ViewAction<Repl> {
session = stopppedChildSession;
}
}
- await debugService.focusStackFrame(undefined, undefined, session, true);
+ await debugService.focusStackFrame(undefined, undefined, session, { explicit: true });
}
// Need to select the session in the view since the focussed session might not have changed
await view.selectSession(session);
diff --git a/src/vs/workbench/contrib/debug/common/breakpoints.ts b/src/vs/workbench/contrib/debug/common/breakpoints.ts
new file mode 100644
index 00000000000..38bc0d5114d
--- /dev/null
+++ b/src/vs/workbench/contrib/debug/common/breakpoints.ts
@@ -0,0 +1,27 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { ContextKeyExpr, ContextKeyExpression, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
+import { IBreakpointContribution } from 'vs/workbench/contrib/debug/common/debug';
+
+export class Breakpoints {
+
+ private breakpointsWhen: ContextKeyExpression | undefined;
+
+ constructor(
+ private readonly breakpointContribution: IBreakpointContribution,
+ @IContextKeyService private readonly contextKeyService: IContextKeyService,
+ ) {
+ this.breakpointsWhen = typeof breakpointContribution.when === 'string' ? ContextKeyExpr.deserialize(breakpointContribution.when) : undefined;
+ }
+
+ get language(): string {
+ return this.breakpointContribution.language;
+ }
+
+ get enabled(): boolean {
+ return !this.breakpointsWhen || this.contextKeyService.contextMatchesRules(this.breakpointsWhen);
+ }
+}
diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts
index 7fd322d2d11..9457ae8c1b4 100644
--- a/src/vs/workbench/contrib/debug/common/debug.ts
+++ b/src/vs/workbench/contrib/debug/common/debug.ts
@@ -80,6 +80,7 @@ export const CONTEXT_BREAK_WHEN_VALUE_CHANGES_SUPPORTED = new RawContextKey<bool
export const CONTEXT_BREAK_WHEN_VALUE_IS_ACCESSED_SUPPORTED = new RawContextKey<boolean>('breakWhenValueIsAccessedSupported', false, { type: 'boolean', description: nls.localize('breakWhenValueIsAccessedSupported', "True when the focused breakpoint supports to break when value is accessed.") });
export const CONTEXT_BREAK_WHEN_VALUE_IS_READ_SUPPORTED = new RawContextKey<boolean>('breakWhenValueIsReadSupported', false, { type: 'boolean', description: nls.localize('breakWhenValueIsReadSupported', "True when the focused breakpoint supports to break when value is read.") });
export const CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED = new RawContextKey<boolean>('terminateDebuggeeSupported', false, { type: 'boolean', description: nls.localize('terminateDebuggeeSupported', "True when the focused session supports the terminate debuggee capability.") });
+export const CONTEXT_SUSPEND_DEBUGGEE_SUPPORTED = new RawContextKey<boolean>('suspendDebuggeeSupported', false, { type: 'boolean', description: nls.localize('suspendDebuggeeSupported', "True when the focused session supports the suspend debuggee capability.") });
export const CONTEXT_VARIABLE_EVALUATE_NAME_PRESENT = new RawContextKey<boolean>('variableEvaluateNamePresent', false, { type: 'boolean', description: nls.localize('variableEvaluateNamePresent', "True when the focused variable has an 'evalauteName' field set.") });
export const CONTEXT_VARIABLE_IS_READONLY = new RawContextKey<boolean>('variableIsReadonly', false, { type: 'boolean', description: nls.localize('variableIsReadonly', "True when the focused variable is readonly.") });
export const CONTEXT_EXCEPTION_WIDGET_VISIBLE = new RawContextKey<boolean>('exceptionWidgetVisible', false, { type: 'boolean', description: nls.localize('exceptionWidgetVisible', "True when the exception widget is visible.") });
@@ -290,6 +291,7 @@ export interface IDebugSession extends ITreeElement {
readonly compoundRoot: DebugCompoundRoot | undefined;
readonly name: string;
readonly isSimpleUI: boolean;
+ readonly autoExpandLazyVariables: boolean;
setSubId(subId: string | undefined): void;
@@ -339,7 +341,7 @@ export interface IDebugSession extends ITreeElement {
launchOrAttach(config: IConfig): Promise<void>;
restart(): Promise<void>;
terminate(restart?: boolean /* false */): Promise<void>;
- disconnect(restart?: boolean /* false */): Promise<void>;
+ disconnect(restart?: boolean /* false */, suspend?: boolean): Promise<void>;
sendBreakpoints(modelUri: uri, bpts: IBreakpoint[], sourceModified: boolean): Promise<void>;
sendFunctionBreakpoints(fbps: IFunctionBreakpoint[]): Promise<void>;
@@ -611,7 +613,7 @@ export interface IDebugConfiguration {
allowBreakpointsEverywhere: boolean;
openDebug: 'neverOpen' | 'openOnSessionStart' | 'openOnFirstSessionStart' | 'openOnDebugBreak';
openExplorerOnEnd: boolean;
- inlineValues: boolean | 'auto';
+ inlineValues: boolean | 'auto' | 'on' | 'off'; // boolean for back-compat
toolBarLocation: 'floating' | 'docked' | 'hidden';
showInStatusBar: 'never' | 'always' | 'onFirstSessionStart';
internalConsoleOptions: 'neverOpen' | 'openOnSessionStart' | 'openOnFirstSessionStart';
@@ -629,6 +631,7 @@ export interface IDebugConfiguration {
acceptSuggestionOnEnter: 'off' | 'on';
};
focusWindowOnBreak: boolean;
+ focusEditorOnBreak: boolean;
onTaskErrors: 'debugAnyway' | 'showErrors' | 'prompt' | 'abort';
showBreakpointsInOverviewRuler: boolean;
showInlineBreakpointCandidates: boolean;
@@ -636,6 +639,7 @@ export interface IDebugConfiguration {
disassemblyView: {
showSourceCode: boolean;
};
+ autoExpandLazyVariables: boolean;
}
export interface IGlobalConfig {
@@ -770,6 +774,11 @@ export interface IDebuggerContribution extends IPlatformSpecificAdapterContribut
when?: string;
}
+export interface IBreakpointContribution {
+ language: string;
+ when?: string;
+}
+
export enum DebugConfigurationProviderTriggerKind {
/**
* `DebugConfigurationProvider.provideDebugConfigurations` is called to provide the initial debug configurations for a newly created launch.json.
@@ -950,7 +959,7 @@ export interface IDebugService {
/**
* Sets the focused stack frame and evaluates all expressions against the newly focused stack frame,
*/
- focusStackFrame(focusedStackFrame: IStackFrame | undefined, thread?: IThread, session?: IDebugSession, explicit?: boolean): Promise<void>;
+ focusStackFrame(focusedStackFrame: IStackFrame | undefined, thread?: IThread, session?: IDebugSession, options?: { explicit?: boolean; preserveFocus?: boolean; sideBySide?: boolean; pinned?: boolean }): Promise<void>;
/**
* Returns true if breakpoints can be set for a given editor model. Depends on mode.
@@ -1073,7 +1082,7 @@ export interface IDebugService {
/**
* Stops the session. If no session is specified then all sessions are stopped.
*/
- stopSession(session: IDebugSession | undefined, disconnect?: boolean): Promise<any>;
+ stopSession(session: IDebugSession | undefined, disconnect?: boolean, suspend?: boolean): Promise<any>;
/**
* Makes unavailable all sources with the passed uri. Source will appear as grayed out in callstack view.
diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts
index 52994951b34..c05fc04c956 100644
--- a/src/vs/workbench/contrib/debug/common/debugModel.ts
+++ b/src/vs/workbench/contrib/debug/common/debugModel.ts
@@ -42,9 +42,9 @@ export class ExpressionContainer implements IExpressionContainer {
constructor(
protected session: IDebugSession | undefined,
- protected threadId: number | undefined,
+ protected readonly threadId: number | undefined,
private _reference: number | undefined,
- private id: string,
+ private readonly id: string,
public namedVariables: number | undefined = 0,
public indexedVariables: number | undefined = 0,
public memoryReference: string | undefined = undefined,
@@ -152,7 +152,7 @@ export class ExpressionContainer implements IExpressionContainer {
}
const nameCount = new Map<string, number>();
- return response.body.variables.filter(v => !!v).map((v: IDebugProtocolVariableWithContext) => {
+ const vars = response.body.variables.filter(v => !!v).map((v: IDebugProtocolVariableWithContext) => {
if (isString(v.value) && isString(v.name) && typeof v.variablesReference === 'number') {
const count = nameCount.get(v.name) || 0;
const idDuplicationIndex = count > 0 ? count.toString() : '';
@@ -161,6 +161,12 @@ export class ExpressionContainer implements IExpressionContainer {
}
return new Variable(this.session, this.threadId, this, 0, '', undefined, nls.localize('invalidVariableAttributes', "Invalid variable attributes"), 0, 0, undefined, { kind: 'virtual' }, undefined, undefined, false);
});
+
+ if (this.session!.autoExpandLazyVariables) {
+ await Promise.all(vars.map(v => v.presentationHint?.lazy && v.evaluateLazy()));
+ }
+
+ return vars;
} catch (e) {
return [new Variable(this.session, this.threadId, this, 0, '', undefined, e.message, 0, 0, undefined, { kind: 'virtual' }, undefined, undefined, false)];
}
@@ -275,9 +281,9 @@ export class Variable extends ExpressionContainer implements IExpression {
constructor(
session: IDebugSession | undefined,
threadId: number | undefined,
- public parent: IExpressionContainer,
+ public readonly parent: IExpressionContainer,
reference: number | undefined,
- public name: string,
+ public readonly name: string,
public evaluateName: string | undefined,
value: string | undefined,
namedVariables: number | undefined,
@@ -285,8 +291,8 @@ export class Variable extends ExpressionContainer implements IExpression {
memoryReference: string | undefined,
presentationHint: DebugProtocol.VariablePresentationHint | undefined,
type: string | undefined = undefined,
- public variableMenuContext: string | undefined = undefined,
- public available = true,
+ public readonly variableMenuContext: string | undefined = undefined,
+ public readonly available = true,
startOfVariables = 0,
idDuplicationIndex = '',
) {
@@ -346,12 +352,12 @@ export class Scope extends ExpressionContainer implements IScope {
constructor(
stackFrame: IStackFrame,
index: number,
- public name: string,
+ public readonly name: string,
reference: number,
public expensive: boolean,
namedVariables?: number,
indexedVariables?: number,
- public range?: IRange
+ public readonly range?: IRange
) {
super(stackFrame.thread.session, stackFrame.thread.threadId, reference, `scope:${name}:${index}`, namedVariables, indexedVariables);
}
@@ -389,15 +395,15 @@ export class StackFrame implements IStackFrame {
private scopes: Promise<Scope[]> | undefined;
constructor(
- public thread: Thread,
- public frameId: number,
- public source: Source,
- public name: string,
- public presentationHint: string | undefined,
- public range: IRange,
- private index: number,
- public canRestart: boolean,
- public instructionPointerReference?: string
+ public readonly thread: Thread,
+ public readonly frameId: number,
+ public readonly source: Source,
+ public readonly name: string,
+ public readonly presentationHint: string | undefined,
+ public readonly range: IRange,
+ private readonly index: number,
+ public readonly canRestart: boolean,
+ public readonly instructionPointerReference?: string
) { }
getId(): string {
@@ -482,7 +488,7 @@ export class Thread implements IThread {
public reachedEndOfCallStack = false;
public lastSteppingGranularity: DebugProtocol.SteppingGranularity | undefined;
- constructor(public session: IDebugSession, public name: string, public threadId: number) {
+ constructor(public readonly session: IDebugSession, public name: string, public readonly threadId: number) {
this.callStack = [];
this.staleCallStack = [];
this.stopped = false;
@@ -721,7 +727,7 @@ export class MemoryRegion extends Disposable implements IMemoryRegion {
export class Enablement implements IEnablement {
constructor(
public enabled: boolean,
- private id: string
+ private readonly id: string
) { }
getId(): string {
@@ -851,7 +857,7 @@ export abstract class BaseBreakpoint extends Enablement implements IBaseBreakpoi
export class Breakpoint extends BaseBreakpoint implements IBreakpoint {
constructor(
- private _uri: uri,
+ private readonly _uri: uri,
private _lineNumber: number,
private _column: number | undefined,
enabled: boolean,
@@ -1006,15 +1012,15 @@ export class FunctionBreakpoint extends BaseBreakpoint implements IFunctionBreak
export class DataBreakpoint extends BaseBreakpoint implements IDataBreakpoint {
constructor(
- public description: string,
- public dataId: string,
- public canPersist: boolean,
+ public readonly description: string,
+ public readonly dataId: string,
+ public readonly canPersist: boolean,
enabled: boolean,
hitCondition: string | undefined,
condition: string | undefined,
logMessage: string | undefined,
- public accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined,
- public accessType: DebugProtocol.DataBreakpointAccessType,
+ public readonly accessTypes: DebugProtocol.DataBreakpointAccessType[] | undefined,
+ public readonly accessType: DebugProtocol.DataBreakpointAccessType,
id = generateUuid()
) {
super(enabled, hitCondition, condition, logMessage, id);
@@ -1045,13 +1051,13 @@ export class DataBreakpoint extends BaseBreakpoint implements IDataBreakpoint {
export class ExceptionBreakpoint extends BaseBreakpoint implements IExceptionBreakpoint {
constructor(
- public filter: string,
- public label: string,
+ public readonly filter: string,
+ public readonly label: string,
enabled: boolean,
- public supportsCondition: boolean,
+ public readonly supportsCondition: boolean,
condition: string | undefined,
- public description: string | undefined,
- public conditionDescription: string | undefined
+ public readonly description: string | undefined,
+ public readonly conditionDescription: string | undefined
) {
super(enabled, undefined, condition, undefined, generateUuid());
}
@@ -1079,9 +1085,9 @@ export class ExceptionBreakpoint extends BaseBreakpoint implements IExceptionBre
export class InstructionBreakpoint extends BaseBreakpoint implements IInstructionBreakpoint {
constructor(
- public instructionReference: string,
- public offset: number,
- public canPersist: boolean,
+ public readonly instructionReference: string,
+ public readonly offset: number,
+ public readonly canPersist: boolean,
enabled: boolean,
hitCondition: string | undefined,
condition: string | undefined,
diff --git a/src/vs/workbench/contrib/debug/common/debugSchemas.ts b/src/vs/workbench/contrib/debug/common/debugSchemas.ts
index 3613a1b44a4..a2a9c7f5480 100644
--- a/src/vs/workbench/contrib/debug/common/debugSchemas.ts
+++ b/src/vs/workbench/contrib/debug/common/debugSchemas.ts
@@ -5,7 +5,7 @@
import * as extensionsRegistry from 'vs/workbench/services/extensions/common/extensionsRegistry';
import * as nls from 'vs/nls';
-import { IDebuggerContribution, ICompound } from 'vs/workbench/contrib/debug/common/debug';
+import { IDebuggerContribution, ICompound, IBreakpointContribution } from 'vs/workbench/contrib/debug/common/debug';
import { launchSchemaId } from 'vs/workbench/services/configuration/common/configuration';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { inputsSchema } from 'vs/workbench/services/configurationResolver/common/configurationResolverSchema';
@@ -68,7 +68,7 @@ export const debuggersExtPoint = extensionsRegistry.ExtensionsRegistry.registerE
type: 'object'
},
when: {
- description: nls.localize('vscode.extension.contributes.debuggers.when', "Condition which must be true to enable this type of debugger. Consider using 'shellExecutionSupported', 'virtualWorkspace', 'resourceScheme' or an extension defined context key as appropriate for this."),
+ description: nls.localize('vscode.extension.contributes.debuggers.when', "Condition which must be true to enable this type of debugger. Consider using 'shellExecutionSupported', 'virtualWorkspace', 'resourceScheme' or an extension-defined context key as appropriate for this."),
type: 'string',
default: ''
},
@@ -107,12 +107,8 @@ export const debuggersExtPoint = extensionsRegistry.ExtensionsRegistry.registerE
}
});
-export interface IRawBreakpointContribution {
- language: string;
-}
-
// breakpoints extension point #9037
-export const breakpointsExtPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint<IRawBreakpointContribution[]>({
+export const breakpointsExtPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint<IBreakpointContribution[]>({
extensionPoint: 'breakpoints',
jsonSchema: {
description: nls.localize('vscode.extension.contributes.breakpoints', 'Contributes breakpoints.'),
@@ -127,6 +123,11 @@ export const breakpointsExtPoint = extensionsRegistry.ExtensionsRegistry.registe
description: nls.localize('vscode.extension.contributes.breakpoints.language', "Allow breakpoints for this language."),
type: 'string'
},
+ when: {
+ description: nls.localize('vscode.extension.contributes.breakpoints.when', "Condition which must be true to enable breakpoints in this language. Consider matching this to the debugger when clause as appropriate."),
+ type: 'string',
+ default: ''
+ }
}
}
}
diff --git a/src/vs/workbench/contrib/debug/common/debugViewModel.ts b/src/vs/workbench/contrib/debug/common/debugViewModel.ts
index a6444a1f5fa..46468bb81d7 100644
--- a/src/vs/workbench/contrib/debug/common/debugViewModel.ts
+++ b/src/vs/workbench/contrib/debug/common/debugViewModel.ts
@@ -5,7 +5,7 @@
import { Emitter, Event } from 'vs/base/common/event';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
-import { CONTEXT_DISASSEMBLE_REQUEST_SUPPORTED, CONTEXT_EXPRESSION_SELECTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_FOCUSED_STACK_FRAME_HAS_INSTRUCTION_POINTER_REFERENCE, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_MULTI_SESSION_DEBUG, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_SET_EXPRESSION_SUPPORTED, CONTEXT_SET_VARIABLE_SUPPORTED, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_STEP_INTO_TARGETS_SUPPORTED, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED, IDebugSession, IExpression, IExpressionContainer, IStackFrame, IThread, IViewModel } from 'vs/workbench/contrib/debug/common/debug';
+import { CONTEXT_DISASSEMBLE_REQUEST_SUPPORTED, CONTEXT_EXPRESSION_SELECTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_FOCUSED_STACK_FRAME_HAS_INSTRUCTION_POINTER_REFERENCE, CONTEXT_JUMP_TO_CURSOR_SUPPORTED, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_MULTI_SESSION_DEBUG, CONTEXT_RESTART_FRAME_SUPPORTED, CONTEXT_SET_EXPRESSION_SUPPORTED, CONTEXT_SET_VARIABLE_SUPPORTED, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_STEP_INTO_TARGETS_SUPPORTED, CONTEXT_SUSPEND_DEBUGGEE_SUPPORTED, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED, IDebugSession, IExpression, IExpressionContainer, IStackFrame, IThread, IViewModel } from 'vs/workbench/contrib/debug/common/debug';
import { isSessionAttach } from 'vs/workbench/contrib/debug/common/debugUtils';
export class ViewModel implements IViewModel {
@@ -31,7 +31,8 @@ export class ViewModel implements IViewModel {
private setVariableSupported!: IContextKey<boolean>;
private setExpressionSupported!: IContextKey<boolean>;
private multiSessionDebug!: IContextKey<boolean>;
- private terminateDebuggeeSuported!: IContextKey<boolean>;
+ private terminateDebuggeeSupported!: IContextKey<boolean>;
+ private suspendDebuggeeSupported!: IContextKey<boolean>;
private disassembleRequestSupported!: IContextKey<boolean>;
private focusedStackFrameHasInstructionPointerReference!: IContextKey<Boolean>;
@@ -47,7 +48,8 @@ export class ViewModel implements IViewModel {
this.setVariableSupported = CONTEXT_SET_VARIABLE_SUPPORTED.bindTo(contextKeyService);
this.setExpressionSupported = CONTEXT_SET_EXPRESSION_SUPPORTED.bindTo(contextKeyService);
this.multiSessionDebug = CONTEXT_MULTI_SESSION_DEBUG.bindTo(contextKeyService);
- this.terminateDebuggeeSuported = CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED.bindTo(contextKeyService);
+ this.terminateDebuggeeSupported = CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED.bindTo(contextKeyService);
+ this.suspendDebuggeeSupported = CONTEXT_SUSPEND_DEBUGGEE_SUPPORTED.bindTo(contextKeyService);
this.disassembleRequestSupported = CONTEXT_DISASSEMBLE_REQUEST_SUPPORTED.bindTo(contextKeyService);
this.focusedStackFrameHasInstructionPointerReference = CONTEXT_FOCUSED_STACK_FRAME_HAS_INSTRUCTION_POINTER_REFERENCE.bindTo(contextKeyService);
});
@@ -85,7 +87,8 @@ export class ViewModel implements IViewModel {
this.jumpToCursorSupported.set(session ? !!session.capabilities.supportsGotoTargetsRequest : false);
this.setVariableSupported.set(session ? !!session.capabilities.supportsSetVariable : false);
this.setExpressionSupported.set(session ? !!session.capabilities.supportsSetExpression : false);
- this.terminateDebuggeeSuported.set(session ? !!session.capabilities.supportTerminateDebuggee : false);
+ this.terminateDebuggeeSupported.set(session ? !!session.capabilities.supportTerminateDebuggee : false);
+ this.suspendDebuggeeSupported.set(session ? !!session.capabilities.supportSuspendDebuggee : false);
this.disassembleRequestSupported.set(!!session?.capabilities.supportsDisassembleRequest);
this.focusedStackFrameHasInstructionPointerReference.set(!!stackFrame?.instructionPointerReference);
const attach = !!session && isSessionAttach(session);
diff --git a/src/vs/workbench/contrib/debug/common/debugger.ts b/src/vs/workbench/contrib/debug/common/debugger.ts
index c1c0166702d..8a41ba6c603 100644
--- a/src/vs/workbench/contrib/debug/common/debugger.ts
+++ b/src/vs/workbench/contrib/debug/common/debugger.ts
@@ -217,6 +217,7 @@ export class Debugger implements IDebugger {
const attributes: IJSONSchema = this.debuggerContribution.configurationAttributes[request];
const defaultRequired = ['name', 'type', 'request'];
attributes.required = attributes.required && attributes.required.length ? defaultRequired.concat(attributes.required) : defaultRequired;
+ attributes.additionalProperties = false;
attributes.type = 'object';
if (!attributes.properties) {
attributes.properties = {};
@@ -239,38 +240,37 @@ export class Debugger implements IDebugger {
$ref: `#/definitions/common/properties/${prop}`
};
}
- definitions[definitionId] = attributes;
-
Object.keys(properties).forEach(name => {
// Use schema allOf property to get independent error reporting #21113
ConfigurationResolverUtils.applyDeprecatedVariableMessage(properties[name]);
});
- const result = {
- allOf: [{
- $ref: `#/definitions/${definitionId}`
- }, {
- properties: {
- windows: {
- $ref: `#/definitions/${definitionId}`,
- description: nls.localize('debugWindowsConfiguration', "Windows specific launch configuration attributes."),
- required: [],
- },
- osx: {
- $ref: `#/definitions/${definitionId}`,
- description: nls.localize('debugOSXConfiguration', "OS X specific launch configuration attributes."),
- required: [],
- },
- linux: {
- $ref: `#/definitions/${definitionId}`,
- description: nls.localize('debugLinuxConfiguration', "Linux specific launch configuration attributes."),
- required: [],
- }
+ definitions[definitionId] = { ...attributes };
+
+ // Don't add the OS props to the real attributes object so they don't show up in 'definitions'
+ const attributesCopy = { ...attributes };
+ attributesCopy.properties = {
+ ...properties,
+ ...{
+ windows: {
+ $ref: `#/definitions/${definitionId}`,
+ description: nls.localize('debugWindowsConfiguration', "Windows specific launch configuration attributes."),
+ required: [],
+ },
+ osx: {
+ $ref: `#/definitions/${definitionId}`,
+ description: nls.localize('debugOSXConfiguration', "OS X specific launch configuration attributes."),
+ required: [],
+ },
+ linux: {
+ $ref: `#/definitions/${definitionId}`,
+ description: nls.localize('debugLinuxConfiguration', "Linux specific launch configuration attributes."),
+ required: [],
}
- }]
+ }
};
- return result;
+ return attributesCopy;
});
}
}
diff --git a/src/vs/workbench/contrib/debug/node/debugAdapter.ts b/src/vs/workbench/contrib/debug/node/debugAdapter.ts
index 6347d7ba2a5..4f0bf897787 100644
--- a/src/vs/workbench/contrib/debug/node/debugAdapter.ts
+++ b/src/vs/workbench/contrib/debug/node/debugAdapter.ts
@@ -13,7 +13,7 @@ import * as strings from 'vs/base/common/strings';
import * as objects from 'vs/base/common/objects';
import * as platform from 'vs/base/common/platform';
import { ExtensionsChannelId } from 'vs/platform/extensionManagement/common/extensionManagement';
-import { IOutputService } from 'vs/workbench/contrib/output/common/output';
+import { IOutputService } from 'vs/workbench/services/output/common/output';
import { IDebugAdapterExecutable, IDebuggerContribution, IPlatformSpecificAdapterContribution, IDebugAdapterServer, IDebugAdapterNamedPipeServer } from 'vs/workbench/contrib/debug/common/debug';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { AbstractDebugAdapter } from '../common/abstractDebugAdapter';
diff --git a/src/vs/workbench/contrib/debug/node/terminals.ts b/src/vs/workbench/contrib/debug/node/terminals.ts
index c27c88f27a6..5418d9e8ffc 100644
--- a/src/vs/workbench/contrib/debug/node/terminals.ts
+++ b/src/vs/workbench/contrib/debug/node/terminals.ts
@@ -121,7 +121,8 @@ export function prepareCommand(shell: string, args: string[], cwd?: string, env?
quote = (s: string) => {
s = s.replace(/\"/g, '""');
- return (s.indexOf(' ') >= 0 || s.indexOf('"') >= 0 || s.length === 0) ? `"${s}"` : s;
+ s = s.replace(/([><!^&|])/g, '^$1');
+ return (' "'.split('').some(char => s.includes(char)) || s.length === 0) ? `"${s}"` : s;
};
if (cwd) {
@@ -138,7 +139,7 @@ export function prepareCommand(shell: string, args: string[], cwd?: string, env?
if (value === null) {
command += `set "${key}=" && `;
} else {
- value = value.replace(/[\^\&\|\<\>]/g, s => `^${s}`);
+ value = value.replace(/[&^|<>]/g, s => `^${s}`);
command += `set "${key}=${value}" && `;
}
}
@@ -154,8 +155,8 @@ export function prepareCommand(shell: string, args: string[], cwd?: string, env?
case ShellType.bash: {
quote = (s: string) => {
- s = s.replace(/(["'\\\$])/g, '\\$1');
- return (s.indexOf(' ') >= 0 || s.indexOf(';') >= 0 || s.length === 0) ? `"${s}"` : s;
+ s = s.replace(/(["'\\\$!><#()\[\]*&^|])/g, '\\$1');
+ return (' ;'.split('').some(char => s.includes(char)) || s.length === 0) ? `"${s}"` : s;
};
const hardQuote = (s: string) => {
diff --git a/src/vs/workbench/contrib/debug/test/browser/mockDebug.ts b/src/vs/workbench/contrib/debug/test/browser/mockDebug.ts
index 81c394466ec..1bebaa31adb 100644
--- a/src/vs/workbench/contrib/debug/test/browser/mockDebug.ts
+++ b/src/vs/workbench/contrib/debug/test/browser/mockDebug.ts
@@ -168,6 +168,8 @@ export class MockDebugService implements IDebugService {
}
export class MockSession implements IDebugSession {
+ readonly autoExpandLazyVariables = false;
+
getMemory(memoryReference: string): IMemoryRegion {
throw new Error('Method not implemented.');
}
diff --git a/src/vs/workbench/contrib/dropIntoEditor/browser/dropIntoEditor.contibution.ts b/src/vs/workbench/contrib/dropIntoEditor/browser/dropIntoEditor.contibution.ts
new file mode 100644
index 00000000000..7e1380f1d1f
--- /dev/null
+++ b/src/vs/workbench/contrib/dropIntoEditor/browser/dropIntoEditor.contibution.ts
@@ -0,0 +1,151 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { distinct } from 'vs/base/common/arrays';
+import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
+import { Disposable } from 'vs/base/common/lifecycle';
+import { Mimes } from 'vs/base/common/mime';
+import { relativePath } from 'vs/base/common/resources';
+import { URI } from 'vs/base/common/uri';
+import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
+import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
+import { IPosition } from 'vs/editor/common/core/position';
+import { Range } from 'vs/editor/common/core/range';
+import { IEditorContribution } from 'vs/editor/common/editorCommon';
+import { IDataTransferItem } from 'vs/editor/common/languages';
+import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
+import { performSnippetEdit } from 'vs/editor/contrib/snippet/browser/snippetController2';
+import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
+import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
+import { extractEditorsDropData } from 'vs/workbench/browser/dnd';
+import { IDataTransfer } from 'vs/workbench/common/dnd';
+
+
+export class DropIntoEditorController extends Disposable implements IEditorContribution {
+
+ public static readonly ID = 'editor.contrib.dropIntoEditorController';
+
+ constructor(
+ editor: ICodeEditor,
+ @IInstantiationService private readonly _instantiationService: IInstantiationService,
+ @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,
+ @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService,
+ ) {
+ super();
+
+ editor.onDropIntoEditor(e => this.onDropIntoEditor(editor, e.position, e.event));
+ }
+
+ private async onDropIntoEditor(editor: ICodeEditor, position: IPosition, dragEvent: DragEvent) {
+ if (!dragEvent.dataTransfer || !editor.hasModel()) {
+ return;
+ }
+
+ const model = editor.getModel();
+ const modelVersionNow = model.getVersionId();
+
+ const textEditorDataTransfer: IDataTransfer = new Map<string, IDataTransferItem>();
+ for (const item of dragEvent.dataTransfer.items) {
+ if (item.kind === 'string') {
+ const type = item.type;
+ const asStringValue = new Promise<string>(resolve => item.getAsString(resolve));
+ textEditorDataTransfer.set(type, {
+ asString: () => asStringValue,
+ value: undefined
+ });
+ }
+ }
+
+ if (!textEditorDataTransfer.has(Mimes.uriList.toLowerCase())) {
+ const editorData = (await this._instantiationService.invokeFunction(extractEditorsDropData, dragEvent))
+ .filter(input => input.resource)
+ .map(input => input.resource!.toString());
+
+ if (editorData.length) {
+ const str = distinct(editorData).join('\n');
+ textEditorDataTransfer.set(Mimes.uriList.toLowerCase(), {
+ asString: () => Promise.resolve(str),
+ value: undefined
+ });
+ }
+ }
+
+ if (textEditorDataTransfer.size === 0) {
+ return;
+ }
+
+ if (editor.getModel().getVersionId() !== modelVersionNow) {
+ return;
+ }
+
+ const cts = new CancellationTokenSource();
+ editor.onDidDispose(() => cts.cancel());
+ model.onDidChangeContent(() => cts.cancel());
+
+ const ordered = this._languageFeaturesService.documentOnDropEditProvider.ordered(model);
+ for (const provider of ordered) {
+ const edit = await provider.provideDocumentOnDropEdits(model, position, textEditorDataTransfer, cts.token);
+ if (cts.token.isCancellationRequested || editor.getModel().getVersionId() !== modelVersionNow) {
+ return;
+ }
+
+ if (edit) {
+ performSnippetEdit(editor, edit);
+ return;
+ }
+ }
+
+ return this.doDefaultDrop(editor, position, textEditorDataTransfer, cts.token);
+ }
+
+ private async doDefaultDrop(editor: ICodeEditor, position: IPosition, textEditorDataTransfer: IDataTransfer, token: CancellationToken): Promise<void> {
+ const range = new Range(position.lineNumber, position.column, position.lineNumber, position.column);
+
+ const urlListEntry = textEditorDataTransfer.get('text/uri-list');
+ if (urlListEntry) {
+ const urlList = await urlListEntry.asString();
+ return this.doUriListDrop(editor, range, urlList, token);
+ }
+
+ const textEntry = textEditorDataTransfer.get('text') ?? textEditorDataTransfer.get(Mimes.text);
+ if (textEntry) {
+ const text = await textEntry.asString();
+ performSnippetEdit(editor, { range, snippet: text });
+ }
+ }
+
+ private async doUriListDrop(editor: ICodeEditor, range: Range, urlList: string, token: CancellationToken): Promise<void> {
+ const uris: URI[] = [];
+ for (const resource of urlList.split('\n')) {
+ try {
+ uris.push(URI.parse(resource));
+ } catch {
+ // noop
+ }
+ }
+
+ if (!uris.length) {
+ return;
+ }
+
+ const snippet = uris
+ .map(uri => {
+ const root = this._workspaceContextService.getWorkspaceFolder(uri);
+ if (root) {
+ const rel = relativePath(root.uri, uri);
+ if (rel) {
+ return rel;
+ }
+ }
+ return uri.fsPath;
+ })
+ .join(' ');
+
+ performSnippetEdit(editor, { range, snippet });
+ }
+}
+
+
+registerEditorContribution(DropIntoEditorController.ID, DropIntoEditorController);
diff --git a/src/vs/workbench/contrib/emmet/browser/emmetActions.ts b/src/vs/workbench/contrib/emmet/browser/emmetActions.ts
index 21292c80a47..08b8e4d9c32 100644
--- a/src/vs/workbench/contrib/emmet/browser/emmetActions.ts
+++ b/src/vs/workbench/contrib/emmet/browser/emmetActions.ts
@@ -93,7 +93,7 @@ export abstract class EmmetEditorAction extends EditorAction {
}
const position = selection.getStartPosition();
- model.tokenizeIfCheap(position.lineNumber);
+ model.tokenization.tokenizeIfCheap(position.lineNumber);
const languageId = model.getLanguageIdAtPosition(position.lineNumber, position.column);
const syntax = languageId.split('.').pop();
diff --git a/src/vs/workbench/contrib/experiments/common/experimentService.ts b/src/vs/workbench/contrib/experiments/common/experimentService.ts
index a0d52ac4b52..0770dbb143e 100644
--- a/src/vs/workbench/contrib/experiments/common/experimentService.ts
+++ b/src/vs/workbench/contrib/experiments/common/experimentService.ts
@@ -21,6 +21,7 @@ import { asJson, IRequestService } from 'vs/platform/request/common/request';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { ITelemetryService, lastSessionDateStorageKey } from 'vs/platform/telemetry/common/telemetry';
import { IWorkspaceTagsService } from 'vs/workbench/contrib/tags/common/workspaceTags';
+import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { ITextFileEditorModel, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
@@ -182,7 +183,8 @@ export class ExperimentService extends Disposable implements IExperimentService
@IConfigurationService private readonly configurationService: IConfigurationService,
@IProductService private readonly productService: IProductService,
@IWorkspaceTagsService private readonly workspaceTagsService: IWorkspaceTagsService,
- @IExtensionService private readonly extensionService: IExtensionService
+ @IExtensionService private readonly extensionService: IExtensionService,
+ @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService
) {
super();
@@ -227,6 +229,10 @@ export class ExperimentService extends Disposable implements IExperimentService
}
protected async getExperiments(): Promise<IRawExperiment[] | null> {
+ if (this.environmentService.enableSmokeTestDriver || this.environmentService.extensionTestsLocationURI) {
+ return []; // TODO@sbatten add CLI argument (https://github.com/microsoft/vscode-internalbacklog/issues/2855)
+ }
+
const experimentsUrl = this.configurationService.getValue<string>('_workbench.experimentsUrl') || this.productService.experimentsUrl;
if (!experimentsUrl || this.configurationService.getValue('workbench.enableExperiments') === false) {
return [];
diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts
index cb560efaaa3..8e2d1a5badd 100644
--- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts
+++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts
@@ -71,6 +71,7 @@ import { MarkdownString } from 'vs/base/common/htmlContent';
import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite';
import { ViewContainerLocation } from 'vs/workbench/common/views';
import { IExtensionGalleryService, IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
+import semver = require('vs/base/common/semver/semver');
class NavBar extends Disposable {
@@ -183,10 +184,10 @@ class VersionWidget extends ExtensionWithDifferentGalleryVersionWidget {
this.render();
}
render(): void {
- if (!this.extension) {
+ if (!this.extension || !semver.valid(this.extension.version)) {
return;
}
- this.element.textContent = `v${this.gallery ? this.gallery.version : this.extension.version}`;
+ this.element.textContent = `v${this.gallery?.version ?? this.extension.version}`;
}
}
diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts
index 41515d1c2e2..ebbd930216f 100644
--- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts
+++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts
@@ -200,9 +200,12 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
}
},
additionalProperties: false,
- default: {
- 'pub.name': false
- }
+ default: {},
+ defaultSnippets: [{
+ 'body': {
+ 'pub.name': false
+ }
+ }]
},
'extensions.experimental.affinity': {
type: 'object',
@@ -214,9 +217,12 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
}
},
additionalProperties: false,
- default: {
- 'pub.name': 1
- }
+ default: {},
+ defaultSnippets: [{
+ 'body': {
+ 'pub.name': 1
+ }
+ }]
},
[WORKSPACE_TRUST_EXTENSION_SUPPORT]: {
type: 'object',
diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts
index 2de5c8e1411..dd69b73511d 100644
--- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts
+++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts
@@ -601,7 +601,7 @@ export abstract class InstallInOtherServerAction extends ExtensionAction {
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);
+ 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);
@@ -2348,6 +2348,11 @@ export class ExtensionStatusAction extends ExtensionAction {
}
}
+ if (isEnabled && !isRunning && !this.extension.local.isValid) {
+ const errors = this.extension.local.validations.filter(([severity]) => severity === Severity.Error).map(([, message]) => message);
+ this.updateStatus({ icon: errorIcon, message: new MarkdownString(errors.join(' ').trim()) }, true);
+ }
+
}
private updateStatus(status: ExtensionStatus | undefined, updateClass: boolean): void {
diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts
index f51545869aa..cf3f3473212 100644
--- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts
+++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts
@@ -719,11 +719,9 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE
const message = err && err.message || '';
if (/ECONNREFUSED/.test(message)) {
- const error = createErrorWithActions(localize('suggestProxyError', "Marketplace returned 'ECONNREFUSED'. Please check the 'http.proxy' setting."), {
- actions: [
- new Action('open user settings', localize('open user settings', "Open User Settings"), undefined, true, () => this.preferencesService.openUserSettings())
- ]
- });
+ const error = createErrorWithActions(localize('suggestProxyError', "Marketplace returned 'ECONNREFUSED'. Please check the 'http.proxy' setting."), [
+ new Action('open user settings', localize('open user settings', "Open User Settings"), undefined, true, () => this.preferencesService.openUserSettings())
+ ]);
this.notificationService.error(error);
return;
diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts
index d7455895d9e..de628c54a8d 100644
--- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts
+++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts
@@ -972,11 +972,9 @@ export class ExtensionsListView extends ViewPane {
const message = err && err.message || '';
if (/ECONNREFUSED/.test(message)) {
- const error = createErrorWithActions(localize('suggestProxyError', "Marketplace returned 'ECONNREFUSED'. Please check the 'http.proxy' setting."), {
- actions: [
- new Action('open user settings', localize('open user settings', "Open User Settings"), undefined, true, () => this.preferencesService.openUserSettings())
- ]
- });
+ const error = createErrorWithActions(localize('suggestProxyError', "Marketplace returned 'ECONNREFUSED'. Please check the 'http.proxy' setting."), [
+ new Action('open user settings', localize('open user settings', "Open User Settings"), undefined, true, () => this.preferencesService.openUserSettings())
+ ]);
this.notificationService.error(error);
return;
diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts
index 522cc6346be..76d062f4c5a 100644
--- a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts
+++ b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts
@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/extensionsWidgets';
+import * as semver from 'vs/base/common/semver/semver';
import { Disposable, toDisposable, DisposableStore, MutableDisposable, IDisposable } from 'vs/base/common/lifecycle';
import { IExtension, IExtensionsWorkbenchService, IExtensionContainer, ExtensionState, ExtensionEditorTab } from 'vs/workbench/contrib/extensions/common/extensions';
import { append, $ } from 'vs/base/browser/dom';
@@ -480,7 +481,10 @@ export class ExtensionHoverWidget extends ExtensionWidget {
}
const markdown = new MarkdownString('', { isTrusted: true, supportThemeIcons: true });
- markdown.appendMarkdown(`**${this.extension.displayName}**&nbsp;<span style="background-color:#8080802B;">**&nbsp;_v${this.extension.version}_**&nbsp;</span>`);
+ markdown.appendMarkdown(`**${this.extension.displayName}**`);
+ if (semver.valid(this.extension.version)) {
+ markdown.appendMarkdown(`&nbsp;<span style="background-color:#8080802B;">**&nbsp;_v${this.extension.version}_**&nbsp;</span>`);
+ }
if (this.extension.state === ExtensionState.Installed ? this.extension.local?.isPreReleaseVersion : this.extension.gallery?.properties.isPreReleaseVersion) {
const extensionPreReleaseIcon = this.themeService.getColorTheme().getColor(extensionPreReleaseIconColor);
markdown.appendMarkdown(`**&nbsp;**&nbsp;<span style="color:#ffffff;background-color:${extensionPreReleaseIcon ? Color.Format.CSS.formatHex(extensionPreReleaseIcon) : '#ffffff'};">&nbsp;$(${preReleaseIcon.id})&nbsp;${localize('pre-release-label', "Pre-Release")}&nbsp;</span>`);
diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts
index 2b0a0fad05b..57acb10a415 100644
--- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts
+++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts
@@ -8,7 +8,7 @@ import * as semver from 'vs/base/common/semver/semver';
import { Event, Emitter } from 'vs/base/common/event';
import { index, distinct } from 'vs/base/common/arrays';
import { Promises, ThrottledDelayer } from 'vs/base/common/async';
-import { canceled, isCancellationError } from 'vs/base/common/errors';
+import { CancellationError, isCancellationError } from 'vs/base/common/errors';
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { IPager, singlePagePager } from 'vs/base/common/paging';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
@@ -203,17 +203,25 @@ export class Extension implements IExtension {
}
get outdated(): boolean {
- if (!this.gallery || !this.local) {
- return false;
- }
- if (!this.local.preRelease && this.gallery.properties.isPreReleaseVersion) {
- return false;
- }
- if (semver.gt(this.latestVersion, this.version)) {
- return true;
- }
- if (this.outdatedTargetPlatform) {
- return true;
+ try {
+ if (!this.gallery || !this.local) {
+ return false;
+ }
+ // Do not allow updating system extensions in stable
+ if (this.type === ExtensionType.System && this.productService.quality === 'stable') {
+ return false;
+ }
+ if (!this.local.preRelease && this.gallery.properties.isPreReleaseVersion) {
+ return false;
+ }
+ if (semver.gt(this.latestVersion, this.version)) {
+ return true;
+ }
+ if (this.outdatedTargetPlatform) {
+ return true;
+ }
+ } catch (error) {
+ /* Ignore */
}
return false;
}
@@ -1057,8 +1065,8 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
// Skip if check updates only for builtin extensions and current extension is not builtin.
continue;
}
- if (installed.isBuiltin && !installed.local?.identifier.uuid) {
- // Skip if the builtin extension does not have Marketplace id
+ if (installed.isBuiltin && (!installed.local?.identifier.uuid || this.productService.quality !== 'stable')) {
+ // Skip if the builtin extension does not have Marketplace identifier or the current quality is not stable.
continue;
}
infos.push({ ...installed.identifier, preRelease: !!installed.local?.preRelease });
@@ -1353,7 +1361,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
}
}
], {
- onCancel: () => reject(canceled())
+ onCancel: () => reject(new CancellationError())
});
});
}
diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extension.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extension.test.ts
index 335679af2a9..f07341fe991 100644
--- a/src/vs/workbench/contrib/extensions/test/electron-browser/extension.test.ts
+++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extension.test.ts
@@ -12,6 +12,7 @@ import { URI } from 'vs/base/common/uri';
import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { generateUuid } from 'vs/base/common/uuid';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
+import { IProductService } from 'vs/platform/product/common/productService';
suite('Extension Test', () => {
@@ -19,6 +20,7 @@ suite('Extension Test', () => {
setup(() => {
instantiationService = new TestInstantiationService();
+ instantiationService.stub(IProductService, <Partial<IProductService>>{ quality: 'insiders' });
});
test('extension is not outdated when there is no local and gallery', () => {
@@ -51,6 +53,12 @@ suite('Extension Test', () => {
assert.strictEqual(extension.outdated, true);
});
+ test('extension is not outdated when local is built in and older than gallery but product quality is stable', () => {
+ instantiationService.stub(IProductService, <Partial<IProductService>>{ quality: 'stable' });
+ const extension = instantiationService.createInstance(Extension, () => ExtensionState.Installed, undefined, aLocalExtension('somext', { version: '1.0.0' }, { type: ExtensionType.System }), aGalleryExtension('somext', { version: '1.0.1' }));
+ assert.strictEqual(extension.outdated, false);
+ });
+
test('extension is outdated when local and gallery are on same version but on different target platforms', () => {
const extension = instantiationService.createInstance(Extension, () => ExtensionState.Installed, undefined, aLocalExtension('somext', {}, { targetPlatform: TargetPlatform.WIN32_IA32 }), aGalleryExtension('somext', {}, { targetPlatform: TargetPlatform.WIN32_X64 }));
assert.strictEqual(extension.outdated, true);
diff --git a/src/vs/workbench/contrib/feedback/browser/feedback.ts b/src/vs/workbench/contrib/feedback/browser/feedback.ts
index 46fc36b304e..77be54e32c4 100644
--- a/src/vs/workbench/contrib/feedback/browser/feedback.ts
+++ b/src/vs/workbench/contrib/feedback/browser/feedback.ts
@@ -4,15 +4,15 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/feedback';
-import * as nls from 'vs/nls';
+import { localize } from 'vs/nls';
import { IDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
-import * as dom from 'vs/base/browser/dom';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IIntegrityService } from 'vs/workbench/services/integrity/common/integrity';
import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
import { attachButtonStyler, attachStylerCallback } from 'vs/platform/theme/common/styler';
import { editorWidgetBackground, editorWidgetForeground, widgetShadow, inputBorder, inputForeground, inputBackground, inputActiveOptionBorder, editorBackground, textLinkForeground, contrastBorder } from 'vs/platform/theme/common/colorRegistry';
+import { append, $, addDisposableListener, EventType, EventHelper, prepend } from 'vs/base/browser/dom';
import { IAnchor } from 'vs/base/browser/ui/contextview/contextview';
import { Button } from 'vs/base/browser/ui/button/button';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
@@ -114,25 +114,26 @@ export class FeedbackWidget extends Disposable {
container.classList.add('monaco-menu-container');
// Form
- this.feedbackForm = dom.append<HTMLFormElement>(container, dom.$('form.feedback-form'));
+ this.feedbackForm = append<HTMLFormElement>(container, $('form.feedback-form'));
this.feedbackForm.setAttribute('action', 'javascript:void(0);');
// Title
- dom.append(this.feedbackForm, dom.$('h2.title')).textContent = nls.localize("label.sendASmile", "Tweet us your feedback.");
+ append(this.feedbackForm, $('h2.title')).textContent = localize("label.sendASmile", "Tweet us your feedback.");
// Close Button (top right)
- const closeBtn = dom.append(this.feedbackForm, dom.$('div.cancel' + Codicon.close.cssSelector));
+ const closeBtn = append(this.feedbackForm, $(`div.cancel${Codicon.close.cssSelector}`));
closeBtn.tabIndex = 0;
closeBtn.setAttribute('role', 'button');
- closeBtn.title = nls.localize('close', "Close");
+ closeBtn.title = localize('close', "Close");
- disposables.add(dom.addDisposableListener(container, dom.EventType.KEY_DOWN, keyboardEvent => {
+ disposables.add(addDisposableListener(container, EventType.KEY_DOWN, keyboardEvent => {
const standardKeyboardEvent = new StandardKeyboardEvent(keyboardEvent);
if (standardKeyboardEvent.keyCode === KeyCode.Escape) {
this.hide();
}
}));
- disposables.add(dom.addDisposableListener(closeBtn, dom.EventType.MOUSE_OVER, () => {
+
+ disposables.add(addDisposableListener(closeBtn, EventType.MOUSE_OVER, () => {
const theme = this.themeService.getColorTheme();
let darkenFactor: number | undefined;
switch (theme.type) {
@@ -155,47 +156,47 @@ export class FeedbackWidget extends Disposable {
}
}));
- disposables.add(dom.addDisposableListener(closeBtn, dom.EventType.MOUSE_OUT, () => {
+ disposables.add(addDisposableListener(closeBtn, EventType.MOUSE_OUT, () => {
closeBtn.style.backgroundColor = '';
}));
this.invoke(closeBtn, disposables, () => this.hide());
// Content
- const content = dom.append(this.feedbackForm, dom.$('div.content'));
+ const content = append(this.feedbackForm, $('div.content'));
// Sentiment Buttons
- const sentimentContainer = dom.append(content, dom.$('div'));
+ const sentimentContainer = append(content, $('div'));
if (!this.isPure) {
- dom.append(sentimentContainer, dom.$('span')).textContent = nls.localize("patchedVersion1", "Your installation is corrupt.");
+ append(sentimentContainer, $('span')).textContent = localize("patchedVersion1", "Your installation is corrupt.");
sentimentContainer.appendChild(document.createElement('br'));
- dom.append(sentimentContainer, dom.$('span')).textContent = nls.localize("patchedVersion2", "Please specify this if you submit a bug.");
+ append(sentimentContainer, $('span')).textContent = localize("patchedVersion2", "Please specify this if you submit a bug.");
sentimentContainer.appendChild(document.createElement('br'));
}
- dom.append(sentimentContainer, dom.$('span')).textContent = nls.localize("sentiment", "How was your experience?");
+ append(sentimentContainer, $('span')).textContent = localize("sentiment", "How was your experience?");
- const feedbackSentiment = dom.append(sentimentContainer, dom.$('div.feedback-sentiment'));
+ const feedbackSentiment = append(sentimentContainer, $('div.feedback-sentiment'));
// Sentiment: Smiley
- this.smileyInput = dom.append(feedbackSentiment, dom.$('div.sentiment'));
+ this.smileyInput = append(feedbackSentiment, $('div.sentiment'));
this.smileyInput.classList.add('smile');
this.smileyInput.setAttribute('aria-checked', 'false');
- this.smileyInput.setAttribute('aria-label', nls.localize('smileCaption', "Happy Feedback Sentiment"));
+ this.smileyInput.setAttribute('aria-label', localize('smileCaption', "Happy Feedback Sentiment"));
this.smileyInput.setAttribute('role', 'checkbox');
- this.smileyInput.title = nls.localize('smileCaption', "Happy Feedback Sentiment");
+ this.smileyInput.title = localize('smileCaption', "Happy Feedback Sentiment");
this.smileyInput.tabIndex = 0;
this.invoke(this.smileyInput, disposables, () => this.setSentiment(true));
// Sentiment: Frowny
- this.frownyInput = dom.append(feedbackSentiment, dom.$('div.sentiment'));
+ this.frownyInput = append(feedbackSentiment, $('div.sentiment'));
this.frownyInput.classList.add('frown');
this.frownyInput.setAttribute('aria-checked', 'false');
- this.frownyInput.setAttribute('aria-label', nls.localize('frownCaption', "Sad Feedback Sentiment"));
+ this.frownyInput.setAttribute('aria-label', localize('frownCaption', "Sad Feedback Sentiment"));
this.frownyInput.setAttribute('role', 'checkbox');
- this.frownyInput.title = nls.localize('frownCaption', "Sad Feedback Sentiment");
+ this.frownyInput.title = localize('frownCaption', "Sad Feedback Sentiment");
this.frownyInput.tabIndex = 0;
this.invoke(this.frownyInput, disposables, () => this.setSentiment(false));
@@ -209,23 +210,23 @@ export class FeedbackWidget extends Disposable {
}
// Contact Us Box
- const contactUsContainer = dom.append(content, dom.$('div.contactus'));
+ const contactUsContainer = append(content, $('div.contactus'));
- dom.append(contactUsContainer, dom.$('span')).textContent = nls.localize("other ways to contact us", "Other ways to contact us");
+ append(contactUsContainer, $('span')).textContent = localize("other ways to contact us", "Other ways to contact us");
- const channelsContainer = dom.append(contactUsContainer, dom.$('div.channels'));
+ const channelsContainer = append(contactUsContainer, $('div.channels'));
// Contact: Submit a Bug
- const submitBugLinkContainer = dom.append(channelsContainer, dom.$('div'));
+ const submitBugLinkContainer = append(channelsContainer, $('div'));
- const submitBugLink = dom.append(submitBugLinkContainer, dom.$('a'));
+ const submitBugLink = append(submitBugLinkContainer, $('a'));
submitBugLink.setAttribute('target', '_blank');
submitBugLink.setAttribute('href', '#');
- submitBugLink.textContent = nls.localize("submit a bug", "Submit a bug");
+ submitBugLink.textContent = localize("submit a bug", "Submit a bug");
submitBugLink.tabIndex = 0;
- disposables.add(dom.addDisposableListener(submitBugLink, 'click', e => {
- dom.EventHelper.stop(e);
+ disposables.add(addDisposableListener(submitBugLink, 'click', e => {
+ EventHelper.stop(e);
const actionId = 'workbench.action.openIssueReporter';
this.commandService.executeCommand(actionId);
this.hide();
@@ -234,57 +235,57 @@ export class FeedbackWidget extends Disposable {
// Contact: Request a Feature
if (!!this.requestFeatureLink) {
- const requestFeatureLinkContainer = dom.append(channelsContainer, dom.$('div'));
+ const requestFeatureLinkContainer = append(channelsContainer, $('div'));
- const requestFeatureLink = dom.append(requestFeatureLinkContainer, dom.$('a'));
+ const requestFeatureLink = append(requestFeatureLinkContainer, $('a'));
requestFeatureLink.setAttribute('target', '_blank');
requestFeatureLink.setAttribute('href', this.requestFeatureLink);
- requestFeatureLink.textContent = nls.localize("request a missing feature", "Request a missing feature");
+ requestFeatureLink.textContent = localize("request a missing feature", "Request a missing feature");
requestFeatureLink.tabIndex = 0;
- disposables.add(dom.addDisposableListener(requestFeatureLink, 'click', e => this.hide()));
+ disposables.add(addDisposableListener(requestFeatureLink, 'click', e => this.hide()));
}
// Remaining Characters
- const remainingCharacterCountContainer = dom.append(this.feedbackForm, dom.$('h3'));
- remainingCharacterCountContainer.textContent = nls.localize("tell us why", "Tell us why?");
+ const remainingCharacterCountContainer = append(this.feedbackForm, $('h3'));
+ remainingCharacterCountContainer.textContent = localize("tell us why", "Tell us why?");
- this.remainingCharacterCount = dom.append(remainingCharacterCountContainer, dom.$('span.char-counter'));
+ this.remainingCharacterCount = append(remainingCharacterCountContainer, $('span.char-counter'));
this.remainingCharacterCount.textContent = this.getCharCountText(0);
// Feedback Input Form
- this.feedbackDescriptionInput = dom.append<HTMLTextAreaElement>(this.feedbackForm, dom.$('textarea.feedback-description'));
+ this.feedbackDescriptionInput = append<HTMLTextAreaElement>(this.feedbackForm, $('textarea.feedback-description'));
this.feedbackDescriptionInput.rows = 3;
this.feedbackDescriptionInput.maxLength = this.maxFeedbackCharacters;
this.feedbackDescriptionInput.textContent = this.feedback;
this.feedbackDescriptionInput.required = true;
- this.feedbackDescriptionInput.setAttribute('aria-label', nls.localize("feedbackTextInput", "Tell us your feedback"));
+ this.feedbackDescriptionInput.setAttribute('aria-label', localize("feedbackTextInput", "Tell us your feedback"));
this.feedbackDescriptionInput.focus();
- disposables.add(dom.addDisposableListener(this.feedbackDescriptionInput, 'keyup', () => this.updateCharCountText()));
+ disposables.add(addDisposableListener(this.feedbackDescriptionInput, 'keyup', () => this.updateCharCountText()));
// Feedback Input Form Buttons Container
- const buttonsContainer = dom.append(this.feedbackForm, dom.$('div.form-buttons'));
+ const buttonsContainer = append(this.feedbackForm, $('div.form-buttons'));
// Checkbox: Hide Feedback Smiley
- const hideButtonContainer = dom.append(buttonsContainer, dom.$('div.hide-button-container'));
+ const hideButtonContainer = append(buttonsContainer, $('div.hide-button-container'));
- this.hideButton = dom.append(hideButtonContainer, dom.$('input.hide-button')) as HTMLInputElement;
+ this.hideButton = append(hideButtonContainer, $('input.hide-button')) as HTMLInputElement;
this.hideButton.type = 'checkbox';
this.hideButton.checked = true;
this.hideButton.id = 'hide-button';
- const hideButtonLabel = dom.append(hideButtonContainer, dom.$('label'));
+ const hideButtonLabel = append(hideButtonContainer, $('label'));
hideButtonLabel.setAttribute('for', 'hide-button');
- hideButtonLabel.textContent = nls.localize('showFeedback', "Show Feedback Icon in Status Bar");
+ hideButtonLabel.textContent = localize('showFeedback', "Show Feedback Icon in Status Bar");
// Button: Send Feedback
this.sendButton = new Button(buttonsContainer);
this.sendButton.enabled = false;
- this.sendButton.label = nls.localize('tweet', "Tweet");
- dom.prepend(this.sendButton.element, dom.$('span' + Codicon.twitter.cssSelector));
+ this.sendButton.label = localize('tweet', "Tweet");
+ prepend(this.sendButton.element, $(`span${Codicon.twitter.cssSelector}`));
this.sendButton.element.classList.add('send');
- this.sendButton.element.title = nls.localize('tweetFeedback', "Tweet Feedback");
+ this.sendButton.element.title = localize('tweetFeedback', "Tweet Feedback");
disposables.add(attachButtonStyler(this.sendButton, this.themeService));
this.sendButton.onDidClick(() => this.onSubmit());
@@ -326,8 +327,8 @@ export class FeedbackWidget extends Disposable {
private getCharCountText(charCount: number): string {
const remaining = this.maxFeedbackCharacters - charCount;
const text = (remaining === 1)
- ? nls.localize("character left", "character left")
- : nls.localize("characters left", "characters left");
+ ? localize("character left", "character left")
+ : localize("characters left", "characters left");
return `(${remaining} ${text})`;
}
@@ -370,9 +371,9 @@ export class FeedbackWidget extends Disposable {
}
private invoke(element: HTMLElement, disposables: DisposableStore, callback: () => void): HTMLElement {
- disposables.add(dom.addDisposableListener(element, 'click', callback));
+ disposables.add(addDisposableListener(element, 'click', callback));
- disposables.add(dom.addDisposableListener(element, 'keypress', e => {
+ disposables.add(addDisposableListener(element, 'keypress', e => {
if (e instanceof KeyboardEvent) {
const keyboardEvent = <KeyboardEvent>e;
if (keyboardEvent.keyCode === 13 || keyboardEvent.keyCode === 32) { // Enter or Spacebar
diff --git a/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts
index 630cd3a523a..b6317eb2dd7 100644
--- a/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts
+++ b/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts
@@ -60,6 +60,10 @@ export class FileEditorInput extends AbstractTextResourceEditorInput implements
}
}
+ if (!(capabilities & EditorInputCapabilities.Readonly)) {
+ capabilities |= EditorInputCapabilities.CanDropIntoEditor;
+ }
+
return capabilities;
}
diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts
index f7eca9c59df..e77d66da61a 100644
--- a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts
+++ b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts
@@ -6,7 +6,7 @@
import { localize } from 'vs/nls';
import { assertIsDefined } from 'vs/base/common/types';
import { IPathService } from 'vs/workbench/services/path/common/pathService';
-import { toAction } from 'vs/base/common/actions';
+import { IAction, toAction } from 'vs/base/common/actions';
import { VIEWLET_ID, TEXT_FILE_EDITOR_ID, BINARY_TEXT_FILE_MODE } from 'vs/workbench/contrib/files/common/files';
import { ITextFileService, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles';
import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor';
@@ -26,7 +26,7 @@ import { ICodeEditorViewState, ScrollType } from 'vs/editor/common/editorCommon'
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { CancellationToken } from 'vs/base/common/cancellation';
-import { IErrorWithActions } from 'vs/base/common/errorMessage';
+import { createErrorWithActions } from 'vs/base/common/errorMessage';
import { EditorActivation, ITextEditorOptions } from 'vs/platform/editor/common/editor';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
import { IExplorerService } from 'vs/workbench/contrib/files/browser/files';
@@ -180,15 +180,29 @@ export class TextFileEditor extends BaseTextEditor<ICodeEditorViewState> {
// Similar, handle case where we were asked to open a folder in the text editor.
if ((<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_IS_DIRECTORY) {
- this.openAsFolder(input);
+ let action: IAction;
+ if (this.contextService.isInsideWorkspace(input.preferredResource)) {
+ action = toAction({
+ id: 'workbench.files.action.reveal', label: localize('reveal', "Reveal in Explorer View"), run: async () => {
+ await this.paneCompositeService.openPaneComposite(VIEWLET_ID, ViewContainerLocation.Sidebar, true);
- throw new Error(localize('openFolderError', "File is a directory"));
+ return this.explorerService.select(input.preferredResource, true);
+ }
+ });
+ } else {
+ action = toAction({
+ id: 'workbench.files.action.ok', label: localize('ok', "OK"), run: async () => {
+ // No operation possible, but clicking OK will close the editor
+ }
+ });
+ }
+
+ throw createErrorWithActions(new FileOperationError(localize('fileIsDirectoryError', "File is a directory"), FileOperationResult.FILE_IS_DIRECTORY), [action]);
}
// Offer to create a file from the error if we have a file not found and the name is valid
if ((<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND && await this.pathService.hasValidBasename(input.preferredResource)) {
- const fileNotFoundError: FileOperationError & IErrorWithActions = new FileOperationError(localize('fileNotFoundError', "File not found"), FileOperationResult.FILE_NOT_FOUND);
- fileNotFoundError.actions = [
+ const fileNotFoundError = createErrorWithActions(new FileOperationError(localize('fileNotFoundError', "File not found"), FileOperationResult.FILE_NOT_FOUND), [
toAction({
id: 'workbench.files.action.createMissingFile', label: localize('createFile', "Create File"), run: async () => {
await this.textFileService.create([{ resource: input.preferredResource }]);
@@ -201,7 +215,7 @@ export class TextFileEditor extends BaseTextEditor<ICodeEditorViewState> {
});
}
})
- ];
+ ]);
throw fileNotFoundError;
}
@@ -262,22 +276,6 @@ export class TextFileEditor extends BaseTextEditor<ICodeEditorViewState> {
group.openEditor(editor, editorOptions);
}
- private async openAsFolder(input: FileEditorInput): Promise<void> {
- if (!this.group) {
- return;
- }
-
- // Since we cannot open a folder, we have to restore the previous input if any and close the editor
- await this.group.closeEditor(this.input);
-
- // Best we can do is to reveal the folder in the explorer
- if (this.contextService.isInsideWorkspace(input.preferredResource)) {
- await this.paneCompositeService.openPaneComposite(VIEWLET_ID, ViewContainerLocation.Sidebar);
-
- this.explorerService.select(input.preferredResource, true);
- }
- }
-
override clearInput(): void {
super.clearInput();
diff --git a/src/vs/workbench/contrib/files/browser/explorerService.ts b/src/vs/workbench/contrib/files/browser/explorerService.ts
index 5293c2640ca..a1d5910f284 100644
--- a/src/vs/workbench/contrib/files/browser/explorerService.ts
+++ b/src/vs/workbench/contrib/files/browser/explorerService.ts
@@ -33,8 +33,7 @@ export class ExplorerService implements IExplorerService {
private readonly disposables = new DisposableStore();
private editable: { stat: ExplorerItem; data: IEditableData } | undefined;
- private _sortOrder: SortOrder;
- private _lexicographicOptions: LexicographicOptions;
+ private config: IFilesConfiguration['explorer'];
private cutItems: ExplorerItem[] | undefined;
private view: IExplorerView | undefined;
private model: ExplorerModel;
@@ -52,8 +51,7 @@ export class ExplorerService implements IExplorerService {
@IProgressService private readonly progressService: IProgressService,
@IHostService hostService: IHostService
) {
- this._sortOrder = this.configurationService.getValue('explorer.sortOrder');
- this._lexicographicOptions = this.configurationService.getValue('explorer.sortOrderLexicographicOptions');
+ this.config = this.configurationService.getValue('explorer');
this.model = new ExplorerModel(this.contextService, this.uriIdentityService, this.fileService, this.configurationService);
this.disposables.add(this.model);
@@ -65,7 +63,7 @@ export class ExplorerService implements IExplorerService {
// Filter to the ones we care
const types = [FileChangeType.DELETED];
- if (this._sortOrder === SortOrder.Modified) {
+ if (this.config.sortOrder === SortOrder.Modified) {
types.push(FileChangeType.UPDATED);
}
@@ -142,8 +140,8 @@ export class ExplorerService implements IExplorerService {
get sortOrderConfiguration(): ISortOrderConfiguration {
return {
- sortOrder: this._sortOrder,
- lexicographicOptions: this._lexicographicOptions,
+ sortOrder: this.config.sortOrder,
+ lexicographicOptions: this.config.sortOrderLexicographicOptions,
};
}
@@ -151,21 +149,19 @@ export class ExplorerService implements IExplorerService {
this.view = contextProvider;
}
- getContext(respectMultiSelection: boolean, includeNestedChildren = false): ExplorerItem[] {
+ getContext(respectMultiSelection: boolean): ExplorerItem[] {
if (!this.view) {
return [];
}
const items = new Set<ExplorerItem>(this.view.getContext(respectMultiSelection));
- if (includeNestedChildren) {
- items.forEach(item => {
- if (item.nestedChildren) {
- for (const child of item.nestedChildren) {
- items.add(child);
- }
+ items.forEach(item => {
+ if (this.view?.isItemCollapsed(item) && item.nestedChildren) {
+ for (const child of item.nestedChildren) {
+ items.add(child);
}
- });
- }
+ }
+ });
return [...items];
}
@@ -261,7 +257,7 @@ export class ExplorerService implements IExplorerService {
}
// Stat needs to be resolved first and then revealed
- const options: IResolveFileOptions = { resolveTo: [resource], resolveMetadata: this._sortOrder === SortOrder.Modified };
+ const options: IResolveFileOptions = { resolveTo: [resource], resolveMetadata: this.config.sortOrder === SortOrder.Modified };
const root = this.findClosestRoot(resource);
if (!root) {
return undefined;
@@ -302,6 +298,11 @@ export class ExplorerService implements IExplorerService {
// File events
private async onDidRunOperation(e: FileOperationEvent): Promise<void> {
+ // When nesting, changes to one file in a folder may impact the rendered structure
+ // of all the folder's immediate children, thus a recursive refresh is needed.
+ // Ideally the tree would be able to recusively refresh just one level but that does not yet exist.
+ const shouldDeepRefresh = this.config.fileNesting.enabled;
+
// Add
if (e.isOperation(FileOperation.CREATE) || e.isOperation(FileOperation.COPY)) {
const addedElement = e.target;
@@ -313,7 +314,7 @@ export class ExplorerService implements IExplorerService {
// Add the new file to its parent (Model)
await Promise.all(parents.map(async p => {
// We have to check if the parent is resolved #29177
- const resolveMetadata = this._sortOrder === `modified`;
+ const resolveMetadata = this.config.sortOrder === `modified`;
if (!p.isDirectoryResolved) {
const stat = await this.fileService.resolve(p.resource, { resolveMetadata });
if (stat) {
@@ -327,7 +328,7 @@ export class ExplorerService implements IExplorerService {
p.removeChild(childElement);
p.addChild(childElement);
// Refresh the Parent (View)
- await this.view?.refresh(false, p);
+ await this.view?.refresh(shouldDeepRefresh, p);
}));
}
}
@@ -346,7 +347,7 @@ export class ExplorerService implements IExplorerService {
await Promise.all(modelElements.map(async modelElement => {
// Rename File (Model)
modelElement.rename(newElement);
- await this.view?.refresh(false, modelElement.parent);
+ await this.view?.refresh(shouldDeepRefresh, modelElement.parent);
}));
}
@@ -363,7 +364,7 @@ export class ExplorerService implements IExplorerService {
await this.view?.refresh(false, oldNestedParent);
}
await this.view?.refresh(false, oldParent);
- await this.view?.refresh(false, newParents[index]);
+ await this.view?.refresh(shouldDeepRefresh, newParents[index]);
}));
}
}
@@ -384,7 +385,7 @@ export class ExplorerService implements IExplorerService {
await this.view?.refresh(false, oldNestedParent);
}
// Refresh Parent (View)
- await this.view?.refresh(false, parent);
+ await this.view?.refresh(shouldDeepRefresh, parent);
}
}));
}
@@ -393,22 +394,22 @@ export class ExplorerService implements IExplorerService {
private async onConfigurationUpdated(configuration: IFilesConfiguration, event?: IConfigurationChangeEvent): Promise<void> {
let shouldRefresh = false;
- if (event?.affectedKeys.some(x => x.startsWith('explorer.experimental.fileNesting.'))) {
+ if (event?.affectedKeys.some(x => x.startsWith('explorer.fileNesting.'))) {
shouldRefresh = true;
}
const configSortOrder = configuration?.explorer?.sortOrder || SortOrder.Default;
- if (this._sortOrder !== configSortOrder) {
- shouldRefresh = this._sortOrder !== undefined;
- this._sortOrder = configSortOrder;
+ if (this.config.sortOrder !== configSortOrder) {
+ shouldRefresh = this.config.sortOrder !== undefined;
}
const configLexicographicOptions = configuration?.explorer?.sortOrderLexicographicOptions || LexicographicOptions.Default;
- if (this._lexicographicOptions !== configLexicographicOptions) {
- shouldRefresh = shouldRefresh || this._lexicographicOptions !== undefined;
- this._lexicographicOptions = configLexicographicOptions;
+ if (this.config.sortOrderLexicographicOptions !== configLexicographicOptions) {
+ shouldRefresh = shouldRefresh || this.config.sortOrderLexicographicOptions !== undefined;
}
+ this.config = configuration.explorer;
+
if (shouldRefresh) {
await this.refresh();
}
diff --git a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts
index d88bd1c2d2b..57bb796dfc4 100644
--- a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts
+++ b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts
@@ -154,15 +154,16 @@ const copyRelativePathCommand = {
// Editor Title Context Menu
appendEditorTitleContextMenuItem(COPY_PATH_COMMAND_ID, copyPathCommand.title, ResourceContextKey.IsFileSystemResource, '1_cutcopypaste');
appendEditorTitleContextMenuItem(COPY_RELATIVE_PATH_COMMAND_ID, copyRelativePathCommand.title, ResourceContextKey.IsFileSystemResource, '1_cutcopypaste');
-appendEditorTitleContextMenuItem(REVEAL_IN_EXPLORER_COMMAND_ID, nls.localize('revealInSideBar', "Reveal in Explorer View"), ResourceContextKey.IsFileSystemResource);
+appendEditorTitleContextMenuItem(REVEAL_IN_EXPLORER_COMMAND_ID, nls.localize('revealInSideBar', "Reveal in Explorer View"), ResourceContextKey.IsFileSystemResource, '2_files', 1);
-export function appendEditorTitleContextMenuItem(id: string, title: string, when: ContextKeyExpression | undefined, group?: string): void {
+export function appendEditorTitleContextMenuItem(id: string, title: string, when: ContextKeyExpression | undefined, group: string, order?: number): void {
// Menu
MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, {
command: { id, title },
when,
- group: group || '2_files'
+ group,
+ order
});
}
@@ -576,7 +577,7 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, {
});
// Empty Editor Group Context Menu
-MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroupContext, { command: { id: NEW_UNTITLED_FILE_COMMAND_ID, title: nls.localize('newFile', "New File") }, group: '1_file', order: 10 });
+MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroupContext, { command: { id: NEW_UNTITLED_FILE_COMMAND_ID, title: nls.localize('newFile', "New Text File") }, group: '1_file', order: 10 });
MenuRegistry.appendMenuItem(MenuId.EmptyEditorGroupContext, { command: { id: 'workbench.action.quickOpen', title: nls.localize('openFile', "Open File...") }, group: '1_file', order: 20 });
// File menu
@@ -585,7 +586,7 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {
group: '1_new',
command: {
id: NEW_UNTITLED_FILE_COMMAND_ID,
- title: nls.localize({ key: 'miNewFile', comment: ['&& denotes a mnemonic'] }, "&&New File")
+ title: nls.localize({ key: 'miNewFile', comment: ['&& denotes a mnemonic'] }, "&&New Text File")
},
order: 1
});
diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts
index d3100d24946..56160c6d47d 100644
--- a/src/vs/workbench/contrib/files/browser/fileActions.ts
+++ b/src/vs/workbench/contrib/files/browser/fileActions.ts
@@ -906,9 +906,7 @@ export const renameHandler = async (accessor: ServicesAccessor) => {
export const moveFileToTrashHandler = async (accessor: ServicesAccessor) => {
const explorerService = accessor.get(IExplorerService);
- const configurationService = accessor.get(IConfigurationService);
- const groupNests = configurationService.getValue<IFilesConfiguration>().explorer.experimental.fileNesting.operateAsGroup;
- const stats = explorerService.getContext(true, groupNests).filter(s => !s.isRoot);
+ const stats = explorerService.getContext(true).filter(s => !s.isRoot);
if (stats.length) {
await deleteFiles(accessor.get(IExplorerService), accessor.get(IWorkingCopyFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, true);
}
@@ -916,9 +914,7 @@ export const moveFileToTrashHandler = async (accessor: ServicesAccessor) => {
export const deleteFileHandler = async (accessor: ServicesAccessor) => {
const explorerService = accessor.get(IExplorerService);
- const configurationService = accessor.get(IConfigurationService);
- const groupNests = configurationService.getValue<IFilesConfiguration>().explorer.experimental.fileNesting.operateAsGroup;
- const stats = explorerService.getContext(true, groupNests).filter(s => !s.isRoot);
+ const stats = explorerService.getContext(true).filter(s => !s.isRoot);
if (stats.length) {
await deleteFiles(accessor.get(IExplorerService), accessor.get(IWorkingCopyFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), stats, false);
@@ -928,9 +924,7 @@ export const deleteFileHandler = async (accessor: ServicesAccessor) => {
let pasteShouldMove = false;
export const copyFileHandler = async (accessor: ServicesAccessor) => {
const explorerService = accessor.get(IExplorerService);
- const configurationService = accessor.get(IConfigurationService);
- const groupNests = configurationService.getValue<IFilesConfiguration>().explorer.experimental.fileNesting.operateAsGroup;
- const stats = explorerService.getContext(true, groupNests);
+ const stats = explorerService.getContext(true);
if (stats.length > 0) {
await explorerService.setToCopy(stats, false);
pasteShouldMove = false;
@@ -939,9 +933,7 @@ export const copyFileHandler = async (accessor: ServicesAccessor) => {
export const cutFileHandler = async (accessor: ServicesAccessor) => {
const explorerService = accessor.get(IExplorerService);
- const configurationService = accessor.get(IConfigurationService);
- const groupNests = configurationService.getValue<IFilesConfiguration>().explorer.experimental.fileNesting.operateAsGroup;
- const stats = explorerService.getContext(true, groupNests);
+ const stats = explorerService.getContext(true);
if (stats.length > 0) {
await explorerService.setToCopy(stats, true);
pasteShouldMove = true;
diff --git a/src/vs/workbench/contrib/files/browser/fileImportExport.ts b/src/vs/workbench/contrib/files/browser/fileImportExport.ts
index 08fdd59d803..71eda39201c 100644
--- a/src/vs/workbench/contrib/files/browser/fileImportExport.ts
+++ b/src/vs/workbench/contrib/files/browser/fileImportExport.ts
@@ -14,7 +14,7 @@ import { IFilesConfiguration, UndoConfirmLevel, VIEW_ID } from 'vs/workbench/con
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { Limiter, Promises, RunOnceWorker } from 'vs/base/common/async';
import { newWriteableBufferStream, VSBuffer } from 'vs/base/common/buffer';
-import { basename, joinPath } from 'vs/base/common/resources';
+import { basename, dirname, joinPath } from 'vs/base/common/resources';
import { ResourceFileEdit } from 'vs/editor/browser/services/bulkEditService';
import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel';
import { URI } from 'vs/base/common/uri';
@@ -35,6 +35,7 @@ import { canceled } from 'vs/base/common/errors';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { WebFileSystemAccess } from 'vs/platform/files/browser/webFileSystemAccess';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
+import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
//#region Browser File Upload (drag and drop, input element)
@@ -574,12 +575,15 @@ interface IDownloadOperation {
export class FileDownload {
+ private static readonly LAST_USED_DOWNLOAD_PATH_STORAGE_KEY = 'workbench.explorer.downloadPath';
+
constructor(
@IFileService private readonly fileService: IFileService,
@IExplorerService private readonly explorerService: IExplorerService,
@IProgressService private readonly progressService: IProgressService,
@ILogService private readonly logService: ILogService,
- @IFileDialogService private readonly fileDialogService: IFileDialogService
+ @IFileDialogService private readonly fileDialogService: IFileDialogService,
+ @IStorageService private readonly storageService: IStorageService
) {
}
@@ -791,12 +795,18 @@ export class FileDownload {
private async doDownloadNative(explorerItem: ExplorerItem, progress: IProgress<IProgressStep>, cts: CancellationTokenSource): Promise<void> {
progress.report({ message: explorerItem.name });
- const defaultUri = joinPath(
- explorerItem.isDirectory ?
- await this.fileDialogService.defaultFolderPath(Schemas.file) :
- await this.fileDialogService.defaultFilePath(Schemas.file),
- explorerItem.name
- );
+ let defaultUri: URI;
+ const lastUsedDownloadPath = this.storageService.get(FileDownload.LAST_USED_DOWNLOAD_PATH_STORAGE_KEY, StorageScope.GLOBAL);
+ if (lastUsedDownloadPath) {
+ defaultUri = joinPath(URI.file(lastUsedDownloadPath), explorerItem.name);
+ } else {
+ defaultUri = joinPath(
+ explorerItem.isDirectory ?
+ await this.fileDialogService.defaultFolderPath(Schemas.file) :
+ await this.fileDialogService.defaultFilePath(Schemas.file),
+ explorerItem.name
+ );
+ }
const destination = await this.fileDialogService.showSaveDialog({
availableFileSystems: [Schemas.file],
@@ -806,6 +816,11 @@ export class FileDownload {
});
if (destination) {
+
+ // Remember as last used download folder
+ this.storageService.store(FileDownload.LAST_USED_DOWNLOAD_PATH_STORAGE_KEY, dirname(destination).fsPath, StorageScope.GLOBAL, StorageTarget.MACHINE);
+
+ // Perform download
await this.explorerService.applyBulkEdit([new ResourceFileEdit(explorerItem.resource, destination, { overwrite: true, copy: true })], {
undoLabel: localize('downloadBulkEdit', "Download {0}", explorerItem.name),
progressLabel: localize('downloadingBulkEdit', "Downloading {0}", explorerItem.name),
diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts
index 1a6f2c8f672..b162ad2ead5 100644
--- a/src/vs/workbench/contrib/files/browser/files.contribution.ts
+++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts
@@ -246,7 +246,7 @@ configurationRegistry.registerConfiguration({
'files.watcherExclude': {
'type': 'object',
'default': { '**/.git/objects/**': true, '**/.git/subtree-cache/**': true, '**/node_modules/*/**': true, '**/.hg/store/**': true },
- 'markdownDescription': nls.localize('watcherExclude', "Configure paths or glob patterns to exclude from file watching. Paths that are relative (for example `build/output`) will be resolved to an absolute path using the currently opened workspace. Glob patterns must match on absolute paths (i.e. prefix with `**/` or the full path and suffix with `/**` to match files within a path) to match properly (for example `**/build/output/**` or `/Users/name/workspaces/project/build/output/**`). When you experience the file watcher process consuming a lot of CPU, make sure to exclude large folders that are of less interest (such as build output folders)."),
+ 'markdownDescription': nls.localize('watcherExclude', "Configure paths or glob patterns to exclude from file watching. Paths or basic glob patterns that are relative (for example `build/output` or `*.js`) will be resolved to an absolute path using the currently opened workspace. Complex glob patterns must match on absolute paths (i.e. prefix with `**/` or the full path and suffix with `/**` to match files within a path) to match properly (for example `**/build/output/**` or `/Users/name/workspaces/project/build/output/**`). When you experience the file watcher process consuming a lot of CPU, make sure to exclude large folders that are of less interest (such as build output folders)."),
'scope': ConfigurationScope.RESOURCE
},
'files.watcherInclude': {
@@ -337,10 +337,16 @@ configurationRegistry.registerConfiguration({
'properties': {
'explorer.openEditors.visible': {
'type': 'number',
- 'description': nls.localize({ key: 'openEditorsVisible', comment: ['Open is an adjective'] }, "Number of editors shown in the Open Editors pane. Setting this to 0 hides the Open Editors pane."),
+ 'description': nls.localize({ key: 'openEditorsVisible', comment: ['Open is an adjective'] }, "The maximum number of editors shown in the Open Editors pane. Setting this to 0 hides the Open Editors pane."),
'default': 9,
'minimum': 0
},
+ 'explorer.openEditors.minVisible': {
+ 'type': 'number',
+ 'description': nls.localize({ key: 'openEditorsVisibleMin', comment: ['Open is an adjective'] }, "The minimum number of editor slots shown in the Open Editors pane. If set to 0 the Open Editors pane will dynamically resize based on the number of editors."),
+ 'default': 0,
+ 'minimum': 0
+ },
'explorer.openEditors.sortOrder': {
'type': 'string',
'enum': ['editorOrder', 'alphabetical', 'fullPath'],
@@ -411,7 +417,7 @@ configurationRegistry.registerConfiguration({
nls.localize('sortOrder.modified', 'Files and folders are sorted by last modified date in descending order. Folders are displayed before files.'),
nls.localize('sortOrder.foldersNestsFiles', 'Files and folders are sorted by their names. Folders are displayed before files. Files with nested children are displayed before other files.')
],
- 'markdownDescription': nls.localize('sortOrder', "Controls the property-based sorting of files and folders in the explorer. When `#explorer.experimental.fileNesting.enabled#` is enabled, also controls sorting of nested files.")
+ 'markdownDescription': nls.localize('sortOrder', "Controls the property-based sorting of files and folders in the explorer. When `#explorer.fileNesting.enabled#` is enabled, also controls sorting of nested files.")
},
'explorer.sortOrderLexicographicOptions': {
'type': 'string',
@@ -465,23 +471,18 @@ configurationRegistry.registerConfiguration({
'description': nls.localize('copyRelativePathSeparator', "The path separation character used when copying relative file paths."),
'default': 'auto'
},
- 'explorer.experimental.fileNesting.enabled': {
+ 'explorer.fileNesting.enabled': {
'type': 'boolean',
scope: ConfigurationScope.RESOURCE,
- 'markdownDescription': nls.localize('fileNestingEnabled', "Experimental. Controls whether file nesting is enabled in the explorer. File nesting allows for related files in a directory to be visually grouped together under a single parent file."),
+ 'markdownDescription': nls.localize('fileNestingEnabled', "Controls whether file nesting is enabled in the explorer. File nesting allows for related files in a directory to be visually grouped together under a single parent file."),
'default': false,
},
- 'explorer.experimental.fileNesting.expand': {
- 'type': 'boolean',
- 'markdownDescription': nls.localize('fileNestingExpand', "Experimental. Controls whether file nests are automatically expanded. `#explorer.experimental.fileNesting.enabled#` must be set for this to take effect."),
- 'default': true,
- },
- 'explorer.experimental.fileNesting.operateAsGroup': {
+ 'explorer.fileNesting.expand': {
'type': 'boolean',
- 'markdownDescription': nls.localize('operateAsGroup', "Controls whether file nests are treated as a group for clipboard operations, file deletions, and during drag and drop."),
+ 'markdownDescription': nls.localize('fileNestingExpand', "Controls whether file nests are automatically expanded. `#explorer.fileNesting.enabled#` must be set for this to take effect."),
'default': true,
},
- 'explorer.experimental.fileNesting.patterns': {
+ 'explorer.fileNesting.patterns': {
'type': 'object',
scope: ConfigurationScope.RESOURCE,
'markdownDescription': nls.localize('fileNestingPatterns', "Controls nesting of files in the explorer. Each __Item__ represents a parent pattern and may contain a single `*` character that matches any string. Each __Value__ represents a comma separated list of the child patterns that should be shown nested under a given parent. Child patterns may contain several special tokens:\n- `${capture}`: Matches the resolved value of the `*` from the parent pattern\n- `${basename}`: Matches the parent file's basename, the `file` in `file.ts`\n- `${extname}`: Matches the parent file's extension, the `ts` in `file.ts`\n- `${dirname}`: Matches the parent file's directory name, the `src` in `src/file.ts`\n- `*`: Matches any string, may only be used once per child pattern"),
diff --git a/src/vs/workbench/contrib/files/browser/files.ts b/src/vs/workbench/contrib/files/browser/files.ts
index 3c034c4e388..b4b01687527 100644
--- a/src/vs/workbench/contrib/files/browser/files.ts
+++ b/src/vs/workbench/contrib/files/browser/files.ts
@@ -56,6 +56,7 @@ export interface IExplorerView {
itemsCopied(tats: ExplorerItem[], cut: boolean, previousCut: ExplorerItem[] | undefined): void;
setEditable(stat: ExplorerItem, isEditing: boolean): Promise<void>;
isItemVisible(item: ExplorerItem): boolean;
+ isItemCollapsed(item: ExplorerItem): boolean;
hasFocus(): boolean;
}
diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts
index c82cc35150a..1ba98e190c7 100644
--- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts
+++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts
@@ -56,7 +56,6 @@ import { Codicon } from 'vs/base/common/codicons';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IEditorResolverService } from 'vs/workbench/services/editor/common/editorResolverService';
import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite';
-import { INotificationService } from 'vs/platform/notification/common/notification';
import { EditorOpenSource } from 'vs/platform/editor/common/editor';
import { ResourceMap } from 'vs/base/common/map';
@@ -188,7 +187,6 @@ export class ExplorerView extends ViewPane implements IExplorerView {
@IMenuService private readonly menuService: IMenuService,
@ITelemetryService telemetryService: ITelemetryService,
@IExplorerService private readonly explorerService: IExplorerService,
- @INotificationService private readonly notificationService: INotificationService,
@IStorageService private readonly storageService: IStorageService,
@IClipboardService private clipboardService: IClipboardService,
@IFileService private readonly fileService: IFileService,
@@ -325,6 +323,10 @@ export class ExplorerView extends ViewPane implements IExplorerView {
return this.filter.filter(item, TreeVisibility.Visible);
}
+ isItemCollapsed(item: ExplorerItem): boolean {
+ return this.tree.isCollapsed(item);
+ }
+
async setEditable(stat: ExplorerItem, isEditing: boolean): Promise<void> {
if (isEditing) {
this.horizontalScrolling = this.tree.options.horizontalScrolling;
@@ -384,10 +386,10 @@ export class ExplorerView extends ViewPane implements IExplorerView {
const isCompressionEnabled = () => this.configurationService.getValue<boolean>('explorer.compactFolders');
- const getFileNestingSettings = (item?: ExplorerItem) => this.configurationService.getValue<IFilesConfiguration>({ resource: item?.root.resource }).explorer.experimental.fileNesting;
+ const getFileNestingSettings = (item?: ExplorerItem) => this.configurationService.getValue<IFilesConfiguration>({ resource: item?.root.resource }).explorer.fileNesting;
this.tree = <WorkbenchCompressibleAsyncDataTree<ExplorerItem | ExplorerItem[], ExplorerItem, FuzzyScore>>this.instantiationService.createInstance(WorkbenchCompressibleAsyncDataTree, 'FileExplorer', container, new ExplorerDelegate(), new ExplorerCompressionDelegate(), [this.renderer],
- this.instantiationService.createInstance(ExplorerDataSource), {
+ this.instantiationService.createInstance(ExplorerDataSource, this.filter), {
compressionEnabled: isCompressionEnabled(),
accessibilityProvider: this.renderer,
identityProvider,
@@ -410,7 +412,7 @@ export class ExplorerView extends ViewPane implements IExplorerView {
multipleSelectionSupport: true,
filter: this.filter,
sorter: this.instantiationService.createInstance(FileSorter),
- dnd: this.instantiationService.createInstance(FileDragAndDrop),
+ dnd: this.instantiationService.createInstance(FileDragAndDrop, (item) => this.isItemCollapsed(item)),
collapseByDefault: (e) => {
if (e instanceof ExplorerItem) {
if (e.hasNests && getFileNestingSettings(e).expand) {
@@ -625,23 +627,9 @@ export class ExplorerView extends ViewPane implements IExplorerView {
}
const toRefresh = item || this.tree.getInput();
- if (this.configurationService.getValue<IFilesConfiguration>({ resource: item?.root.resource }).explorer.experimental.fileNesting.enabled) {
- return (async () => {
- try {
- await this.tree.updateChildren(toRefresh, recursive, false, {
- diffIdentityProvider: identityProvider
- });
- } catch (e) {
- this.notificationService.error('Internal error in file explorer. This may be due to experimental file nesting.');
- console.error('Unepxected error', e, 'in refreshing explorer. This may be due to experimental file nesting.');
- return;
- }
- })();
- } else {
- return this.tree.updateChildren(toRefresh, recursive, false, {
- diffIdentityProvider: identityProvider
- });
- }
+ return this.tree.updateChildren(toRefresh, recursive, false, {
+ diffIdentityProvider: identityProvider
+ });
}
override getOptimalWidth(): number {
diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts
index f445df889f1..1181f9867c4 100644
--- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts
+++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts
@@ -75,6 +75,7 @@ export const explorerRootErrorEmitter = new Emitter<URI>();
export class ExplorerDataSource implements IAsyncDataSource<ExplorerItem | ExplorerItem[], ExplorerItem> {
constructor(
+ private fileFilter: FilesFilter,
@IProgressService private readonly progressService: IProgressService,
@IConfigurationService private readonly configService: IConfigurationService,
@INotificationService private readonly notificationService: INotificationService,
@@ -85,7 +86,8 @@ export class ExplorerDataSource implements IAsyncDataSource<ExplorerItem | Explo
) { }
hasChildren(element: ExplorerItem | ExplorerItem[]): boolean {
- return Array.isArray(element) || element.hasChildren;
+ // don't render nest parents as containing children when all the children are filtered out
+ return Array.isArray(element) || element.hasChildren((stat) => this.fileFilter.filter(stat, TreeVisibility.Visible));
}
getChildren(element: ExplorerItem | ExplorerItem[]): ExplorerItem[] | Promise<ExplorerItem[]> {
@@ -849,6 +851,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
private dropEnabled = false;
constructor(
+ private isCollapsed: (item: ExplorerItem) => boolean,
@IExplorerService private explorerService: IExplorerService,
@IEditorService private editorService: IEditorService,
@IDialogService private dialogService: IDialogService,
@@ -1082,8 +1085,8 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
const elementsData = FileDragAndDrop.getStatsFromDragAndDropData(data);
const distinctItems = new Set(elementsData);
- if (this.configurationService.getValue<IFilesConfiguration>().explorer.experimental.fileNesting.operateAsGroup) {
- for (const item of distinctItems) {
+ for (const item of distinctItems) {
+ if (this.isCollapsed(item)) {
const nestedChildren = item.nestedChildren;
if (nestedChildren) {
for (const child of nestedChildren) {
@@ -1092,6 +1095,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
}
}
}
+
const items = distinctParents([...distinctItems], s => s.resource);
const isCopy = (originalEvent.ctrlKey && !isMacintosh) || (originalEvent.altKey && isMacintosh);
diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts
index b7bc23b3257..06ba5acdbde 100644
--- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts
+++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts
@@ -59,6 +59,7 @@ const $ = dom.$;
export class OpenEditorsView extends ViewPane {
private static readonly DEFAULT_VISIBLE_OPEN_EDITORS = 9;
+ private static readonly DEFAULT_MIN_VISIBLE_OPEN_EDITORS = 0;
static readonly ID = 'workbench.explorer.openEditorsView';
static readonly NAME = nls.localize({ key: 'openEditors', comment: ['Open is an adjective'] }, "Open Editors");
@@ -466,12 +467,17 @@ export class OpenEditorsView extends ViewPane {
}
private getMaxExpandedBodySize(): number {
+ let minVisibleOpenEditors = this.configurationService.getValue<number>('explorer.openEditors.minVisible');
+ // If it's not a number setting it to 0 will result in dynamic resizing.
+ if (typeof minVisibleOpenEditors !== 'number') {
+ minVisibleOpenEditors = OpenEditorsView.DEFAULT_MIN_VISIBLE_OPEN_EDITORS;
+ }
const containerModel = this.viewDescriptorService.getViewContainerModel(this.viewDescriptorService.getViewContainerByViewId(this.id)!)!;
if (containerModel.visibleViewDescriptors.length <= 1) {
return Number.POSITIVE_INFINITY;
}
- return this.elementCount * OpenEditorsDelegate.ITEM_HEIGHT;
+ return (Math.max(this.elementCount, minVisibleOpenEditors)) * OpenEditorsDelegate.ITEM_HEIGHT;
}
private getMinExpandedBodySize(): number {
diff --git a/src/vs/workbench/contrib/files/common/explorerModel.ts b/src/vs/workbench/contrib/files/common/explorerModel.ts
index dfc1c9effc5..4b7b2c6eace 100644
--- a/src/vs/workbench/contrib/files/common/explorerModel.ts
+++ b/src/vs/workbench/contrib/files/common/explorerModel.ts
@@ -120,8 +120,12 @@ export class ExplorerItem {
this._isExcluded = value;
}
- get hasChildren() {
- return this.isDirectory || this.hasNests;
+ hasChildren(filter: (stat: ExplorerItem) => boolean): boolean {
+ if (this.hasNests) {
+ return this.nestedChildren?.some(c => filter(c)) ?? false;
+ } else {
+ return this.isDirectory;
+ }
}
get hasNests() {
@@ -298,7 +302,7 @@ export class ExplorerItem {
}
fetchChildren(sortOrder: SortOrder): ExplorerItem[] | Promise<ExplorerItem[]> {
- const nestingConfig = this.configService.getValue<IFilesConfiguration>({ resource: this.root.resource }).explorer.experimental.fileNesting;
+ const nestingConfig = this.configService.getValue<IFilesConfiguration>({ resource: this.root.resource }).explorer.fileNesting;
// fast path when the children can be resolved sync
if (nestingConfig.enabled && this.nestedChildren) {
@@ -369,12 +373,15 @@ export class ExplorerItem {
private _fileNester: ExplorerFileNestingTrie | undefined;
private get fileNester(): ExplorerFileNestingTrie {
if (!this.root._fileNester) {
- const nestingConfig = this.configService.getValue<IFilesConfiguration>({ resource: this.root.resource }).explorer.experimental.fileNesting;
+ const nestingConfig = this.configService.getValue<IFilesConfiguration>({ resource: this.root.resource }).explorer.fileNesting;
const patterns = Object.entries(nestingConfig.patterns)
.filter(entry =>
typeof (entry[0]) === 'string' && typeof (entry[1]) === 'string' && entry[0] && entry[1])
.map(([parentPattern, childrenPatterns]) =>
- [parentPattern.trim(), childrenPatterns.split(',').map(p => this.getPlatformAwareName(p.trim().replace(/\u200b/g, '')))] as [string, string[]]);
+ [
+ this.getPlatformAwareName(parentPattern.trim()),
+ childrenPatterns.split(',').map(p => this.getPlatformAwareName(p.trim().replace(/\u200b/g, '')))
+ ] as [string, string[]]);
this.root._fileNester = new ExplorerFileNestingTrie(patterns);
}
diff --git a/src/vs/workbench/contrib/files/common/files.ts b/src/vs/workbench/contrib/files/common/files.ts
index aff70bc0e8b..5f10b350de4 100644
--- a/src/vs/workbench/contrib/files/common/files.ts
+++ b/src/vs/workbench/contrib/files/common/files.ts
@@ -98,13 +98,10 @@ export interface IFilesConfiguration extends PlatformIFilesConfiguration, IWorkb
badges: boolean;
};
incrementalNaming: 'simple' | 'smart';
- experimental: {
- fileNesting: {
- enabled: boolean;
- operateAsGroup: boolean;
- expand: boolean;
- patterns: { [parent: string]: string };
- };
+ fileNesting: {
+ enabled: boolean;
+ expand: boolean;
+ patterns: { [parent: string]: string };
};
};
editor: IEditorOptions;
diff --git a/src/vs/workbench/contrib/files/electron-sandbox/fileActions.contribution.ts b/src/vs/workbench/contrib/files/electron-sandbox/fileActions.contribution.ts
index 7e5450675c1..6473ad59136 100644
--- a/src/vs/workbench/contrib/files/electron-sandbox/fileActions.contribution.ts
+++ b/src/vs/workbench/contrib/files/electron-sandbox/fileActions.contribution.ts
@@ -57,7 +57,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
}
});
-appendEditorTitleContextMenuItem(REVEAL_IN_OS_COMMAND_ID, REVEAL_IN_OS_LABEL, REVEAL_IN_OS_WHEN_CONTEXT);
+appendEditorTitleContextMenuItem(REVEAL_IN_OS_COMMAND_ID, REVEAL_IN_OS_LABEL, REVEAL_IN_OS_WHEN_CONTEXT, '2_files', 0);
// Menu registration - open editors
diff --git a/src/vs/workbench/contrib/files/electron-sandbox/textFileEditor.ts b/src/vs/workbench/contrib/files/electron-sandbox/textFileEditor.ts
index e4d1eee0b33..64978ce1742 100644
--- a/src/vs/workbench/contrib/files/electron-sandbox/textFileEditor.ts
+++ b/src/vs/workbench/contrib/files/electron-sandbox/textFileEditor.ts
@@ -62,24 +62,22 @@ export class NativeTextFileEditor extends TextFileEditor {
if ((<FileOperationError>error).fileOperationResult === FileOperationResult.FILE_EXCEEDS_MEMORY_LIMIT) {
const memoryLimit = Math.max(MIN_MAX_MEMORY_SIZE_MB, +this.textResourceConfigurationService.getValue<number>(undefined, 'files.maxMemoryForLargeFilesMB') || FALLBACK_MAX_MEMORY_SIZE_MB);
- throw createErrorWithActions(localize('fileTooLargeForHeapError', "To open a file of this size, you need to restart and allow {0} to use more memory", this.productService.nameShort), {
- actions: [
- toAction({
- id: 'workbench.window.action.relaunchWithIncreasedMemoryLimit', label: localize('relaunchWithIncreasedMemoryLimit', "Restart with {0} MB", memoryLimit), run: () => {
- return this.nativeHostService.relaunch({
- addArgs: [
- `--max-memory=${memoryLimit}`
- ]
- });
- }
- }),
- toAction({
- id: 'workbench.window.action.configureMemoryLimit', label: localize('configureMemoryLimit', 'Configure Memory Limit'), run: () => {
- return this.preferencesService.openUserSettings({ query: 'files.maxMemoryForLargeFilesMB' });
- }
- }),
- ]
- });
+ throw createErrorWithActions(localize('fileTooLargeForHeapError', "To open a file of this size, you need to restart and allow {0} to use more memory", this.productService.nameShort), [
+ toAction({
+ id: 'workbench.window.action.relaunchWithIncreasedMemoryLimit', label: localize('relaunchWithIncreasedMemoryLimit', "Restart with {0} MB", memoryLimit), run: () => {
+ return this.nativeHostService.relaunch({
+ addArgs: [
+ `--max-memory=${memoryLimit}`
+ ]
+ });
+ }
+ }),
+ toAction({
+ id: 'workbench.window.action.configureMemoryLimit', label: localize('configureMemoryLimit', 'Configure Memory Limit'), run: () => {
+ return this.preferencesService.openUserSettings({ query: 'files.maxMemoryForLargeFilesMB' });
+ }
+ }),
+ ]);
}
// Fallback to handling in super type
diff --git a/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts b/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts
index 3481452d194..aba0c2eed69 100644
--- a/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts
+++ b/src/vs/workbench/contrib/format/browser/formatActionsMultiple.ts
@@ -3,15 +3,15 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
-import { EditorAction, registerEditorAction, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
+import { getCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser';
+import { EditorAction, registerEditorAction } from 'vs/editor/browser/editorExtensions';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { DocumentFormattingEditProvider, DocumentRangeFormattingEditProvider } from 'vs/editor/common/languages';
import * as nls from 'vs/nls';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
-import { CancellationToken } from 'vs/base/common/cancellation';
-import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
+import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
+import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { formatDocumentRangesWithProvider, formatDocumentWithProvider, getRealAndSyntheticDocumentFormattersOrdered, FormattingConflicts, FormattingMode } from 'vs/editor/contrib/format/browser/format';
import { Range } from 'vs/editor/common/core/range';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
@@ -21,7 +21,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'v
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { IExtensionService, toExtension } from 'vs/workbench/services/extensions/common/extensions';
-import { Disposable } from 'vs/base/common/lifecycle';
+import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ITextModel } from 'vs/editor/common/model';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
@@ -30,6 +30,10 @@ import { IWorkbenchExtensionEnablementService } from 'vs/workbench/services/exte
import { editorConfigurationBaseNode } from 'vs/editor/common/config/editorConfigurationSchema';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
+import { ILanguageStatusService } from 'vs/workbench/services/languageStatus/common/languageStatusService';
+import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
+import { CommandsRegistry } from 'vs/platform/commands/common/commands';
+import { generateUuid } from 'vs/base/common/uuid';
type FormattingEditProvider = DocumentFormattingEditProvider | DocumentRangeFormattingEditProvider;
@@ -41,6 +45,8 @@ class DefaultFormatter extends Disposable implements IWorkbenchContribution {
static extensionItemLabels: string[] = [];
static extensionDescriptions: string[] = [];
+ private readonly _languageStatusStore = this._store.add(new DisposableStore());
+
constructor(
@IExtensionService private readonly _extensionService: IExtensionService,
@IWorkbenchExtensionEnablementService private readonly _extensionEnablementService: IWorkbenchExtensionEnablementService,
@@ -49,10 +55,17 @@ class DefaultFormatter extends Disposable implements IWorkbenchContribution {
@IDialogService private readonly _dialogService: IDialogService,
@IQuickInputService private readonly _quickInputService: IQuickInputService,
@ILanguageService private readonly _languageService: ILanguageService,
+ @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,
+ @ILanguageStatusService private readonly _languageStatusService: ILanguageStatusService,
+ @IEditorService private readonly _editorService: IEditorService,
) {
super();
- this._register(this._extensionService.onDidChangeExtensions(this._updateConfigValues, this));
- this._register(FormattingConflicts.setFormatterSelector((formatter, document, mode) => this._selectFormatter(formatter, document, mode)));
+ this._store.add(this._extensionService.onDidChangeExtensions(this._updateConfigValues, this));
+ this._store.add(FormattingConflicts.setFormatterSelector((formatter, document, mode) => this._selectFormatter(formatter, document, mode)));
+ this._store.add(_editorService.onDidActiveEditorChange(this._updateStatus, this));
+ this._store.add(_languageFeaturesService.documentFormattingEditProvider.onDidChange(this._updateStatus, this));
+ this._store.add(_languageFeaturesService.documentRangeFormattingEditProvider.onDidChange(this._updateStatus, this));
+ this._store.add(_configService.onDidChangeConfiguration(e => e.affectsConfiguration(DefaultFormatter.configName) && this._updateStatus()));
this._updateConfigValues();
}
@@ -93,8 +106,7 @@ class DefaultFormatter extends Disposable implements IWorkbenchContribution {
return s.match(/\s/) ? `'${s}'` : s;
}
- private async _selectFormatter<T extends FormattingEditProvider>(formatter: T[], document: ITextModel, mode: FormattingMode): Promise<T | undefined> {
-
+ private async _analyzeFormatter<T extends FormattingEditProvider>(formatter: T[], document: ITextModel): Promise<T | string> {
const defaultFormatterId = this._configService.getValue<string>(DefaultFormatter.configName, {
resource: document.uri,
overrideIdentifier: document.getLanguageId()
@@ -114,23 +126,9 @@ class DefaultFormatter extends Disposable implements IWorkbenchContribution {
// formatter does not target this file
const langName = this._languageService.getLanguageName(document.getLanguageId()) || document.getLanguageId();
const detail = nls.localize('miss', "Extension '{0}' is configured as formatter but it cannot format '{1}'-files", extension.displayName || extension.name, langName);
- if (mode === FormattingMode.Silent) {
- this._notificationService.status(detail, { hideAfter: 4000 });
- return undefined;
- } else {
- const result = await this._dialogService.confirm({
- message: nls.localize('miss.1', "Change Default Formatter"),
- detail,
- primaryButton: nls.localize('do.config', "Configure..."),
- secondaryButton: nls.localize('cancel', "Cancel")
- });
- if (result.confirmed) {
- return this._pickAndPersistDefaultFormatter(formatter, document);
- } else {
- return undefined;
- }
- }
+ return detail;
}
+
} else if (formatter.length === 1) {
// ok -> nothing configured but only one formatter available
return formatter[0];
@@ -138,26 +136,35 @@ class DefaultFormatter extends Disposable implements IWorkbenchContribution {
const langName = this._languageService.getLanguageName(document.getLanguageId()) || document.getLanguageId();
const message = !defaultFormatterId
- ? nls.localize('config.needed', "There are multiple formatters for '{0}' files. Select a default formatter to continue.", DefaultFormatter._maybeQuotes(langName))
+ ? nls.localize('config.needed', "There are multiple formatters for '{0}' files. One of them should be configured as default formatter.", DefaultFormatter._maybeQuotes(langName))
: nls.localize('config.bad', "Extension '{0}' is configured as formatter but not available. Select a different default formatter to continue.", defaultFormatterId);
+ return message;
+ }
+
+ private async _selectFormatter<T extends FormattingEditProvider>(formatter: T[], document: ITextModel, mode: FormattingMode): Promise<T | undefined> {
+ const formatterOrMessage = await this._analyzeFormatter(formatter, document);
+ if (typeof formatterOrMessage !== 'string') {
+ return formatterOrMessage;
+ }
+
if (mode !== FormattingMode.Silent) {
// running from a user action -> show modal dialog so that users configure
// a default formatter
const result = await this._dialogService.confirm({
- message,
+ message: nls.localize('miss.1', "Configure Default Formatter"),
+ detail: formatterOrMessage,
primaryButton: nls.localize('do.config', "Configure..."),
secondaryButton: nls.localize('cancel', "Cancel")
});
if (result.confirmed) {
return this._pickAndPersistDefaultFormatter(formatter, document);
}
-
} else {
// no user action -> show a silent notification and proceed
this._notificationService.prompt(
Severity.Info,
- message,
+ formatterOrMessage,
[{ label: nls.localize('do.config', "Configure..."), run: () => this._pickAndPersistDefaultFormatter(formatter, document) }],
{ silent: true }
);
@@ -184,6 +191,51 @@ class DefaultFormatter extends Disposable implements IWorkbenchContribution {
});
return formatter[pick.index];
}
+
+ // --- status item
+
+ private _updateStatus() {
+ this._languageStatusStore.clear();
+
+ const editor = getCodeEditor(this._editorService.activeTextEditorControl);
+ if (!editor || !editor.hasModel()) {
+ return;
+ }
+
+
+ const document = editor.getModel();
+ const formatter = getRealAndSyntheticDocumentFormattersOrdered(this._languageFeaturesService.documentFormattingEditProvider, this._languageFeaturesService.documentRangeFormattingEditProvider, document);
+
+ if (formatter.length === 0) {
+ return;
+ }
+
+ const cts = new CancellationTokenSource();
+ this._languageStatusStore.add(toDisposable(() => cts.dispose(true)));
+
+ this._analyzeFormatter(formatter, document).then(result => {
+ if (cts.token.isCancellationRequested) {
+ return;
+ }
+ if (typeof result !== 'string') {
+ return;
+ }
+ const command = { id: `formatter/configure/dfl/${generateUuid()}`, title: nls.localize('do.config', "Configure...") };
+ this._languageStatusStore.add(CommandsRegistry.registerCommand(command.id, () => this._pickAndPersistDefaultFormatter(formatter, document)));
+ this._languageStatusStore.add(this._languageStatusService.addStatus({
+ id: 'formatter.conflict',
+ name: nls.localize('summary', "Formatter Conflicts"),
+ selector: { language: document.getLanguageId(), pattern: document.uri.fsPath },
+ severity: Severity.Error,
+ label: nls.localize('formatter', "Formatting"),
+ detail: result,
+ busy: false,
+ source: '',
+ command,
+ accessibilityInfo: undefined
+ }));
+ });
+ }
}
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(
diff --git a/src/vs/workbench/contrib/keybindings/browser/keybindings.contribution.ts b/src/vs/workbench/contrib/keybindings/browser/keybindings.contribution.ts
index 6fc249b03be..5f6cdfb3298 100644
--- a/src/vs/workbench/contrib/keybindings/browser/keybindings.contribution.ts
+++ b/src/vs/workbench/contrib/keybindings/browser/keybindings.contribution.ts
@@ -9,7 +9,7 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { CATEGORIES } from 'vs/workbench/common/actions';
import { rendererLogChannelId } from 'vs/workbench/contrib/logs/common/logConstants';
-import { IOutputService } from 'vs/workbench/contrib/output/common/output';
+import { IOutputService } from 'vs/workbench/services/output/common/output';
class ToggleKeybindingsLogAction extends Action2 {
diff --git a/src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts b/src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts
new file mode 100644
index 00000000000..94f1e9af9bd
--- /dev/null
+++ b/src/vs/workbench/contrib/languageDetection/browser/languageDetection.contribution.ts
@@ -0,0 +1,148 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { DisposableStore } from 'vs/base/common/lifecycle';
+import { getCodeEditor } from 'vs/editor/browser/editorBrowser';
+import { localize } from 'vs/nls';
+import { Registry } from 'vs/platform/registry/common/platform';
+import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
+import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
+import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
+import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/browser/statusbar';
+import { ILanguageDetectionService } from 'vs/workbench/services/languageDetection/common/languageDetectionWorkerService';
+import { ThrottledDelayer } from 'vs/base/common/async';
+import { ILanguageService } from 'vs/editor/common/languages/language';
+import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
+import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
+import { registerAction2, Action2 } from 'vs/platform/actions/common/actions';
+import { INotificationService } from 'vs/platform/notification/common/notification';
+import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
+import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
+import { NOTEBOOK_EDITOR_EDITABLE } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
+import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
+import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
+import { Schemas } from 'vs/base/common/network';
+import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
+
+const detectLanguageCommandId = 'editor.detectLanguage';
+
+class LanguageDetectionStatusContribution implements IWorkbenchContribution {
+
+ private static readonly _id = 'status.languageDetectionStatus';
+
+ private readonly _disposables = new DisposableStore();
+ private _combinedEntry?: IStatusbarEntryAccessor;
+ private _delayer = new ThrottledDelayer(1000);
+ private _renderDisposables = new DisposableStore();
+
+ constructor(
+ @ILanguageDetectionService private readonly _languageDetectionService: ILanguageDetectionService,
+ @IStatusbarService private readonly _statusBarService: IStatusbarService,
+ @IConfigurationService private readonly _configurationService: IConfigurationService,
+ @IEditorService private readonly _editorService: IEditorService,
+ @ILanguageService private readonly _languageService: ILanguageService,
+ @IKeybindingService private readonly _keybindingService: IKeybindingService,
+ ) {
+ _editorService.onDidActiveEditorChange(() => this._update(true), this, this._disposables);
+ this._update(false);
+ }
+
+ dispose(): void {
+ this._disposables.dispose();
+ this._delayer.dispose();
+ this._combinedEntry?.dispose();
+ this._renderDisposables.dispose();
+ }
+
+ private _update(clear: boolean): void {
+ if (clear) {
+ this._combinedEntry?.dispose();
+ this._combinedEntry = undefined;
+ }
+ this._delayer.trigger(() => this._doUpdate());
+ }
+
+ private async _doUpdate(): Promise<void> {
+ const editor = getCodeEditor(this._editorService.activeTextEditorControl);
+
+ this._renderDisposables.clear();
+
+ // update when editor language changes
+ editor?.onDidChangeModelLanguage(() => this._update(true), this, this._renderDisposables);
+ editor?.onDidChangeModelContent(() => this._update(false), this, this._renderDisposables);
+ const editorModel = editor?.getModel();
+ const editorUri = editorModel?.uri;
+ const existingId = editorModel?.getLanguageId();
+ const enablementConfig = this._configurationService.getValue('workbench.editor.languageDetectionHints');
+ const enabled = enablementConfig === 'always' || enablementConfig === 'textEditors';
+ const disableLightbulb = !enabled || editorUri?.scheme !== Schemas.untitled || !existingId;
+
+ if (disableLightbulb || !editorUri) {
+ this._combinedEntry?.dispose();
+ this._combinedEntry = undefined;
+ } else {
+ const lang = await this._languageDetectionService.detectLanguage(editorUri);
+ const skip: Record<string, string | undefined> = { 'jsonc': 'json' };
+ const existing = editorModel.getLanguageId();
+ if (lang && lang !== existing && skip[existing] !== lang) {
+ const detectedName = this._languageService.getLanguageName(lang) || lang;
+ let tooltip = localize('status.autoDetectLanguage', "Accept Detected Language: {0}", detectedName);
+ const keybinding = this._keybindingService.lookupKeybinding(detectLanguageCommandId);
+ const label = keybinding?.getLabel();
+ if (label) {
+ tooltip += ` (${label})`;
+ }
+
+ const props: IStatusbarEntry = {
+ name: localize('langDetection.name', "Language Detection"),
+ ariaLabel: localize('langDetection.aria', "Change to Detected Language: {0}", lang),
+ tooltip,
+ command: detectLanguageCommandId,
+ text: '$(lightbulb-autofix)',
+ };
+ if (!this._combinedEntry) {
+ this._combinedEntry = this._statusBarService.addEntry(props, LanguageDetectionStatusContribution._id, StatusbarAlignment.RIGHT, { id: 'status.editor.mode', alignment: StatusbarAlignment.RIGHT, compact: true });
+ } else {
+ this._combinedEntry.update(props);
+ }
+ } else {
+ this._combinedEntry?.dispose();
+ this._combinedEntry = undefined;
+ }
+ }
+ }
+}
+
+Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(LanguageDetectionStatusContribution, LifecyclePhase.Restored);
+
+
+registerAction2(class extends Action2 {
+
+ constructor() {
+ super({
+ id: detectLanguageCommandId,
+ title: localize('detectlang', '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 }
+ });
+ }
+
+ async run(accessor: ServicesAccessor): Promise<void> {
+ const editorService = accessor.get(IEditorService);
+ const languageDetectionService = accessor.get(ILanguageDetectionService);
+ const editor = getCodeEditor(editorService.activeTextEditorControl);
+ const notificationService = accessor.get(INotificationService);
+ const editorUri = editor?.getModel()?.uri;
+ if (editorUri) {
+ const lang = await languageDetectionService.detectLanguage(editorUri);
+ if (lang) {
+ editor.getModel()?.setMode(lang);
+ } else {
+ notificationService.warn(localize('noDetection', "Unable to detect editor language"));
+ }
+ }
+ }
+});
diff --git a/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts b/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts
index d6d4689d90e..0372c278593 100644
--- a/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts
+++ b/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts
@@ -6,7 +6,7 @@
import 'vs/css!./media/languageStatus';
import * as dom from 'vs/base/browser/dom';
import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';
-import { DisposableStore, dispose } from 'vs/base/common/lifecycle';
+import { DisposableStore, dispose, toDisposable } from 'vs/base/common/lifecycle';
import Severity from 'vs/base/common/severity';
import { getCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { localize } from 'vs/nls';
@@ -28,6 +28,8 @@ import { Codicon } from 'vs/base/common/codicons';
import { IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { equals } from 'vs/base/common/arrays';
import { URI } from 'vs/base/common/uri';
+import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
+import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
class LanguageStatusViewModel {
@@ -202,16 +204,21 @@ class EditorStatusContribution implements IWorkbenchContribution {
// animate the status bar icon whenever language status changes, repeat animation
// when severity is warning or error, don't show animation when showing progress/busy
const userHasInteractedWithStatus = this._interactionCounter.value >= 3;
- const node = document.querySelector('.monaco-workbench .statusbar DIV#status\\.languageStatus span.codicon');
- if (node instanceof HTMLElement) {
+ const node = document.querySelector('.monaco-workbench .statusbar DIV#status\\.languageStatus A>SPAN.codicon');
+ const container = document.querySelector('.monaco-workbench .statusbar DIV#status\\.languageStatus');
+ if (node instanceof HTMLElement && container) {
const _wiggle = 'wiggle';
- const _repeat = 'repeat';
+ const _flash = 'flash';
if (!isOneBusy) {
+ // wiggle icon when severe or "new"
node.classList.toggle(_wiggle, showSeverity || !userHasInteractedWithStatus);
- node.classList.toggle(_repeat, showSeverity);
- this._renderDisposables.add(dom.addDisposableListener(node, 'animationend', _e => node.classList.remove(_wiggle, _repeat)));
+ this._renderDisposables.add(dom.addDisposableListener(node, 'animationend', _e => node.classList.remove(_wiggle)));
+ // flash background when severe
+ container.classList.toggle(_flash, showSeverity);
+ this._renderDisposables.add(dom.addDisposableListener(container, 'animationend', _e => container.classList.remove(_flash)));
} else {
- node.classList.remove(_wiggle, _repeat);
+ node.classList.remove(_wiggle);
+ container.classList.remove(_flash);
}
}
@@ -226,7 +233,8 @@ class EditorStatusContribution implements IWorkbenchContribution {
observer.disconnect();
}
});
- observer.observe(document.body, { childList: true, subtree: true });
+ observer.observe(hoverTarget, { childList: true, subtree: true });
+ this._renderDisposables.add(toDisposable(() => observer.disconnect()));
}
}
}
@@ -384,3 +392,19 @@ class EditorStatusContribution implements IWorkbenchContribution {
}
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(EditorStatusContribution, LifecyclePhase.Restored);
+
+registerAction2(class extends Action2 {
+
+ constructor() {
+ super({
+ id: 'editor.inlayHints.Reset',
+ title: localize('reset', 'Reset Language Status Interaction Counter'),
+ category: localize('cat', 'View'),
+ f1: true
+ });
+ }
+
+ run(accessor: ServicesAccessor): void {
+ accessor.get(IStorageService).remove('languageStatus.interactCount', StorageScope.GLOBAL);
+ }
+});
diff --git a/src/vs/workbench/contrib/languageStatus/browser/media/languageStatus.css b/src/vs/workbench/contrib/languageStatus/browser/media/languageStatus.css
index 131695f460a..1fc68790f97 100644
--- a/src/vs/workbench/contrib/languageStatus/browser/media/languageStatus.css
+++ b/src/vs/workbench/contrib/languageStatus/browser/media/languageStatus.css
@@ -3,37 +3,56 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
+/* status bar animation */
@keyframes wiggle {
0% {
- transform: rotate(0) scale(1)
+ transform: rotate(0) scale(1);
}
15%,
45% {
- transform: rotate(.04turn) scale(1.1)
+ transform: rotate(.04turn) scale(1.1);
}
30%,
60% {
- transform: rotate(-.04turn) scale(1.2)
+ transform: rotate(-.04turn) scale(1.2);
}
100% {
- transform: rotate(0) scale(1)
+ transform: rotate(0) scale(1);
}
}
-.monaco-workbench.enable-motion .statusbar DIV#status\.languageStatus span.codicon.wiggle {
+.monaco-workbench .statusbar DIV#status\.languageStatus A>SPAN.codicon.wiggle {
animation-duration: .8s;
animation-iteration-count: 1;
animation-name: wiggle;
}
-.monaco-workbench.enable-motion .statusbar DIV#status\.languageStatus span.codicon.wiggle.repeat {
- animation-iteration-count: 3;
+@keyframes flash {
+ 0% {
+ background-color: initial;
+ }
+
+ 50% {
+ background-color: var(--vscode-statusBarItem-prominentBackground);
+ }
+
+ 100% {
+ background-color: initial;
+ }
+}
+
+.monaco-workbench .statusbar DIV#status\.languageStatus.flash A {
+ animation-duration: .8s;
+ animation-iteration-count: 1;
+ animation-name: flash;
}
+/* --- hover */
+
.monaco-workbench .hover-language-status {
display: flex;
padding: 4px 8px;
diff --git a/src/vs/workbench/contrib/localHistory/browser/localHistory.ts b/src/vs/workbench/contrib/localHistory/browser/localHistory.ts
index c182f0ccfd1..20bd201237b 100644
--- a/src/vs/workbench/contrib/localHistory/browser/localHistory.ts
+++ b/src/vs/workbench/contrib/localHistory/browser/localHistory.ts
@@ -8,8 +8,22 @@ import { Codicon } from 'vs/base/common/codicons';
import { language } from 'vs/base/common/platform';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
+import { IdleValue } from 'vs/base/common/async';
-export const LOCAL_HISTORY_DATE_FORMATTER = new Intl.DateTimeFormat(language, { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' });
+export const LOCAL_HISTORY_DATE_FORMATTER: IdleValue<{ format: (timestamp: number) => string }> = new IdleValue(() => {
+ const options: Intl.DateTimeFormatOptions = { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' };
+
+ let formatter: Intl.DateTimeFormat;
+ try {
+ formatter = new Intl.DateTimeFormat(language, options);
+ } catch (error) {
+ formatter = new Intl.DateTimeFormat(undefined, options); // error can happen when language is invalid (https://github.com/microsoft/vscode/issues/147086)
+ }
+
+ return {
+ format: date => formatter.format(date)
+ };
+});
export const LOCAL_HISTORY_MENU_CONTEXT_VALUE = 'localHistory:item';
export const LOCAL_HISTORY_MENU_CONTEXT_KEY = ContextKeyExpr.equals('timelineItem', LOCAL_HISTORY_MENU_CONTEXT_VALUE);
diff --git a/src/vs/workbench/contrib/localHistory/browser/localHistoryCommands.ts b/src/vs/workbench/contrib/localHistory/browser/localHistoryCommands.ts
index 0b2a709a069..49a5a67e8e7 100644
--- a/src/vs/workbench/contrib/localHistory/browser/localHistoryCommands.ts
+++ b/src/vs/workbench/contrib/localHistory/browser/localHistoryCommands.ts
@@ -632,7 +632,7 @@ export async function findLocalHistoryEntry(workingCopyHistoryService: IWorkingC
const SEP = /\//g;
function toLocalHistoryEntryDateLabel(timestamp: number): string {
- return `${LOCAL_HISTORY_DATE_FORMATTER.format(timestamp).replace(SEP, '-')}`; // preserving `/` will break editor labels, so replace it with a non-path symbol
+ return `${LOCAL_HISTORY_DATE_FORMATTER.value.format(timestamp).replace(SEP, '-')}`; // preserving `/` will break editor labels, so replace it with a non-path symbol
}
//#endregion
diff --git a/src/vs/workbench/contrib/localHistory/browser/localHistoryTimeline.ts b/src/vs/workbench/contrib/localHistory/browser/localHistoryTimeline.ts
index ca34fae943c..2dee3866cbd 100644
--- a/src/vs/workbench/contrib/localHistory/browser/localHistoryTimeline.ts
+++ b/src/vs/workbench/contrib/localHistory/browser/localHistoryTimeline.ts
@@ -8,7 +8,7 @@ import { Emitter } from 'vs/base/common/event';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
-import { InternalTimelineOptions, ITimelineService, Timeline, TimelineChangeEvent, TimelineItem, TimelineOptions, TimelineProvider } from 'vs/workbench/contrib/timeline/common/timeline';
+import { ITimelineService, Timeline, TimelineChangeEvent, TimelineItem, TimelineOptions, TimelineProvider } from 'vs/workbench/contrib/timeline/common/timeline';
import { IWorkingCopyHistoryEntry, IWorkingCopyHistoryService } from 'vs/workbench/services/workingCopy/common/workingCopyHistory';
import { URI } from 'vs/base/common/uri';
import { IPathService } from 'vs/workbench/services/path/common/pathService';
@@ -102,7 +102,7 @@ export class LocalHistoryTimeline extends Disposable implements IWorkbenchContri
});
}
- async provideTimeline(uri: URI, options: TimelineOptions, token: CancellationToken, internalOptions?: InternalTimelineOptions): Promise<Timeline> {
+ async provideTimeline(uri: URI, options: TimelineOptions, token: CancellationToken): Promise<Timeline> {
const items: TimelineItem[] = [];
// Try to convert the provided `uri` into a form that is likely
@@ -151,7 +151,7 @@ export class LocalHistoryTimeline extends Disposable implements IWorkbenchContri
return {
handle: entry.id,
label: SaveSourceRegistry.getSourceLabel(entry.source),
- tooltip: new MarkdownString(`$(history) ${LOCAL_HISTORY_DATE_FORMATTER.format(entry.timestamp)}\n\n${SaveSourceRegistry.getSourceLabel(entry.source)}`, { supportThemeIcons: true }),
+ tooltip: new MarkdownString(`$(history) ${LOCAL_HISTORY_DATE_FORMATTER.value.format(entry.timestamp)}\n\n${SaveSourceRegistry.getSourceLabel(entry.source)}`, { supportThemeIcons: true }),
source: LocalHistoryTimeline.ID,
timestamp: entry.timestamp,
themeIcon: LOCAL_HISTORY_ICON_ENTRY,
diff --git a/src/vs/workbench/contrib/logs/browser/logs.contribution.ts b/src/vs/workbench/contrib/logs/browser/logs.contribution.ts
new file mode 100644
index 00000000000..d78954e856e
--- /dev/null
+++ b/src/vs/workbench/contrib/logs/browser/logs.contribution.ts
@@ -0,0 +1,34 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { Registry } from 'vs/platform/registry/common/platform';
+import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions, CATEGORIES } from 'vs/workbench/common/actions';
+import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
+import { OpenWindowSessionLogFileAction } from 'vs/workbench/contrib/logs/common/logsActions';
+import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
+import { Disposable } from 'vs/base/common/lifecycle';
+import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
+import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
+import { LogsDataCleaner } from 'vs/workbench/contrib/logs/common/logsDataCleaner';
+
+class WebLogOutputChannels extends Disposable implements IWorkbenchContribution {
+
+ constructor(
+ @IInstantiationService private readonly instantiationService: IInstantiationService,
+ ) {
+ super();
+ this.registerWebContributions();
+ }
+
+ private registerWebContributions(): void {
+ this.instantiationService.createInstance(LogsDataCleaner);
+
+ const workbenchActionsRegistry = Registry.as<IWorkbenchActionRegistry>(WorkbenchActionExtensions.WorkbenchActions);
+ workbenchActionsRegistry.registerWorkbenchAction(SyncActionDescriptor.from(OpenWindowSessionLogFileAction), 'Developer: Open Window Log File (Session)...', CATEGORIES.Developer.value);
+ }
+
+}
+
+Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(WebLogOutputChannels, LifecyclePhase.Restored);
diff --git a/src/vs/workbench/contrib/logs/common/logs.contribution.ts b/src/vs/workbench/contrib/logs/common/logs.contribution.ts
index 6cbfdc4cd7b..5e533149305 100644
--- a/src/vs/workbench/contrib/logs/common/logs.contribution.ts
+++ b/src/vs/workbench/contrib/logs/common/logs.contribution.ts
@@ -4,29 +4,22 @@
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
-import { join } from 'vs/base/common/path';
import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions, CATEGORIES } from 'vs/workbench/common/actions';
import { Action2, registerAction2, SyncActionDescriptor } from 'vs/platform/actions/common/actions';
-import { SetLogLevelAction, OpenWindowSessionLogFileAction } from 'vs/workbench/contrib/logs/common/logsActions';
+import { SetLogLevelAction } from 'vs/workbench/contrib/logs/common/logsActions';
import * as Constants from 'vs/workbench/contrib/logs/common/logConstants';
import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
-import { IFileService, whenProviderRegistered } from 'vs/platform/files/common/files';
-import { URI } from 'vs/base/common/uri';
-import { IOutputChannelRegistry, Extensions as OutputExt } from 'vs/workbench/services/output/common/output';
+import { IFileService } from 'vs/platform/files/common/files';
+import { IOutputService, registerLogChannel } from 'vs/workbench/services/output/common/output';
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
import { ILogService, LogLevel } from 'vs/platform/log/common/log';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
-import { isWeb } from 'vs/base/common/platform';
-import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
-import { LogsDataCleaner } from 'vs/workbench/contrib/logs/common/logsDataCleaner';
-import { IOutputService } from 'vs/workbench/contrib/output/common/output';
+import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { supportsTelemetry } from 'vs/platform/telemetry/common/telemetryUtils';
import { IProductService } from 'vs/platform/product/common/productService';
-import { createCancelablePromise, timeout } from 'vs/base/common/async';
-import { canceled, getErrorMessage, isCancellationError } from 'vs/base/common/errors';
-import { CancellationToken } from 'vs/base/common/cancellation';
+import { URI } from 'vs/base/common/uri';
const workbenchActionsRegistry = Registry.as<IWorkbenchActionRegistry>(WorkbenchActionExtensions.WorkbenchActions);
workbenchActionsRegistry.registerWorkbenchAction(SyncActionDescriptor.from(SetLogLevelAction), 'Developer: Set Log Level...', CATEGORIES.Developer.value);
@@ -38,15 +31,9 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution {
@IProductService private readonly productService: IProductService,
@ILogService private readonly logService: ILogService,
@IFileService private readonly fileService: IFileService,
- @IInstantiationService private readonly instantiationService: IInstantiationService,
) {
super();
this.registerCommonContributions();
- if (isWeb) {
- this.registerWebContributions();
- } else {
- this.registerNativeContributions();
- }
}
private registerCommonContributions(): void {
@@ -84,47 +71,9 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution {
});
}
- private registerWebContributions(): void {
- this.instantiationService.createInstance(LogsDataCleaner);
-
- const workbenchActionsRegistry = Registry.as<IWorkbenchActionRegistry>(WorkbenchActionExtensions.WorkbenchActions);
- workbenchActionsRegistry.registerWorkbenchAction(SyncActionDescriptor.from(OpenWindowSessionLogFileAction), 'Developer: Open Window Log File (Session)...', CATEGORIES.Developer.value);
- }
-
- private registerNativeContributions(): void {
- this.registerLogChannel(Constants.mainLogChannelId, nls.localize('mainLog', "Main"), URI.file(join(this.environmentService.logsPath, `main.log`)));
- this.registerLogChannel(Constants.sharedLogChannelId, nls.localize('sharedLog', "Shared"), URI.file(join(this.environmentService.logsPath, `sharedprocess.log`)));
- }
-
- private async registerLogChannel(id: string, label: string, file: URI): Promise<void> {
- await whenProviderRegistered(file, this.fileService);
- const outputChannelRegistry = Registry.as<IOutputChannelRegistry>(OutputExt.OutputChannels);
- try {
- const promise = createCancelablePromise(token => this.whenFileExists(file, 1, token));
- this._register(toDisposable(() => promise.cancel()));
- await promise;
- outputChannelRegistry.registerChannel({ id, label, file, log: true });
- } catch (error) {
- if (!isCancellationError(error)) {
- this.logService.error('Error while registering log channel', file.toString(), getErrorMessage(error));
- }
- }
- }
-
- private async whenFileExists(file: URI, trial: number, token: CancellationToken): Promise<void> {
- const exists = await this.fileService.exists(file);
- if (exists) {
- return;
- }
- if (token.isCancellationRequested) {
- throw canceled();
- }
- if (trial > 10) {
- throw new Error(`Timed out while waiting for file to be created`);
- }
- this.logService.debug(`[Registering Log Channel] File does not exist. Waiting for 1s to retry.`, file.toString());
- await timeout(1000, token);
- await this.whenFileExists(file, trial + 1, token);
+ private registerLogChannel(id: string, label: string, file: URI): void {
+ const promise = registerLogChannel(id, label, file, this.fileService, this.logService);
+ this._register(toDisposable(() => promise.cancel()));
}
}
diff --git a/src/vs/workbench/contrib/logs/electron-sandbox/logs.contribution.ts b/src/vs/workbench/contrib/logs/electron-sandbox/logs.contribution.ts
index 34b8d2ff3fa..022c66402f2 100644
--- a/src/vs/workbench/contrib/logs/electron-sandbox/logs.contribution.ts
+++ b/src/vs/workbench/contrib/logs/electron-sandbox/logs.contribution.ts
@@ -3,10 +3,46 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
+import * as nls from 'vs/nls';
import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions, CATEGORIES } from 'vs/workbench/common/actions';
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { OpenLogsFolderAction, OpenExtensionLogsFolderAction } from 'vs/workbench/contrib/logs/electron-sandbox/logsActions';
+import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
+import * as Constants from 'vs/workbench/contrib/logs/common/logConstants';
+import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
+import { IFileService } from 'vs/platform/files/common/files';
+import { ILogService } from 'vs/platform/log/common/log';
+import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
+import { URI } from 'vs/base/common/uri';
+import { join } from 'vs/base/common/path';
+import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
+import { registerLogChannel } from 'vs/workbench/services/output/common/output';
+
+class NativeLogOutputChannels extends Disposable implements IWorkbenchContribution {
+
+ constructor(
+ @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
+ @ILogService private readonly logService: ILogService,
+ @IFileService private readonly fileService: IFileService,
+ ) {
+ super();
+ this.registerNativeContributions();
+ }
+
+ private registerNativeContributions(): void {
+ this.registerLogChannel(Constants.mainLogChannelId, nls.localize('mainLog', "Main"), URI.file(join(this.environmentService.logsPath, `main.log`)));
+ this.registerLogChannel(Constants.sharedLogChannelId, nls.localize('sharedLog', "Shared"), URI.file(join(this.environmentService.logsPath, `sharedprocess.log`)));
+ }
+
+ private registerLogChannel(id: string, label: string, file: URI): void {
+ const promise = registerLogChannel(id, label, file, this.fileService, this.logService);
+ this._register(toDisposable(() => promise.cancel()));
+ }
+
+}
+
+Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(NativeLogOutputChannels, LifecyclePhase.Restored);
const workbenchActionsRegistry = Registry.as<IWorkbenchActionRegistry>(WorkbenchActionExtensions.WorkbenchActions);
workbenchActionsRegistry.registerWorkbenchAction(SyncActionDescriptor.from(OpenLogsFolderAction), 'Developer: Open Logs Folder', CATEGORIES.Developer.value);
diff --git a/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts b/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts
index c47ff0b58ff..68185694831 100644
--- a/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts
+++ b/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts
@@ -121,7 +121,7 @@ code > div {
}
.vscode-high-contrast code > div {
- background-color: rgb(0, 0, 0);
+ background-color: var(--vscode-textCodeBlock-background);
}
.vscode-high-contrast h1 {
diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/statusBarProviders.ts b/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/statusBarProviders.ts
index f1834e4ee96..750af12fdf4 100644
--- a/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/statusBarProviders.ts
+++ b/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/statusBarProviders.ts
@@ -5,16 +5,21 @@
import { CancellationToken } from 'vs/base/common/cancellation';
import { Disposable } from 'vs/base/common/lifecycle';
+import { ResourceMap } from 'vs/base/common/map';
import { URI } from 'vs/base/common/uri';
import { ILanguageService } from 'vs/editor/common/languages/language';
import { localize } from 'vs/nls';
+import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
+import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { Registry } from 'vs/platform/registry/common/platform';
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
-import { CHANGE_CELL_LANGUAGE } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
+import { CHANGE_CELL_LANGUAGE, DETECT_CELL_LANGUAGE } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService';
import { CellKind, CellStatusbarAlignment, INotebookCellStatusBarItem, INotebookCellStatusBarItemList, INotebookCellStatusBarItemProvider } from 'vs/workbench/contrib/notebook/common/notebookCommon';
+import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
+import { ILanguageDetectionService } from 'vs/workbench/services/languageDetection/common/languageDetectionWorkerService';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
class CellStatusBarLanguagePickerProvider implements INotebookCellStatusBarItemProvider {
@@ -50,6 +55,80 @@ class CellStatusBarLanguagePickerProvider implements INotebookCellStatusBarItemP
}
}
+class CellStatusBarLanguageDetectionProvider implements INotebookCellStatusBarItemProvider {
+
+ readonly viewType = '*';
+
+ private cache = new ResourceMap<{
+ lastUpdate: number;
+ lastCellLang: string;
+ lastGuess?: string;
+ }>();
+
+ constructor(
+ @INotebookService private readonly _notebookService: INotebookService,
+ @INotebookKernelService private readonly _notebookKernelService: INotebookKernelService,
+ @ILanguageService private readonly _languageService: ILanguageService,
+ @IConfigurationService private readonly _configurationService: IConfigurationService,
+ @ILanguageDetectionService private readonly _languageDetectionService: ILanguageDetectionService,
+ @IKeybindingService private readonly _keybindingService: IKeybindingService,
+ ) { }
+
+ async provideCellStatusBarItems(uri: URI, index: number, token: CancellationToken): Promise<INotebookCellStatusBarItemList | undefined> {
+ const doc = this._notebookService.getNotebookTextModel(uri);
+ const cell = doc?.cells[index];
+ if (!cell) { return; }
+
+ const enablementConfig = this._configurationService.getValue('workbench.editor.languageDetectionHints');
+ const enabled = enablementConfig === 'always' || enablementConfig === 'notebookEditors';
+ if (!enabled) {
+ return;
+ }
+
+ const currentLanguageId = cell.cellKind === CellKind.Markup ?
+ 'markdown' :
+ (this._languageService.getLanguageIdByLanguageName(cell.language) || cell.language);
+
+ if (!this.cache.has(uri)) {
+ this.cache.set(uri, { lastCellLang: currentLanguageId, lastUpdate: 0 });
+ }
+
+ const cached = this.cache.get(uri)!;
+ if (cached.lastUpdate < Date.now() - 1000 || cached.lastCellLang !== currentLanguageId) {
+ cached.lastUpdate = Date.now();
+ cached.lastCellLang = currentLanguageId;
+
+ const kernel = this._notebookKernelService.getSelectedOrSuggestedKernel(doc);
+
+ if (kernel) {
+ const availableLangs = [];
+ availableLangs.push(...kernel.supportedLanguages, 'markdown');
+ cached.lastGuess = await this._languageDetectionService.detectLanguage(cell.uri, availableLangs);
+ }
+ }
+
+ const items: INotebookCellStatusBarItem[] = [];
+ if (cached.lastGuess && currentLanguageId !== cached.lastGuess) {
+ const detectedName = this._languageService.getLanguageName(cached.lastGuess) || cached.lastGuess;
+ let tooltip = localize('notebook.cell.status.autoDetectLanguage', "Accept Detected Language: {0}", detectedName);
+ const keybinding = this._keybindingService.lookupKeybinding(DETECT_CELL_LANGUAGE);
+ const label = keybinding?.getLabel();
+ if (label) {
+ tooltip += ` (${label})`;
+ }
+ items.push({
+ text: '$(lightbulb-autofix)',
+ command: DETECT_CELL_LANGUAGE,
+ tooltip,
+ alignment: CellStatusbarAlignment.Right,
+ priority: -Number.MAX_SAFE_INTEGER + 1
+ });
+ }
+
+ return { items };
+ }
+}
+
class BuiltinCellStatusBarProviders extends Disposable {
constructor(
@IInstantiationService instantiationService: IInstantiationService,
@@ -58,6 +137,7 @@ class BuiltinCellStatusBarProviders extends Disposable {
const builtinProviders = [
CellStatusBarLanguagePickerProvider,
+ CellStatusBarLanguageDetectionProvider,
];
builtinProviders.forEach(p => {
this._register(notebookCellStatusBarService.registerCellStatusBarItemProvider(instantiationService.createInstance(p)));
diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts b/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts
index 10037c96943..8fbc0999419 100644
--- a/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts
+++ b/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts
@@ -27,7 +27,7 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation
import { RedoCommand, UndoCommand } from 'vs/editor/browser/editorExtensions';
import { IWebview } from 'vs/workbench/contrib/webview/browser/webview';
import { CATEGORIES } from 'vs/workbench/common/actions';
-import { IOutputService } from 'vs/workbench/contrib/output/common/output';
+import { IOutputService } from 'vs/workbench/services/output/common/output';
import { rendererLogChannelId } from 'vs/workbench/contrib/logs/common/logConstants';
import { ILogService } from 'vs/platform/log/common/log';
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 cb7ab0f8586..920669194f6 100644
--- a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts
+++ b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts
@@ -28,7 +28,7 @@ import { NotebookEditorWidget } from 'vs/workbench/contrib/notebook/browser/note
import { configureKernelIcon, selectKernelIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
-import { INotebookKernel, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
+import { INotebookKernel, INotebookKernelService, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite';
@@ -38,7 +38,7 @@ import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeat
registerAction2(class extends Action2 {
constructor() {
super({
- id: '_notebook.selectKernel',
+ id: SELECT_KERNEL_ID,
category: NOTEBOOK_ACTIONS_CATEGORY,
title: { value: nls.localize('notebookActions.selectKernel', "Select Notebook Kernel"), original: 'Select Notebook Kernel' },
// precondition: NOTEBOOK_IS_ACTIVE_EDITOR,
@@ -182,12 +182,7 @@ registerAction2(class extends Action2 {
return res;
}
const quickPickItems: QuickPickInput<IQuickPickItem | KernelPick>[] = [];
- if (!all.length) {
- quickPickItems.push({
- id: 'install',
- label: nls.localize('installKernels', "Install kernels from the marketplace"),
- });
- } else {
+ if (all.length) {
// Always display suggested kernels on the top.
if (suggestions.length) {
quickPickItems.push({
@@ -210,6 +205,14 @@ registerAction2(class extends Action2 {
});
}
+ if (!all.find(item => item.type === NotebookKernelType.Resolved)) {
+ // there is no resolved kernel, show the install from marketplace
+ quickPickItems.push({
+ id: 'install',
+ label: nls.localize('installKernels', "Install kernels from the marketplace"),
+ });
+ }
+
const pick = await quickInputService.pick(quickPickItems, {
placeHolder: selected
? nls.localize('prompt.placeholder.change', "Change kernel for '{0}'", labelService.getUriLabel(notebook.uri, { relative: true }))
@@ -381,7 +384,7 @@ export class KernelStatus extends Disposable implements IWorkbenchContribution {
tooltip: isSuggested ? nls.localize('tooltop', "{0} (suggestion)", tooltip) : tooltip,
command: SELECT_KERNEL_ID,
},
- '_notebook.selectKernel',
+ SELECT_KERNEL_ID,
StatusbarAlignment.RIGHT,
10
));
@@ -399,7 +402,7 @@ export class KernelStatus extends Disposable implements IWorkbenchContribution {
command: SELECT_KERNEL_ID,
backgroundColor: { id: 'statusBarItem.prominentBackground' }
},
- '_notebook.selectKernel',
+ SELECT_KERNEL_ID,
StatusbarAlignment.RIGHT,
10
));
diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts
index f1b0150286c..2f5d35ba93f 100644
--- a/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts
+++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts
@@ -88,7 +88,7 @@ export class FindModel extends Disposable {
};
}
- find(previous: boolean) {
+ find(option: { previous: boolean } | { index: number }) {
if (!this.findMatches.length) {
return;
}
@@ -96,14 +96,20 @@ export class FindModel extends Disposable {
// let currCell;
if (!this._findMatchesStarts) {
this.set(this._findMatches, true);
+ if ('index' in option) {
+ this._currentMatch = option.index;
+ }
} else {
// const currIndex = this._findMatchesStarts!.getIndexOf(this._currentMatch);
// currCell = this._findMatches[currIndex.index].cell;
const totalVal = this._findMatchesStarts.getTotalSum();
- if (this._currentMatch === -1) {
- this._currentMatch = previous ? totalVal - 1 : 0;
+ if ('index' in option) {
+ this._currentMatch = option.index;
+ }
+ else if (this._currentMatch === -1) {
+ this._currentMatch = option.previous ? totalVal - 1 : 0;
} else {
- const nextVal = (this._currentMatch + (previous ? -1 : 1) + totalVal) % totalVal;
+ const nextVal = (this._currentMatch + (option.previous ? -1 : 1) + totalVal) % totalVal;
this._currentMatch = nextVal;
}
}
@@ -157,8 +163,8 @@ export class FindModel extends Disposable {
}
async research() {
- this._throttledDelayer.trigger(() => {
- this._research();
+ return this._throttledDelayer.trigger(() => {
+ return this._research();
});
}
diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFind.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFind.ts
new file mode 100644
index 00000000000..f1ff2386aa7
--- /dev/null
+++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFind.ts
@@ -0,0 +1,135 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import 'vs/css!./media/notebookFind';
+import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
+import { Schemas } from 'vs/base/common/network';
+import { isEqual } from 'vs/base/common/resources';
+import { URI } from 'vs/base/common/uri';
+import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
+import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
+import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
+import { ITextModel } from 'vs/editor/common/model';
+import { StartFindAction, StartFindReplaceAction } from 'vs/editor/contrib/find/browser/findController';
+import { localize } from 'vs/nls';
+import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
+import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
+import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
+import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
+import { NotebookFindWidget } from 'vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget';
+import { getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
+import { registerNotebookContribution } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions';
+import { CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon';
+import { KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_IS_ACTIVE_EDITOR } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
+import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
+
+registerNotebookContribution(NotebookFindWidget.id, NotebookFindWidget);
+
+registerAction2(class extends Action2 {
+ constructor() {
+ super({
+ id: 'notebook.hideFind',
+ title: { value: localize('notebookActions.hideFind', "Hide Find in Notebook"), original: 'Hide Find in Notebook' },
+ keybinding: {
+ when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED),
+ primary: KeyCode.Escape,
+ weight: KeybindingWeight.WorkbenchContrib
+ }
+ });
+ }
+
+ async run(accessor: ServicesAccessor): Promise<void> {
+ const editorService = accessor.get(IEditorService);
+ const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane);
+
+ if (!editor) {
+ return;
+ }
+
+ const controller = editor.getContribution<NotebookFindWidget>(NotebookFindWidget.id);
+ controller.hide();
+ editor.focus();
+ }
+});
+
+registerAction2(class extends Action2 {
+ constructor() {
+ super({
+ id: 'notebook.find',
+ title: { value: localize('notebookActions.findInNotebook', "Find in Notebook"), original: 'Find in Notebook' },
+ keybinding: {
+ when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_IS_ACTIVE_EDITOR, EditorContextKeys.focus.toNegated()),
+ primary: KeyCode.KeyF | KeyMod.CtrlCmd,
+ weight: KeybindingWeight.WorkbenchContrib
+ }
+ });
+ }
+
+ async run(accessor: ServicesAccessor): Promise<void> {
+ const editorService = accessor.get(IEditorService);
+ const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane);
+
+ if (!editor) {
+ return;
+ }
+
+ const controller = editor.getContribution<NotebookFindWidget>(NotebookFindWidget.id);
+ controller.show();
+ }
+});
+
+function notebookContainsTextModel(uri: URI, textModel: ITextModel) {
+ if (textModel.uri.scheme === Schemas.vscodeNotebookCell) {
+ const cellUri = CellUri.parse(textModel.uri);
+ if (cellUri && isEqual(cellUri.notebook, uri)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+StartFindAction.addImplementation(100, (accessor: ServicesAccessor, codeEditor: ICodeEditor, args: any) => {
+ const editorService = accessor.get(IEditorService);
+ const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane);
+
+ if (!editor) {
+ return false;
+ }
+
+ if (!editor.hasEditorFocus() && !editor.hasWebviewFocus()) {
+ const codeEditorService = accessor.get(ICodeEditorService);
+ // check if the active pane contains the active text editor
+ const textEditor = codeEditorService.getFocusedCodeEditor() || codeEditorService.getActiveCodeEditor();
+ if (editor.hasModel() && textEditor && textEditor.hasModel() && notebookContainsTextModel(editor.textModel.uri, textEditor.getModel())) {
+ // the active text editor is in notebook editor
+ } else {
+ return false;
+ }
+ }
+
+ const controller = editor.getContribution<NotebookFindWidget>(NotebookFindWidget.id);
+ controller.show();
+ return true;
+});
+
+StartFindReplaceAction.addImplementation(100, (accessor: ServicesAccessor, codeEditor: ICodeEditor, args: any) => {
+ const editorService = accessor.get(IEditorService);
+ const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane);
+
+ if (!editor) {
+ return false;
+ }
+
+ const controller = editor.getContribution<NotebookFindWidget>(NotebookFindWidget.id);
+ if (controller) {
+ controller.replace();
+ return true;
+ }
+
+ return false;
+});
+
diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts
index 9e7e660b6b0..329490a8431 100644
--- a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts
+++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts
@@ -614,7 +614,7 @@ export abstract class SimpleFindReplaceWidget extends Widget {
this._findInput.focus();
}
- public show(initialInput?: string): void {
+ public show(initialInput?: string, options?: { focus?: boolean }): void {
if (initialInput && !this._isVisible) {
this._findInput.setValue(initialInput);
}
@@ -625,7 +625,9 @@ export abstract class SimpleFindReplaceWidget extends Widget {
this._domNode.classList.add('visible', 'visible-transition');
this._domNode.setAttribute('aria-hidden', 'false');
- this.focus();
+ if (options?.focus ?? true) {
+ this.focus();
+ }
}, 0);
}
diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts
index 662b1929a2e..c027d8137d4 100644
--- a/src/vs/workbench/contrib/notebook/browser/contrib/find/findController.ts
+++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts
@@ -3,46 +3,42 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import 'vs/css!./media/notebookFind';
+import * as DOM from 'vs/base/browser/dom';
+import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { alert as alertFn } from 'vs/base/browser/ui/aria/aria';
+import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import * as strings from 'vs/base/common/strings';
-import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
-import { IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
-import { KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_IS_ACTIVE_EDITOR } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
-import { INotebookEditor, CellEditState, INotebookEditorContribution, getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { Range } from 'vs/editor/common/core/range';
+import { FindMatch } from 'vs/editor/common/model';
import { MATCHES_LIMIT } from 'vs/editor/contrib/find/browser/findModel';
-import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
-import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
-import { IThemeService } from 'vs/platform/theme/common/themeService';
-import * as DOM from 'vs/base/browser/dom';
-import { registerNotebookContribution } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions';
-import { registerAction2, Action2, IMenuService } from 'vs/platform/actions/common/actions';
-import { localize } from 'vs/nls';
-import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
-import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
-import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { FindReplaceState } from 'vs/editor/contrib/find/browser/findState';
-import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
-import { StartFindAction, StartFindReplaceAction } from 'vs/editor/contrib/find/browser/findController';
-import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { NLS_MATCHES_LOCATION, NLS_NO_RESULTS } from 'vs/editor/contrib/find/browser/findWidget';
+import { localize } from 'vs/nls';
+import { IMenuService } from 'vs/platform/actions/common/actions';
+import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
+import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
+import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
+import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
+import { IThemeService } from 'vs/platform/theme/common/themeService';
+import { NotebookFindFilters } from 'vs/workbench/contrib/notebook/browser/contrib/find/findFilters';
import { FindModel } from 'vs/workbench/contrib/notebook/browser/contrib/find/findModel';
-import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
-import { FindMatch, ITextModel } from 'vs/editor/common/model';
import { SimpleFindReplaceWidget } from 'vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget';
-import { NotebookFindFilters } from 'vs/workbench/contrib/notebook/browser/contrib/find/findFilters';
-import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
-import { Schemas } from 'vs/base/common/network';
-import { CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon';
-import { URI } from 'vs/base/common/uri';
-import { isEqual } from 'vs/base/common/resources';
+import { CellEditState, INotebookEditor, INotebookEditorContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
+import { KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
const FIND_HIDE_TRANSITION = 'find-hide-transition';
const FIND_SHOW_TRANSITION = 'find-show-transition';
let MAX_MATCHES_COUNT_WIDTH = 69;
const PROGRESS_BAR_DELAY = 200; // show progress for at least 200ms
+export interface IShowNotebookFindWidgetOptions {
+ isRegex?: boolean;
+ wholeWord?: boolean;
+ matchCase?: boolean;
+ matchIndex?: number;
+ focus?: boolean;
+}
+
export class NotebookFindWidget extends SimpleFindReplaceWidget implements INotebookEditorContribution {
static id: string = 'workbench.notebook.find';
protected _findWidgetFocused: IContextKey<boolean>;
@@ -104,7 +100,7 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote
private _onFindInputKeyDown(e: IKeyboardEvent): void {
if (e.equals(KeyCode.Enter)) {
- this._findModel.find(false);
+ this.find(false);
e.preventDefault();
return;
} else if (e.equals(KeyMod.Shift | KeyCode.Enter)) {
@@ -125,8 +121,12 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote
return false;
}
+ private findIndex(index: number): void {
+ this._findModel.find({ index });
+ }
+
protected find(previous: boolean): void {
- this._findModel.find(previous);
+ this._findModel.find({ previous });
}
protected replaceOne() {
@@ -141,7 +141,7 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote
this._findModel.ensureFindMatches();
if (this._findModel.currentMatch < 0) {
- this._findModel.find(false);
+ this._findModel.find({ previous: false });
}
const currentMatch = this._findModel.getCurrentMatch();
@@ -213,10 +213,18 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote
protected onFindInputFocusTrackerFocus(): void { }
protected onFindInputFocusTrackerBlur(): void { }
- override show(initialInput?: string): void {
- super.show(initialInput);
+ override async show(initialInput?: string, options?: IShowNotebookFindWidgetOptions): Promise<void> {
+ super.show(initialInput, options);
this._state.change({ searchString: initialInput ?? '', isRevealed: true }, false);
- this._findInput.select();
+
+ if (typeof options?.matchIndex === 'number') {
+ if (!this._findModel.findMatches.length) {
+ await this._findModel.research();
+ }
+ this.findIndex(options.matchIndex);
+ } else {
+ this._findInput.select();
+ }
if (this._showTimeout === null) {
if (this._hideTimeout !== null) {
@@ -343,110 +351,3 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote
super.dispose();
}
}
-
-registerNotebookContribution(NotebookFindWidget.id, NotebookFindWidget);
-
-registerAction2(class extends Action2 {
- constructor() {
- super({
- id: 'notebook.hideFind',
- title: { value: localize('notebookActions.hideFind', "Hide Find in Notebook"), original: 'Hide Find in Notebook' },
- keybinding: {
- when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, KEYBINDING_CONTEXT_NOTEBOOK_FIND_WIDGET_FOCUSED),
- primary: KeyCode.Escape,
- weight: KeybindingWeight.WorkbenchContrib
- }
- });
- }
-
- async run(accessor: ServicesAccessor): Promise<void> {
- const editorService = accessor.get(IEditorService);
- const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane);
-
- if (!editor) {
- return;
- }
-
- const controller = editor.getContribution<NotebookFindWidget>(NotebookFindWidget.id);
- controller.hide();
- editor.focus();
- }
-});
-
-registerAction2(class extends Action2 {
- constructor() {
- super({
- id: 'notebook.find',
- title: { value: localize('notebookActions.findInNotebook', "Find in Notebook"), original: 'Find in Notebook' },
- keybinding: {
- when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_IS_ACTIVE_EDITOR, EditorContextKeys.focus.toNegated()),
- primary: KeyCode.KeyF | KeyMod.CtrlCmd,
- weight: KeybindingWeight.WorkbenchContrib
- }
- });
- }
-
- async run(accessor: ServicesAccessor): Promise<void> {
- const editorService = accessor.get(IEditorService);
- const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane);
-
- if (!editor) {
- return;
- }
-
- const controller = editor.getContribution<NotebookFindWidget>(NotebookFindWidget.id);
- controller.show();
- }
-});
-
-function notebookContainsTextModel(uri: URI, textModel: ITextModel) {
- if (textModel.uri.scheme === Schemas.vscodeNotebookCell) {
- const cellUri = CellUri.parse(textModel.uri);
- if (cellUri && isEqual(cellUri.notebook, uri)) {
- return true;
- }
- }
-
- return false;
-}
-
-StartFindAction.addImplementation(100, (accessor: ServicesAccessor, codeEditor: ICodeEditor, args: any) => {
- const editorService = accessor.get(IEditorService);
- const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane);
-
- if (!editor) {
- return false;
- }
-
- if (!editor.hasEditorFocus() && !editor.hasWebviewFocus()) {
- const codeEditorService = accessor.get(ICodeEditorService);
- // check if the active pane contains the active text editor
- const textEditor = codeEditorService.getFocusedCodeEditor() || codeEditorService.getActiveCodeEditor();
- if (editor.hasModel() && textEditor && textEditor.hasModel() && notebookContainsTextModel(editor.textModel.uri, textEditor.getModel())) {
- // the active text editor is in notebook editor
- } else {
- return false;
- }
- }
-
- const controller = editor.getContribution<NotebookFindWidget>(NotebookFindWidget.id);
- controller.show();
- return true;
-});
-
-StartFindReplaceAction.addImplementation(100, (accessor: ServicesAccessor, codeEditor: ICodeEditor, args: any) => {
- const editorService = accessor.get(IEditorService);
- const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane);
-
- if (!editor) {
- return false;
- }
-
- const controller = editor.getContribution<NotebookFindWidget>(NotebookFindWidget.id);
- if (controller) {
- controller.replace();
- return true;
- }
-
- return false;
-});
diff --git a/src/vs/workbench/contrib/notebook/browser/controller/apiActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/apiActions.ts
index d0c6379a54b..68d9d0f8dcc 100644
--- a/src/vs/workbench/contrib/notebook/browser/controller/apiActions.ts
+++ b/src/vs/workbench/contrib/notebook/browser/controller/apiActions.ts
@@ -7,7 +7,7 @@ import * as glob from 'vs/base/common/glob';
import { URI, UriComponents } from 'vs/base/common/uri';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { isDocumentExcludePattern, TransientCellMetadata, TransientDocumentMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
-import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
+import { INotebookKernelService, IResolvedNotebookKernel, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
CommandsRegistry.registerCommand('_resolveNotebookContentProvider', (accessor): {
@@ -66,13 +66,13 @@ CommandsRegistry.registerCommand('_resolveNotebookKernels', async (accessor, arg
const uri = URI.revive(args.uri as UriComponents);
const kernels = notebookKernelService.getMatchingKernel({ uri, viewType: args.viewType });
- return kernels.all.map(provider => ({
+ return kernels.all.filter(kernel => kernel.type === NotebookKernelType.Resolved).map((provider) => ({
id: provider.id,
label: provider.label,
kind: provider.kind,
description: provider.description,
detail: provider.detail,
isPreferred: false, // todo@jrieken,@rebornix
- preloads: provider.preloadUris,
+ preloads: (provider as IResolvedNotebookKernel).preloadUris,
}));
});
diff --git a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts
index 588675f6403..8d253828d2e 100644
--- a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts
+++ b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts
@@ -21,12 +21,14 @@ import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/
import { changeCellToKind, runDeleteAction } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations';
import { CellToolbarOrder, CELL_TITLE_CELL_GROUP_ID, CELL_TITLE_OUTPUT_GROUP_ID, executeNotebookCondition, INotebookActionContext, INotebookCellActionContext, NotebookAction, NotebookCellAction, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT } from 'vs/workbench/contrib/notebook/browser/controller/coreActions';
import { NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_TYPE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_HAS_OUTPUTS, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_USE_CONSOLIDATED_OUTPUT_BUTTON } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
-import { CellEditState, CHANGE_CELL_LANGUAGE, QUIT_EDIT_CELL_COMMAND_ID } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
+import { CellEditState, CHANGE_CELL_LANGUAGE, DETECT_CELL_LANGUAGE, QUIT_EDIT_CELL_COMMAND_ID } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons';
import { CellEditType, CellKind, ICellEditOperation, NotebookCellExecutionState } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange';
import { ILanguageDetectionService } from 'vs/workbench/services/languageDetection/common/languageDetectionWorkerService';
import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
+import { INotificationService } from 'vs/platform/notification/common/notification';
+import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
const CLEAR_ALL_CELLS_OUTPUTS_COMMAND_ID = 'notebook.clearAllCellsOutputs';
const EDIT_CELL_COMMAND_ID = 'notebook.cell.edit';
@@ -437,23 +439,7 @@ registerAction2(class ChangeCellLanguageAction extends NotebookCellAction<ICellR
}
private async setLanguage(context: IChangeCellContext, languageId: string) {
- if (languageId === 'markdown' && context.cell?.language !== 'markdown') {
- const idx = context.notebookEditor.getCellIndex(context.cell);
- await changeCellToKind(CellKind.Markup, { cell: context.cell, notebookEditor: context.notebookEditor, ui: true }, 'markdown', Mimes.markdown);
- const newCell = context.notebookEditor.cellAt(idx);
-
- if (newCell) {
- context.notebookEditor.focusNotebookCell(newCell, 'editor');
- }
- } else if (languageId !== 'markdown' && context.cell?.cellKind === CellKind.Markup) {
- await changeCellToKind(CellKind.Code, { cell: context.cell, notebookEditor: context.notebookEditor, ui: true }, languageId);
- } else {
- const index = context.notebookEditor.textModel.cells.indexOf(context.cell.model);
- context.notebookEditor.textModel.applyEdits(
- [{ editType: CellEditType.CellLanguage, index, language: languageId }],
- true, undefined, () => undefined, undefined, true
- );
- }
+ await setCellToLanguage(languageId, context);
}
/**
@@ -478,3 +464,50 @@ registerAction2(class ChangeCellLanguageAction extends NotebookCellAction<ICellR
return fakeResource;
}
});
+
+registerAction2(class DetectCellLanguageAction extends NotebookCellAction {
+ constructor() {
+ super({
+ id: DETECT_CELL_LANGUAGE,
+ title: localize('detectLanguage', '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 }
+ });
+ }
+
+ async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise<void> {
+ const languageDetectionService = accessor.get(ILanguageDetectionService);
+ const notificationService = accessor.get(INotificationService);
+ const kernelService = accessor.get(INotebookKernelService);
+ const kernel = kernelService.getSelectedOrSuggestedKernel(context.notebookEditor.textModel);
+ const providerLanguages = [...kernel?.supportedLanguages ?? []];
+ providerLanguages.push('markdown');
+ const detection = await languageDetectionService.detectLanguage(context.cell.uri, providerLanguages);
+ if (detection) {
+ setCellToLanguage(detection, context);
+ } else {
+ notificationService.warn(localize('noDetection', "Unable to detect cell language"));
+ }
+ }
+});
+
+async function setCellToLanguage(languageId: string, context: IChangeCellContext) {
+ if (languageId === 'markdown' && context.cell?.language !== 'markdown') {
+ const idx = context.notebookEditor.getCellIndex(context.cell);
+ await changeCellToKind(CellKind.Markup, { cell: context.cell, notebookEditor: context.notebookEditor, ui: true }, 'markdown', Mimes.markdown);
+ const newCell = context.notebookEditor.cellAt(idx);
+
+ if (newCell) {
+ context.notebookEditor.focusNotebookCell(newCell, 'editor');
+ }
+ } else if (languageId !== 'markdown' && context.cell?.cellKind === CellKind.Markup) {
+ await changeCellToKind(CellKind.Code, { cell: context.cell, notebookEditor: context.notebookEditor, ui: true }, languageId);
+ } else {
+ const index = context.notebookEditor.textModel.cells.indexOf(context.cell.model);
+ context.notebookEditor.textModel.applyEdits(
+ [{ editType: CellEditType.CellLanguage, index, language: languageId }],
+ true, undefined, () => undefined, undefined, true
+ );
+ }
+}
diff --git a/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts
index 27ce6ca5434..4f6a3e489c5 100644
--- a/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts
+++ b/src/vs/workbench/contrib/notebook/browser/controller/layoutActions.ts
@@ -173,15 +173,6 @@ registerAction2(class ToggleBreadcrumbFromEditorTitle extends Action2 {
}
});
-MenuRegistry.appendMenuItem(MenuId.NotebookToolbar, {
- command: {
- id: 'breadcrumbs.toggle',
- title: { value: localize('cmd.toggle', "Toggle Breadcrumbs"), original: 'Toggle Breadcrumbs' },
- },
- group: 'notebookLayout',
- order: 2
-});
-
registerAction2(class SaveMimeTypeDisplayOrder extends Action2 {
constructor() {
super({
diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebook.css b/src/vs/workbench/contrib/notebook/browser/media/notebook.css
index a660892ff1f..e4220b57b14 100644
--- a/src/vs/workbench/contrib/notebook/browser/media/notebook.css
+++ b/src/vs/workbench/contrib/notebook/browser/media/notebook.css
@@ -429,7 +429,7 @@
outline: none !important;
}
-.monaco-workbench .notebookOverlay.notebook-editor-editable > .cell-list-container > .monaco-list > .monaco-scrollable-element > .scrollbar.visible {
+.monaco-workbench .notebookOverlay.notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .scrollbar.visible {
z-index: var(--z-index-notebook-scrollbar);
cursor: default;
}
diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts
index 2b793c40961..466fd1bf565 100644
--- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts
+++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts
@@ -70,7 +70,7 @@ import 'vs/workbench/contrib/notebook/browser/controller/foldingController';
// Editor Contribution
import 'vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard';
-import 'vs/workbench/contrib/notebook/browser/contrib/find/findController';
+import 'vs/workbench/contrib/notebook/browser/contrib/find/notebookFind';
import 'vs/workbench/contrib/notebook/browser/contrib/format/formatting';
import 'vs/workbench/contrib/notebook/browser/contrib/gettingStarted/notebookGettingStarted';
import 'vs/workbench/contrib/notebook/browser/contrib/layout/layoutActions';
@@ -892,5 +892,22 @@ configurationRegistry.registerConfiguration({
enum: ['always', 'never', 'fromEditor'],
default: 'fromEditor'
},
+ [NotebookSetting.outputLineHeight]: {
+ markdownDescription: nls.localize('notebook.outputLineHeight', "Line height of the output text for notebook cells.\n - Values between 0 and 8 will be used as a multiplier with the font size.\n - Values greater than or equal to 8 will be used as effective values."),
+ type: 'number',
+ default: 22,
+ 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."),
+ 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."),
+ type: 'string',
+ tags: ['notebookLayout']
+ },
}
});
diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts
index 7153bcfe26c..77a50b8c655 100644
--- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts
+++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts
@@ -11,7 +11,7 @@ import { IEditorContributionDescription } from 'vs/editor/browser/editorExtensio
import * as editorCommon from 'vs/editor/common/editorCommon';
import { FontInfo } from 'vs/editor/common/config/fontInfo';
import { IPosition } from 'vs/editor/common/core/position';
-import { Range } from 'vs/editor/common/core/range';
+import { IRange, Range } from 'vs/editor/common/core/range';
import { FindMatch, IModelDeltaDecoration, IReadonlyTextBuffer, ITextModel, TrackedRangeStickiness } from 'vs/editor/common/model';
import { MenuId } from 'vs/platform/actions/common/actions';
import { ITextEditorOptions, ITextResourceEditorInput } from 'vs/platform/editor/common/editor';
@@ -31,6 +31,7 @@ import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
//#region Shared commands
export const EXPAND_CELL_INPUT_COMMAND_ID = 'notebook.cell.expandCellInput';
export const EXECUTE_CELL_COMMAND_ID = 'notebook.cell.execute';
+export const DETECT_CELL_LANGUAGE = 'notebook.cell.detectLanguage';
export const CHANGE_CELL_LANGUAGE = 'notebook.cell.changeLanguage';
export const QUIT_EDIT_CELL_COMMAND_ID = 'notebook.cell.quitEdit';
export const EXPAND_CELL_OUTPUT_COMMAND_ID = 'notebook.cell.expandCellOutput';
@@ -280,6 +281,7 @@ export interface INotebookEditorOptions extends ITextEditorOptions {
readonly cellSelections?: ICellRange[];
readonly isReadOnly?: boolean;
readonly viewState?: INotebookEditorViewState;
+ readonly indexedCellOptions?: { index: number; selection?: IRange };
}
export type INotebookEditorContributionCtor = IConstructorSignature<INotebookEditorContribution, [INotebookEditor]>;
diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts
index 623686523fa..58edfc13e6b 100644
--- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts
+++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts
@@ -7,7 +7,7 @@ import * as DOM from 'vs/base/browser/dom';
import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
import { IAction, toAction } from 'vs/base/common/actions';
import { CancellationToken } from 'vs/base/common/cancellation';
-import { IErrorWithActions } from 'vs/base/common/errorMessage';
+import { createErrorWithActions } from 'vs/base/common/errorMessage';
import { Emitter, Event } from 'vs/base/common/event';
import { DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle';
import { extname, isEqual } from 'vs/base/common/resources';
@@ -289,8 +289,7 @@ export class NotebookEditor extends EditorPane implements IEditorPaneWithSelecti
}
}
} catch (e) {
- const error: Error & IErrorWithActions = e instanceof Error ? e : new Error(e.message);
- error.actions = [
+ const error = createErrorWithActions(e instanceof Error ? e : new Error(e.message), [
toAction({
id: 'workbench.notebook.action.openInTextEditor', label: localize('notebookOpenInTextEditor', "Open in Text Editor"), run: async () => {
const activeEditorPane = this._editorService.activeEditorPane;
@@ -317,7 +316,7 @@ export class NotebookEditor extends EditorPane implements IEditorPaneWithSelecti
return;
}
})
- ];
+ ]);
throw error;
}
diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts
index aa1bcc7853f..20ea78a5058 100644
--- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts
+++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts
@@ -101,7 +101,6 @@ export class BaseCellEditorOptions extends Disposable implements IBaseCellEditor
},
renderLineHighlightOnlyWhenFocus: true,
overviewRulerLanes: 0,
- selectOnLineNumbers: false,
lineNumbers: 'off',
lineDecorationsWidth: 0,
folding: true,
@@ -397,7 +396,11 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
this._updateForNotebookConfiguration();
}
- if (e.compactView || e.focusIndicator || e.insertToolbarPosition || e.cellToolbarLocation || e.dragAndDropEnabled || e.fontSize || e.markupFontSize || e.insertToolbarAlignment) {
+ if (e.fontFamily) {
+ this._generateFontInfo();
+ }
+
+ if (e.compactView || e.focusIndicator || e.insertToolbarPosition || e.cellToolbarLocation || e.dragAndDropEnabled || e.fontSize || e.outputFontSize || e.markupFontSize || e.fontFamily || e.outputFontFamily || e.insertToolbarAlignment || e.outputLineHeight) {
this._styleElement?.remove();
this._createLayoutStyles();
this._webview?.updateOptions({
@@ -1216,8 +1219,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
this.viewModel.updateOptions({ isReadOnly: this._readOnly });
// reveal cell if editor options tell to do so
- if (options?.cellOptions) {
- const cellOptions = options.cellOptions;
+ const cellOptions = options?.cellOptions ?? this._parseIndexedCellOptions(options);
+ if (cellOptions) {
const cell = this.viewModel.viewCells.find(cell => cell.uri.toString() === cellOptions.resource.toString());
if (cell) {
this.focusElement(cell);
@@ -1270,6 +1273,24 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
this._onDidChangeOptions.fire();
}
+ private _parseIndexedCellOptions(options: INotebookEditorOptions | undefined) {
+ if (options?.indexedCellOptions) {
+ // convert index based selections
+ const cell = this.cellAt(options.indexedCellOptions.index);
+ if (cell) {
+ return {
+ resource: cell.uri,
+ options: {
+ selection: options.indexedCellOptions.selection,
+ preserveFocus: false
+ }
+ };
+ }
+ }
+
+ return undefined;
+ }
+
private _detachModel() {
this._localStore.clear();
dispose(this._localCellStateListeners);
@@ -1802,6 +1823,11 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
const focusRange = this.viewModel.getFocus();
const element = this.viewModel.cellAt(focusRange.start);
+ // The notebook editor doesn't have focus yet
+ if (!this.hasEditorFocus()) {
+ this.focusContainer();
+ }
+
if (element && element.focusMode === CellFocusMode.Editor) {
element.updateEditState(CellEditState.Editing, 'editorWidget.focus');
element.focusMode = CellFocusMode.Editor;
diff --git a/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts
index 000c7b3b72c..2d4fe4cebe0 100644
--- a/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts
+++ b/src/vs/workbench/contrib/notebook/browser/notebookExecutionServiceImpl.ts
@@ -4,6 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
+import { CancellationTokenSource } from 'vs/base/common/cancellation';
+import { IDisposable } from 'vs/base/common/lifecycle';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { ILogService } from 'vs/platform/log/common/log';
import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust';
@@ -12,10 +14,11 @@ import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/mode
import { CellKind, INotebookTextModel, NotebookCellExecutionState } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookExecutionService } from 'vs/workbench/contrib/notebook/common/notebookExecutionService';
import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
-import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
+import { INotebookKernelService, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
-export class NotebookExecutionService implements INotebookExecutionService {
+export class NotebookExecutionService implements INotebookExecutionService, IDisposable {
declare _serviceBrand: undefined;
+ private _activeProxyKernelExecutionToken: CancellationTokenSource | undefined;
constructor(
@ICommandService private readonly _commandService: ICommandService,
@@ -45,6 +48,29 @@ export class NotebookExecutionService implements INotebookExecutionService {
return;
}
+ if (kernel.type === NotebookKernelType.Proxy) {
+ this._activeProxyKernelExecutionToken?.dispose(true);
+ const tokenSource = this._activeProxyKernelExecutionToken = new CancellationTokenSource();
+ const resolved = await kernel.resolveKernel(notebook.uri);
+ const kernels = this._notebookKernelService.getMatchingKernel(notebook);
+ const newlyMatchedKernel = kernels.all.find(k => k.id === resolved);
+
+ if (!newlyMatchedKernel) {
+ return;
+ }
+
+ kernel = newlyMatchedKernel;
+ if (tokenSource.token.isCancellationRequested) {
+ // execution was cancelled but we still need to update the active kernel
+ this._notebookKernelService.selectKernelForNotebook(kernel, notebook);
+ return;
+ }
+ }
+
+ if (kernel.type === NotebookKernelType.Proxy) {
+ return;
+ }
+
const executeCells: NotebookCellTextModel[] = [];
for (const cell of cellsArr) {
const cellExe = this._notebookExecutionStateService.getCellExecution(cell.uri);
@@ -75,11 +101,20 @@ export class NotebookExecutionService implements INotebookExecutionService {
this._logService.debug(`NotebookExecutionService#cancelNotebookCellHandles ${JSON.stringify(cellsArr)}`);
const kernel = this._notebookKernelService.getSelectedOrSuggestedKernel(notebook);
if (kernel) {
- await kernel.cancelNotebookCellExecution(notebook.uri, cellsArr);
+ if (kernel.type === NotebookKernelType.Proxy) {
+ this._activeProxyKernelExecutionToken?.dispose(true);
+ } else {
+ await kernel.cancelNotebookCellExecution(notebook.uri, cellsArr);
+ }
+
}
}
async cancelNotebookCells(notebook: INotebookTextModel, cells: Iterable<NotebookCellTextModel>): Promise<void> {
this.cancelNotebookCellHandles(notebook, Array.from(cells, cell => cell.handle));
}
+
+ dispose() {
+ this._activeProxyKernelExecutionToken?.dispose(true);
+ }
}
diff --git a/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts
index 1f2e2e72032..3a14f27d0ff 100644
--- a/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts
+++ b/src/vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl.ts
@@ -196,7 +196,16 @@ export class NotebookKernelService extends Disposable implements INotebookKernel
getSelectedOrSuggestedKernel(notebook: INotebookTextModel): INotebookKernel | undefined {
const info = this.getMatchingKernel(notebook);
- return info.selected ?? (info.all.length === 1 ? info.all[0] : undefined);
+ if (info.selected) {
+ return info.selected;
+ }
+
+ const preferred = info.all.filter(kernel => this._kernels.get(kernel.id)?.notebookPriorities.get(notebook.uri) === 2 /* vscode.NotebookControllerPriority.Preferred */);
+ if (preferred.length === 1) {
+ return preferred[0];
+ }
+
+ return info.all.length === 1 ? info.all[0] : undefined;
}
// default kernel for notebookType
diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellContextKeys.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellContextKeys.ts
index e1e2a982be5..364939e3456 100644
--- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellContextKeys.ts
+++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellContextKeys.ts
@@ -5,15 +5,15 @@
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
+import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { CellEditState, CellFocusMode, ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
-import { NotebookCellExecutionStateContext, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_CELL_EXECUTING, NOTEBOOK_CELL_EXECUTION_STATE, NOTEBOOK_CELL_FOCUSED, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_INPUT_COLLAPSED, NOTEBOOK_CELL_LINE_NUMBERS, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_OUTPUT_COLLAPSED, NOTEBOOK_CELL_TYPE } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
+import { CellViewModelStateChangeEvent } from 'vs/workbench/contrib/notebook/browser/notebookViewEvents';
+import { CellPart } from 'vs/workbench/contrib/notebook/browser/view/cellPart';
import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel';
import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel';
import { NotebookCellExecutionState } from 'vs/workbench/contrib/notebook/common/notebookCommon';
+import { NotebookCellExecutionStateContext, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_CELL_EXECUTING, NOTEBOOK_CELL_EXECUTION_STATE, NOTEBOOK_CELL_FOCUSED, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_INPUT_COLLAPSED, NOTEBOOK_CELL_LINE_NUMBERS, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_OUTPUT_COLLAPSED, NOTEBOOK_CELL_RESOURCE, NOTEBOOK_CELL_TYPE } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
-import { CellViewModelStateChangeEvent } from 'vs/workbench/contrib/notebook/browser/notebookViewEvents';
-import { CellPart } from 'vs/workbench/contrib/notebook/browser/view/cellPart';
-import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
export class CellContextKeyPart extends CellPart {
private cellContextKeyManager: CellContextKeyManager;
@@ -44,6 +44,7 @@ export class CellContextKeyManager extends Disposable {
private cellContentCollapsed!: IContextKey<boolean>;
private cellOutputCollapsed!: IContextKey<boolean>;
private cellLineNumbers!: IContextKey<'on' | 'off' | 'inherit'>;
+ private cellResource!: IContextKey<string>;
private markdownEditMode!: IContextKey<boolean>;
@@ -69,6 +70,7 @@ export class CellContextKeyManager extends Disposable {
this.cellContentCollapsed = NOTEBOOK_CELL_INPUT_COLLAPSED.bindTo(this._contextKeyService);
this.cellOutputCollapsed = NOTEBOOK_CELL_OUTPUT_COLLAPSED.bindTo(this._contextKeyService);
this.cellLineNumbers = NOTEBOOK_CELL_LINE_NUMBERS.bindTo(this._contextKeyService);
+ this.cellResource = NOTEBOOK_CELL_RESOURCE.bindTo(this._contextKeyService);
if (element) {
this.updateForElement(element);
@@ -112,6 +114,7 @@ export class CellContextKeyManager extends Disposable {
this.updateForOutputs();
this.cellLineNumbers.set(this.element!.lineNumbers);
+ this.cellResource.set(this.element!.uri.toString());
});
}
diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDnd.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDnd.ts
index 8c0b8a99748..86e0a69074a 100644
--- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDnd.ts
+++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDnd.ts
@@ -78,7 +78,7 @@ export class CellDragAndDropController extends Disposable {
this._register(DOM.addDisposableListener(document.body, DOM.EventType.DRAG_START, this.onGlobalDragStart.bind(this), true));
this._register(DOM.addDisposableListener(document.body, DOM.EventType.DRAG_END, this.onGlobalDragEnd.bind(this), true));
- const addCellDragListener = (eventType: string, handler: (e: CellDragEvent) => void) => {
+ const addCellDragListener = (eventType: string, handler: (e: CellDragEvent) => void, useCapture = false) => {
this._register(DOM.addDisposableListener(
notebookEditor.getDomNode(),
eventType,
@@ -87,14 +87,21 @@ export class CellDragAndDropController extends Disposable {
if (cellDragEvent) {
handler(cellDragEvent);
}
- }));
+ }, useCapture));
};
addCellDragListener(DOM.EventType.DRAG_OVER, event => {
+ if (!this.currentDraggedCell) {
+ return;
+ }
event.browserEvent.preventDefault();
+ event.browserEvent.stopImmediatePropagation();
this.onCellDragover(event);
- });
+ }, true);
addCellDragListener(DOM.EventType.DROP, event => {
+ if (!this.currentDraggedCell) {
+ return;
+ }
event.browserEvent.preventDefault();
this.onCellDrop(event);
});
diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDragRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDragRenderer.ts
index 46388b581b4..43c5852753e 100644
--- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDragRenderer.ts
+++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDragRenderer.ts
@@ -65,7 +65,7 @@ class EditorTextRenderer {
let result = '';
for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {
- const lineTokens = model.getLineTokens(lineNumber);
+ const lineTokens = model.tokenization.getLineTokens(lineNumber);
const lineContent = lineTokens.getLineContent();
const startOffset = (lineNumber === startLineNumber ? startColumn - 1 : 0);
const endOffset = (lineNumber === endLineNumber ? endColumn - 1 : lineContent.length);
diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellExecution.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellExecution.ts
index d4745c02a43..469e7cbef3d 100644
--- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellExecution.ts
+++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellExecution.ts
@@ -9,6 +9,7 @@ import { ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/no
import { CellViewModelStateChangeEvent } from 'vs/workbench/contrib/notebook/browser/notebookViewEvents';
import { CellPart } from 'vs/workbench/contrib/notebook/browser/view/cellPart';
import { NotebookCellInternalMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
+import { NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
export class CellExecutionPart extends CellPart {
private kernelDisposables = this._register(new DisposableStore());
@@ -41,7 +42,7 @@ export class CellExecutionPart extends CellPart {
}
private updateExecutionOrder(internalMetadata: NotebookCellInternalMetadata): void {
- if (this._notebookEditor.activeKernel?.implementsExecutionOrder) {
+ if (this._notebookEditor.activeKernel?.type === NotebookKernelType.Resolved && this._notebookEditor.activeKernel?.implementsExecutionOrder) {
const executionOrderLabel = typeof internalMetadata.executionOrder === 'number' ?
`[${internalMetadata.executionOrder}]` :
'[ ]';
@@ -62,7 +63,8 @@ export class CellExecutionPart extends CellPart {
DOM.hide(this._executionOrderLabel);
} else {
DOM.show(this._executionOrderLabel);
- this._executionOrderLabel.style.top = `${element.layoutInfo.editorHeight}px`;
+ const top = element.layoutInfo.editorHeight - 22 + element.layoutInfo.statusBarHeight;
+ this._executionOrderLabel.style.top = `${top}px`;
}
}
}
diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts
index 3f2df96e305..6cd5d2c242d 100644
--- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts
+++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts
@@ -316,8 +316,17 @@ export class CodeCell extends Disposable {
}));
}
+ private shouldUpdateDOMFocus() {
+ // The DOM focus needs to be adjusted:
+ // when a cell editor should be focused
+ // the document active element is inside the notebook editor or the document body (cell editor being disposed previously)
+ return this.notebookEditor.getActiveCell() === this.viewCell
+ && this.viewCell.focusMode === CellFocusMode.Editor
+ && (this.notebookEditor.hasEditorFocus() || document.activeElement === document.body);
+ }
+
private updateEditorForFocusModeChange() {
- if (this.viewCell.focusMode === CellFocusMode.Editor && this.notebookEditor.getActiveCell() === this.viewCell) {
+ if (this.shouldUpdateDOMFocus()) {
this.templateData.editor?.focus();
}
@@ -479,7 +488,7 @@ export class CodeCell extends Disposable {
this._isDisposed = true;
// move focus back to the cell list otherwise the focus goes to body
- if (this.notebookEditor.getActiveCell() === this.viewCell && this.viewCell.focusMode === CellFocusMode.Editor) {
+ if (this.shouldUpdateDOMFocus()) {
this.notebookEditor.focusContainer();
}
diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/markdownCell.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/markdownCell.ts
index bf119feb15c..82e710ed494 100644
--- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/markdownCell.ts
+++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/markdownCell.ts
@@ -221,7 +221,7 @@ export class StatefulMarkdownCell extends Disposable {
override dispose() {
// move focus back to the cell list otherwise the focus goes to body
- if (this.notebookEditor.getActiveCell() === this.viewCell && this.viewCell.focusMode === CellFocusMode.Editor) {
+ if (this.notebookEditor.getActiveCell() === this.viewCell && this.viewCell.focusMode === CellFocusMode.Editor && (this.notebookEditor.hasEditorFocus() || document.activeElement === document.body)) {
this.notebookEditor.focusContainer();
}
@@ -327,6 +327,7 @@ export class StatefulMarkdownCell extends Disposable {
width: width,
height: editorHeight
},
+ enableDropIntoEditor: true,
// overflowWidgetsDomNode: this.notebookEditor.getOverflowContainerDomNode()
}, {
contributions: this.notebookEditor.creationOptions.cellEditorContributions
diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts
index 7bda661ed4e..a7145c9ffc9 100644
--- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts
+++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts
@@ -37,10 +37,11 @@ import { preloadsScriptStr, RendererMetadata } from 'vs/workbench/contrib/notebo
import { transformWebviewThemeVars } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewThemeMapping';
import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel';
import { CellUri, INotebookRendererInfo, NotebookSetting, RendererMessagingSpec } from 'vs/workbench/contrib/notebook/common/notebookCommon';
-import { INotebookKernel } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
+import { INotebookKernel, IResolvedNotebookKernel, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
import { IScopedRendererMessaging } from 'vs/workbench/contrib/notebook/common/notebookRendererMessagingService';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { IWebviewElement, IWebviewService, WebviewContentPurpose } from 'vs/workbench/contrib/webview/browser/webview';
+import { WebviewWindowDragMonitor } from 'vs/workbench/contrib/webview/browser/webviewWindowDragMonitor';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { FromWebviewMessage, IAckOutputHeight, IClickedDataUrlMessage, ICodeBlockHighlightRequest, IContentWidgetTopRequest, IControllerPreload, ICreationContent, ICreationRequestMessage, IFindMatch, IMarkupCellInitialization, ToWebviewMessage } from './webviewMessages';
@@ -88,8 +89,11 @@ interface BacklayerWebviewOptions {
readonly runGutter: number;
readonly dragAndDropEnabled: boolean;
readonly fontSize: number;
+ readonly outputFontSize: number;
readonly fontFamily: string;
+ readonly outputFontFamily: string;
readonly markupFontSize: number;
+ readonly outputLineHeight: number;
}
export class BackLayerWebView<T extends ICommonCellInfo> extends Disposable {
@@ -204,8 +208,9 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Disposable {
'notebook-output-node-left-padding': `${this.options.outputNodeLeftPadding}px`,
'notebook-markdown-min-height': `${this.options.previewNodePadding * 2}px`,
'notebook-markup-font-size': typeof this.options.markupFontSize === 'number' && this.options.markupFontSize > 0 ? `${this.options.markupFontSize}px` : `calc(${this.options.fontSize}px * 1.2)`,
- 'notebook-cell-output-font-size': `${this.options.fontSize}px`,
- 'notebook-cell-output-font-family': this.options.fontFamily,
+ 'notebook-cell-output-font-size': `${this.options.outputFontSize || this.options.fontSize}px`,
+ 'notebook-cell-output-line-height': `${this.options.outputLineHeight}px`,
+ 'notebook-cell-output-font-family': this.options.outputFontFamily || this.options.fontFamily,
'notebook-cell-markup-empty-content': nls.localize('notebook.emptyMarkdownPlaceholder', "Empty markdown cell, double click or press enter to edit."),
'notebook-cell-renderer-not-found-error': nls.localize({
key: 'notebook.error.rendererNotFound',
@@ -523,6 +528,8 @@ var requirejs = (function() {
this.webview.mountTo(this.element);
this._register(this.webview);
+ this._register(new WebviewWindowDragMonitor(() => this.webview));
+
this._register(this.webview.onDidClickLink(link => {
if (this._disposed) {
return;
@@ -897,7 +904,7 @@ var requirejs = (function() {
}
this._preloadsCache.clear();
- if (this._currentKernel) {
+ if (this._currentKernel?.type === NotebookKernelType.Resolved) {
this._updatePreloadsFromKernel(this._currentKernel);
}
@@ -1394,14 +1401,14 @@ var requirejs = (function() {
const previousKernel = this._currentKernel;
this._currentKernel = kernel;
- if (previousKernel && previousKernel.preloadUris.length > 0) {
+ if (previousKernel?.type === NotebookKernelType.Resolved && previousKernel.preloadUris.length > 0) {
this.webview?.reload(); // preloads will be restored after reload
- } else if (kernel) {
+ } else if (kernel?.type === NotebookKernelType.Resolved) {
this._updatePreloadsFromKernel(kernel);
}
}
- private _updatePreloadsFromKernel(kernel: INotebookKernel) {
+ private _updatePreloadsFromKernel(kernel: IResolvedNotebookKernel) {
const resources: IControllerPreload[] = [];
for (const preload of kernel.preloadUris) {
const uri = this.environmentService.isExtensionDevelopment && (preload.scheme === 'http' || preload.scheme === 'https')
@@ -1427,7 +1434,7 @@ var requirejs = (function() {
const mixedResourceRoots = [
...(this.localResourceRootsCache || []),
- ...(this._currentKernel ? [this._currentKernel.localResourceRoot] : []),
+ ...(this._currentKernel?.type === NotebookKernelType.Resolved ? [this._currentKernel.localResourceRoot] : []),
];
this.webview.localResourcesRoot = mixedResourceRoots;
diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts
index 02beef093cf..2bcf55c8531 100644
--- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts
+++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts
@@ -168,7 +168,7 @@ export class MarkupCellRenderer extends AbstractCellRenderer implements IListRen
templateDisposables.add(scopedInstaService.createInstance(BetweenCellToolbar, this.notebookEditor, titleToolbarContainer, bottomCellContainer)),
templateDisposables.add(scopedInstaService.createInstance(CellEditorStatusBar, this.notebookEditor, container, editorPart, undefined)),
templateDisposables.add(new CellFocusIndicator(this.notebookEditor, titleToolbar, focusIndicatorTop, focusIndicatorLeft, focusIndicatorRight, focusIndicatorBottom)),
- templateDisposables.add(scopedInstaService.createInstance(FoldedCellHint, this.notebookEditor, DOM.append(container, $('.notebook-folded-hint')))),
+ templateDisposables.add(new FoldedCellHint(this.notebookEditor, DOM.append(container, $('.notebook-folded-hint')))),
templateDisposables.add(new CellDecorations(rootContainer, decorationContainer)),
templateDisposables.add(scopedInstaService.createInstance(CellComments, this.notebookEditor, cellCommentPartContainer)),
templateDisposables.add(new CollapsedCellInput(this.notebookEditor, cellInputCollapsedContainer)),
@@ -274,6 +274,7 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende
width: 0,
height: 0
},
+ enableDropIntoEditor: true,
}, {
contributions: this.notebookEditor.creationOptions.cellEditorContributions
});
diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts
index 8b7c306af28..11b51e17e2f 100644
--- a/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts
+++ b/src/vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel.ts
@@ -156,8 +156,6 @@ export abstract class BaseCellViewModel extends Disposable {
this._onDidChangeState.fire({ outputCollapsedChanged: true });
}
- private _textEditorRestore: any;
-
constructor(
readonly viewType: string,
readonly model: NotebookCellTextModel,
@@ -236,9 +234,7 @@ export abstract class BaseCellViewModel extends Disposable {
this._textEditor = editor;
if (this._editorViewStates) {
- this._textEditorRestore = setTimeout(() => {
- this._restoreViewState(this._editorViewStates);
- });
+ this._restoreViewState(this._editorViewStates);
}
if (this._editorTransientState) {
@@ -264,7 +260,6 @@ export abstract class BaseCellViewModel extends Disposable {
}
detachTextEditor() {
- clearTimeout(this._textEditorRestore);
this.saveViewState();
this.saveTransientState();
// decorations need to be cleared first as editors can be resued.
@@ -589,7 +584,6 @@ export abstract class BaseCellViewModel extends Disposable {
super.dispose();
dispose(this._editorListeners);
- clearTimeout(this._textEditorRestore);
// Only remove the undo redo stack if we map this cell uri to itself
// If we are not in perCell mode, it will map to the full NotebookDocument and
diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts
index a90477db542..b1cf70a4946 100644
--- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts
+++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts
@@ -8,7 +8,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c
import { ICellViewModel, INotebookEditorDelegate, KERNEL_EXTENSIONS } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { NOTEBOOK_CELL_TOOLBAR_LOCATION, NOTEBOOK_HAS_OUTPUTS, NOTEBOOK_HAS_RUNNING_CELL, NOTEBOOK_INTERRUPTIBLE_KERNEL, NOTEBOOK_KERNEL, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_KERNEL_SELECTED, NOTEBOOK_MISSING_KERNEL_EXTENSION, NOTEBOOK_USE_CONSOLIDATED_OUTPUT_BUTTON, NOTEBOOK_VIEW_TYPE } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
-import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
+import { INotebookKernelService, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
export class NotebookEditorContextKeys {
@@ -148,7 +148,7 @@ export class NotebookEditorContextKeys {
const { selected, all } = this._notebookKernelService.getMatchingKernel(this._editor.textModel);
this._notebookKernelCount.set(all.length);
- this._interruptibleKernel.set(selected?.implementsInterrupt ?? false);
+ this._interruptibleKernel.set((selected?.type === NotebookKernelType.Resolved && selected.implementsInterrupt) ?? false);
this._notebookKernelSelected.set(Boolean(selected));
this._notebookKernel.set(selected?.id ?? '');
}
diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts
index dc9a494193e..e5814ce98ec 100644
--- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts
+++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.ts
@@ -6,10 +6,11 @@
import 'vs/css!./notebookKernelActionViewItem';
import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { Action, IAction } from 'vs/base/common/actions';
+import { DisposableStore } from 'vs/base/common/lifecycle';
import { localize } from 'vs/nls';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { selectKernelIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons';
-import { INotebookKernelMatchResult, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
+import { INotebookKernelMatchResult, INotebookKernelService, NotebookKernelType, ProxyKernelState } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
import { Event } from 'vs/base/common/event';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
@@ -17,6 +18,7 @@ import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookB
export class NotebooKernelActionViewItem extends ActionViewItem {
private _kernelLabel?: HTMLAnchorElement;
+ private _kernelDisposable: DisposableStore;
constructor(
actualAction: IAction,
@@ -31,6 +33,7 @@ export class NotebooKernelActionViewItem extends ActionViewItem {
this._register(_editor.onDidChangeModel(this._update, this));
this._register(_notebookKernelService.onDidChangeNotebookAffinity(this._update, this));
this._register(_notebookKernelService.onDidChangeSelectedNotebooks(this._update, this));
+ this._kernelDisposable = this._register(new DisposableStore());
}
override render(container: HTMLElement): void {
@@ -63,9 +66,9 @@ export class NotebooKernelActionViewItem extends ActionViewItem {
}
private _updateActionFromKernelInfo(info: INotebookKernelMatchResult): void {
-
+ this._kernelDisposable.clear();
this._action.enabled = true;
- const selectedOrSuggested = info.selected ?? (info.all.length === 1 && info.suggestions.length === 1 ? info.suggestions[0] : undefined);
+ const selectedOrSuggested = info.selected ?? ((info.suggestions.length === 1 && info.suggestions[0].type === NotebookKernelType.Resolved) ? info.suggestions[0] : undefined);
if (selectedOrSuggested) {
// selected or suggested kernel
this._action.label = selectedOrSuggested.label;
@@ -74,6 +77,23 @@ export class NotebooKernelActionViewItem extends ActionViewItem {
// special UI for selected kernel?
}
+ if (selectedOrSuggested.type === NotebookKernelType.Proxy) {
+ if (selectedOrSuggested.connectionState === ProxyKernelState.Initializing) {
+ this._action.label = localize('initializing', "Initializing...");
+ } else {
+ this._action.label = selectedOrSuggested.label;
+ }
+
+ this._kernelDisposable.add(selectedOrSuggested.onDidChange(e => {
+ if (e.connectionState) {
+ if (selectedOrSuggested.connectionState === ProxyKernelState.Initializing) {
+ this._action.label = localize('initializing', "Initializing...");
+ } else {
+ this._action.label = selectedOrSuggested.label;
+ }
+ }
+ }));
+ }
} else {
// many kernels or no kernels
this._action.label = localize('select', "Select Kernel");
diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts
index bc1f504dc36..28ff3f4f885 100644
--- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts
+++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts
@@ -25,8 +25,7 @@ import { IEditorModel } from 'vs/platform/editor/common/editor';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { ThemeColor } from 'vs/platform/theme/common/themeService';
import { UndoRedoGroup } from 'vs/platform/undoRedo/common/undoRedo';
-import { IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor';
-import { EditorInput } from 'vs/workbench/common/editor/editorInput';
+import { IRevertOptions, ISaveOptions, IUntypedEditorInput } from 'vs/workbench/common/editor';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange';
import { IWorkingCopyBackupMeta, IWorkingCopySaveEvent } from 'vs/workbench/services/workingCopy/common/workingCopy';
@@ -792,7 +791,7 @@ export interface INotebookEditorModel extends IEditorModel {
hasAssociatedFilePath(): boolean;
load(options?: INotebookLoadOptions): Promise<IResolvedNotebookEditorModel>;
save(options?: ISaveOptions): Promise<boolean>;
- saveAs(target: URI): Promise<EditorInput | undefined>;
+ saveAs(target: URI): Promise<IUntypedEditorInput | undefined>;
revert(options?: IRevertOptions): Promise<void>;
}
@@ -925,7 +924,10 @@ export const NotebookSetting = {
textOutputLineLimit: 'notebook.output.textLineLimit',
globalToolbarShowLabel: 'notebook.globalToolbarShowLabel',
markupFontSize: 'notebook.markup.fontSize',
- interactiveWindowCollapseCodeCells: 'interactiveWindow.collapseCellInputCode'
+ interactiveWindowCollapseCodeCells: 'interactiveWindow.collapseCellInputCode',
+ outputLineHeight: 'notebook.outputLineHeight',
+ outputFontSize: 'notebook.outputFontSize',
+ outputFontFamily: 'notebook.outputFontFamily'
} as const;
export const enum CellStatusbarAlignment {
diff --git a/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts b/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts
index fd2279cba63..689f9ca995e 100644
--- a/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts
+++ b/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts
@@ -38,6 +38,8 @@ export const NOTEBOOK_CELL_EXECUTING = new RawContextKey<boolean>('notebookCellE
export const NOTEBOOK_CELL_HAS_OUTPUTS = new RawContextKey<boolean>('notebookCellHasOutputs', false);
export const NOTEBOOK_CELL_INPUT_COLLAPSED = new RawContextKey<boolean>('notebookCellInputIsCollapsed', false);
export const NOTEBOOK_CELL_OUTPUT_COLLAPSED = new RawContextKey<boolean>('notebookCellOutputIsCollapsed', false);
+export const NOTEBOOK_CELL_RESOURCE = new RawContextKey<string>('notebookCellResource', '');
+
// Kernels
export const NOTEBOOK_KERNEL = new RawContextKey<string>('notebookKernel', undefined);
export const NOTEBOOK_KERNEL_COUNT = new RawContextKey<number>('notebookKernelCount', 0);
diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts
index cffd2b0362d..b8cbd4985d0 100644
--- a/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts
+++ b/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts
@@ -102,6 +102,10 @@ export class NotebookEditorInput extends AbstractResourceEditorInput {
}
}
+ if (!(capabilities & EditorInputCapabilities.Readonly)) {
+ capabilities |= EditorInputCapabilities.CanDropIntoEditor;
+ }
+
return capabilities;
}
@@ -120,7 +124,7 @@ export class NotebookEditorInput extends AbstractResourceEditorInput {
return this._editorModelReference.object.isDirty();
}
- override async save(group: GroupIdentifier, options?: ISaveOptions): Promise<EditorInput | undefined> {
+ override async save(group: GroupIdentifier, options?: ISaveOptions): Promise<EditorInput | IUntypedEditorInput | undefined> {
if (this._editorModelReference) {
if (this.hasCapability(EditorInputCapabilities.Untitled)) {
@@ -135,7 +139,7 @@ export class NotebookEditorInput extends AbstractResourceEditorInput {
return undefined;
}
- override async saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise<EditorInput | undefined> {
+ override async saveAs(group: GroupIdentifier, options?: ISaveOptions): Promise<IUntypedEditorInput | undefined> {
if (!this._editorModelReference) {
return undefined;
}
diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts
index 8938cff2e80..7cd345036a7 100644
--- a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts
+++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts
@@ -4,8 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
-import { IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor';
-import { EditorInput } from 'vs/workbench/common/editor/editorInput';
+import { IRevertOptions, ISaveOptions, IUntypedEditorInput } from 'vs/workbench/common/editor';
import { EditorModel } from 'vs/workbench/common/editor/editorModel';
import { Emitter, Event } from 'vs/base/common/event';
import { ICellDto2, INotebookEditorModel, INotebookLoadOptions, IResolvedNotebookEditorModel, NotebookCellsChangeType, NotebookData, NotebookDocumentBackupData } from 'vs/workbench/contrib/notebook/common/notebookCommon';
@@ -28,8 +27,6 @@ import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/commo
import { StoredFileWorkingCopyState, IStoredFileWorkingCopy, IStoredFileWorkingCopyModel, IStoredFileWorkingCopyModelContentChangedEvent, IStoredFileWorkingCopyModelFactory, IStoredFileWorkingCopySaveEvent } from 'vs/workbench/services/workingCopy/common/storedFileWorkingCopy';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { CancellationError } from 'vs/base/common/errors';
-import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput';
-import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { filter } from 'vs/base/common/objects';
import { IFileWorkingCopyManager } from 'vs/workbench/services/workingCopy/common/fileWorkingCopyManager';
import { IUntitledFileWorkingCopy, IUntitledFileWorkingCopyModel, IUntitledFileWorkingCopyModelContentChangedEvent, IUntitledFileWorkingCopyModelFactory } from 'vs/workbench/services/workingCopy/common/untitledFileWorkingCopy';
@@ -59,7 +56,6 @@ export class ComplexNotebookEditorModel extends EditorModel implements INotebook
readonly resource: URI,
readonly viewType: string,
private readonly _contentProvider: INotebookContentProvider,
- @IInstantiationService private readonly _instantiationService: IInstantiationService,
@INotebookService private readonly _notebookService: INotebookService,
@IWorkingCopyService private readonly _workingCopyService: IWorkingCopyService,
@IWorkingCopyBackupService private readonly _workingCopyBackupService: IWorkingCopyBackupService,
@@ -393,7 +389,7 @@ export class ComplexNotebookEditorModel extends EditorModel implements INotebook
});
}
- async saveAs(targetResource: URI): Promise<EditorInput | undefined> {
+ async saveAs(targetResource: URI): Promise<IUntypedEditorInput | undefined> {
if (!this.isResolved()) {
return undefined;
@@ -419,7 +415,7 @@ export class ComplexNotebookEditorModel extends EditorModel implements INotebook
}
this.setDirty(false);
this._onDidSave.fire({});
- return this._instantiationService.createInstance(NotebookEditorInput, targetResource, this.viewType, {});
+ return { resource: targetResource };
}
private async _resolveStats(resource: URI) {
@@ -462,7 +458,6 @@ export class SimpleNotebookEditorModel extends EditorModel implements INotebookE
private readonly _hasAssociatedFilePath: boolean,
readonly viewType: string,
private readonly _workingCopyManager: IFileWorkingCopyManager<NotebookFileWorkingCopyModel, NotebookFileWorkingCopyModel>,
- @IInstantiationService private readonly _instantiationService: IInstantiationService,
@IFileService private readonly _fileService: IFileService
) {
super();
@@ -547,14 +542,14 @@ export class SimpleNotebookEditorModel extends EditorModel implements INotebookE
return this;
}
- async saveAs(target: URI): Promise<EditorInput | undefined> {
+ async saveAs(target: URI): Promise<IUntypedEditorInput | undefined> {
const newWorkingCopy = await this._workingCopyManager.saveAs(this.resource, target);
if (!newWorkingCopy) {
return undefined;
}
// this is a little hacky because we leave the new working copy alone. BUT
// the newly created editor input will pick it up and claim ownership of it.
- return this._instantiationService.createInstance(NotebookEditorInput, newWorkingCopy.resource, this.viewType, {});
+ return { resource: newWorkingCopy.resource };
}
private static _isStoredFileWorkingCopy(candidate?: IStoredFileWorkingCopy<NotebookFileWorkingCopyModel> | IUntitledFileWorkingCopy<NotebookFileWorkingCopyModel>): candidate is IStoredFileWorkingCopy<NotebookFileWorkingCopyModel> {
diff --git a/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts b/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts
index 6610fe7177d..4f5304600b0 100644
--- a/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts
+++ b/src/vs/workbench/contrib/notebook/common/notebookKernelService.ts
@@ -31,8 +31,13 @@ export interface INotebookKernelChangeEvent {
hasExecutionOrder?: true;
}
-export interface INotebookKernel {
+export const enum NotebookKernelType {
+ Resolved,
+ Proxy = 1
+}
+export interface IResolvedNotebookKernel {
+ readonly type: NotebookKernelType.Resolved;
readonly id: string;
readonly viewType: string;
readonly onDidChange: Event<Readonly<INotebookKernelChangeEvent>>;
@@ -54,6 +59,34 @@ export interface INotebookKernel {
cancelNotebookCellExecution(uri: URI, cellHandles: number[]): Promise<void>;
}
+export const enum ProxyKernelState {
+ Disconnected = 1,
+ Connected = 2,
+ Initializing = 3
+}
+
+export interface INotebookProxyKernelChangeEvent extends INotebookKernelChangeEvent {
+ connectionState?: true;
+}
+
+export interface INotebookProxyKernel {
+ readonly type: NotebookKernelType.Proxy;
+ readonly id: string;
+ readonly viewType: string;
+ readonly extension: ExtensionIdentifier;
+ readonly preloadProvides: string[];
+ readonly onDidChange: Event<Readonly<INotebookProxyKernelChangeEvent>>;
+ label: string;
+ description?: string;
+ detail?: string;
+ kind?: string;
+ supportedLanguages: string[];
+ connectionState: ProxyKernelState;
+ resolveKernel(uri: URI): Promise<string | null>;
+}
+
+export type INotebookKernel = IResolvedNotebookKernel | INotebookProxyKernel;
+
export interface INotebookTextModelLike { uri: URI; viewType: string }
export const INotebookKernelService = createDecorator<INotebookKernelService>('INotebookKernelService');
diff --git a/src/vs/workbench/contrib/notebook/common/notebookOptions.ts b/src/vs/workbench/contrib/notebook/common/notebookOptions.ts
index 9564a563def..426871f84fa 100644
--- a/src/vs/workbench/contrib/notebook/common/notebookOptions.ts
+++ b/src/vs/workbench/contrib/notebook/common/notebookOptions.ts
@@ -62,6 +62,9 @@ export interface NotebookLayoutConfiguration {
showFoldingControls: 'always' | 'mouseover';
dragAndDropEnabled: boolean;
fontSize: number;
+ outputFontSize: number;
+ outputFontFamily: string;
+ outputLineHeight: number;
markupFontSize: number;
focusIndicatorLeftMargin: number;
editorOptionsCustomizations: any | undefined;
@@ -84,9 +87,13 @@ export interface NotebookOptionsChangeEvent {
readonly consolidatedRunButton?: boolean;
readonly dragAndDropEnabled?: boolean;
readonly fontSize?: boolean;
+ readonly outputFontSize?: boolean;
readonly markupFontSize?: boolean;
+ readonly fontFamily?: boolean;
+ readonly outputFontFamily?: boolean;
readonly editorOptionsCustomizations?: boolean;
readonly interactiveWindowCollapseCodeCells?: boolean;
+ readonly outputLineHeight?: boolean;
}
const defaultConfigConstants = Object.freeze({
@@ -134,9 +141,12 @@ export class NotebookOptions extends Disposable {
const showFoldingControls = this._computeShowFoldingControlsOption();
// const { bottomToolbarGap, bottomToolbarHeight } = this._computeBottomToolbarDimensions(compactView, insertToolbarPosition, insertToolbarAlignment);
const fontSize = this.configurationService.getValue<number>('editor.fontSize');
+ const outputFontSize = this.configurationService.getValue<number>(NotebookSetting.outputFontSize);
+ const outputFontFamily = this.configurationService.getValue<string>(NotebookSetting.outputFontFamily);
const markupFontSize = this.configurationService.getValue<number>(NotebookSetting.markupFontSize);
const editorOptionsCustomizations = this.configurationService.getValue(NotebookSetting.cellEditorOptionsCustomizations);
const interactiveWindowCollapseCodeCells: InteractiveWindowCollapseCodeCells = this.configurationService.getValue(NotebookSetting.interactiveWindowCollapseCodeCells);
+ const outputLineHeight = this._computeOutputLineHeight();
this._layoutConfiguration = {
...(compactView ? compactConfigConstants : defaultConfigConstants),
@@ -166,6 +176,9 @@ export class NotebookOptions extends Disposable {
insertToolbarAlignment,
showFoldingControls,
fontSize,
+ outputFontSize,
+ outputFontFamily,
+ outputLineHeight,
markupFontSize,
editorOptionsCustomizations,
focusIndicatorGap: 3,
@@ -185,6 +198,29 @@ export class NotebookOptions extends Disposable {
}));
}
+ private _computeOutputLineHeight(): number {
+ const minimumLineHeight = 8;
+ let lineHeight = this.configurationService.getValue<number>(NotebookSetting.outputLineHeight);
+
+ if (lineHeight < minimumLineHeight) {
+ // Values too small to be line heights in pixels are in ems.
+ let fontSize = this.configurationService.getValue<number>(NotebookSetting.outputFontSize);
+ if (fontSize === 0) {
+ fontSize = this.configurationService.getValue<number>('editor.fontSize');
+ }
+
+ lineHeight = lineHeight * fontSize;
+ }
+
+ // Enforce integer, minimum constraints
+ lineHeight = Math.round(lineHeight);
+ if (lineHeight < minimumLineHeight) {
+ lineHeight = minimumLineHeight;
+ }
+
+ return lineHeight;
+ }
+
private _updateConfiguration(e: IConfigurationChangeEvent) {
const cellStatusBarVisibility = e.affectsConfiguration(NotebookSetting.showCellStatusBar);
const cellToolbarLocation = e.affectsConfiguration(NotebookSetting.cellToolbarLocation);
@@ -199,9 +235,13 @@ export class NotebookOptions extends Disposable {
const showFoldingControls = e.affectsConfiguration(NotebookSetting.showFoldingControls);
const dragAndDropEnabled = e.affectsConfiguration(NotebookSetting.dragAndDropEnabled);
const fontSize = e.affectsConfiguration('editor.fontSize');
+ const outputFontSize = e.affectsConfiguration(NotebookSetting.outputFontSize);
const markupFontSize = e.affectsConfiguration(NotebookSetting.markupFontSize);
+ const fontFamily = e.affectsConfiguration('editor.fontFamily');
+ const outputFontFamily = e.affectsConfiguration(NotebookSetting.outputFontFamily);
const editorOptionsCustomizations = e.affectsConfiguration(NotebookSetting.cellEditorOptionsCustomizations);
const interactiveWindowCollapseCodeCells = e.affectsConfiguration(NotebookSetting.interactiveWindowCollapseCodeCells);
+ const outputLineHeight = e.affectsConfiguration(NotebookSetting.outputLineHeight);
if (
!cellStatusBarVisibility
@@ -217,9 +257,13 @@ export class NotebookOptions extends Disposable {
&& !showFoldingControls
&& !dragAndDropEnabled
&& !fontSize
+ && !outputFontSize
&& !markupFontSize
+ && !fontFamily
+ && !outputFontFamily
&& !editorOptionsCustomizations
- && !interactiveWindowCollapseCodeCells) {
+ && !interactiveWindowCollapseCodeCells
+ && !outputLineHeight) {
return;
}
@@ -281,10 +325,18 @@ export class NotebookOptions extends Disposable {
configuration.fontSize = this.configurationService.getValue<number>('editor.fontSize');
}
+ if (outputFontSize) {
+ configuration.outputFontSize = this.configurationService.getValue<number>(NotebookSetting.outputFontSize) ?? configuration.fontSize;
+ }
+
if (markupFontSize) {
configuration.markupFontSize = this.configurationService.getValue<number>(NotebookSetting.markupFontSize);
}
+ if (outputFontFamily) {
+ configuration.outputFontFamily = this.configurationService.getValue<string>(NotebookSetting.outputFontFamily);
+ }
+
if (editorOptionsCustomizations) {
configuration.editorOptionsCustomizations = this.configurationService.getValue(NotebookSetting.cellEditorOptionsCustomizations);
}
@@ -293,6 +345,10 @@ export class NotebookOptions extends Disposable {
configuration.interactiveWindowCollapseCodeCells = this.configurationService.getValue(NotebookSetting.interactiveWindowCollapseCodeCells);
}
+ if (outputLineHeight || fontSize || outputFontSize) {
+ configuration.outputLineHeight = this._computeOutputLineHeight();
+ }
+
this._layoutConfiguration = Object.freeze(configuration);
// trigger event
@@ -310,9 +366,13 @@ export class NotebookOptions extends Disposable {
consolidatedRunButton,
dragAndDropEnabled,
fontSize,
+ outputFontSize,
markupFontSize,
+ fontFamily,
+ outputFontFamily,
editorOptionsCustomizations,
- interactiveWindowCollapseCodeCells
+ interactiveWindowCollapseCodeCells,
+ outputLineHeight
});
}
@@ -502,7 +562,10 @@ export class NotebookOptions extends Disposable {
runGutter: this._layoutConfiguration.cellRunGutter,
dragAndDropEnabled: this._layoutConfiguration.dragAndDropEnabled,
fontSize: this._layoutConfiguration.fontSize,
+ outputFontSize: this._layoutConfiguration.outputFontSize,
+ outputFontFamily: this._layoutConfiguration.outputFontFamily,
markupFontSize: this._layoutConfiguration.markupFontSize,
+ outputLineHeight: this._layoutConfiguration.outputLineHeight,
};
}
@@ -517,7 +580,10 @@ export class NotebookOptions extends Disposable {
runGutter: 0,
dragAndDropEnabled: false,
fontSize: this._layoutConfiguration.fontSize,
+ outputFontSize: this._layoutConfiguration.outputFontSize,
+ outputFontFamily: this._layoutConfiguration.outputFontFamily,
markupFontSize: this._layoutConfiguration.markupFontSize,
+ outputLineHeight: this._layoutConfiguration.outputLineHeight,
};
}
diff --git a/src/vs/workbench/contrib/notebook/test/browser/contrib/find.test.ts b/src/vs/workbench/contrib/notebook/test/browser/contrib/find.test.ts
index b9822f87393..6b1282d9afb 100644
--- a/src/vs/workbench/contrib/notebook/test/browser/contrib/find.test.ts
+++ b/src/vs/workbench/contrib/notebook/test/browser/contrib/find.test.ts
@@ -68,11 +68,11 @@ suite('Notebook Find', () => {
await found;
assert.strictEqual(model.findMatches.length, 2);
assert.strictEqual(model.currentMatch, -1);
- model.find(false);
+ model.find({ previous: false });
assert.strictEqual(model.currentMatch, 0);
- model.find(false);
+ model.find({ previous: false });
assert.strictEqual(model.currentMatch, 1);
- model.find(false);
+ model.find({ previous: false });
assert.strictEqual(model.currentMatch, 0);
assert.strictEqual(editor.textModel.length, 3);
@@ -115,11 +115,11 @@ suite('Notebook Find', () => {
// find matches is not necessarily find results
assert.strictEqual(model.findMatches.length, 4);
assert.strictEqual(model.currentMatch, -1);
- model.find(false);
+ model.find({ previous: false });
assert.strictEqual(model.currentMatch, 0);
- model.find(false);
+ model.find({ previous: false });
assert.strictEqual(model.currentMatch, 1);
- model.find(false);
+ model.find({ previous: false });
assert.strictEqual(model.currentMatch, 2);
const found2 = new Promise<boolean>(resolve => state.onFindReplaceStateChange(e => {
@@ -132,13 +132,13 @@ suite('Notebook Find', () => {
assert.strictEqual(model.findMatches.length, 3);
assert.strictEqual(model.currentMatch, 2);
- model.find(true);
+ model.find({ previous: true });
assert.strictEqual(model.currentMatch, 1);
- model.find(false);
+ model.find({ previous: false });
assert.strictEqual(model.currentMatch, 2);
- model.find(false);
+ model.find({ previous: false });
assert.strictEqual(model.currentMatch, 3);
- model.find(false);
+ model.find({ previous: false });
assert.strictEqual(model.currentMatch, 0);
});
});
@@ -166,7 +166,7 @@ suite('Notebook Find', () => {
// find matches is not necessarily find results
assert.strictEqual(model.findMatches.length, 4);
assert.strictEqual(model.currentMatch, -1);
- model.find(true);
+ model.find({ previous: true });
assert.strictEqual(model.currentMatch, 4);
const found2 = new Promise<boolean>(resolve => state.onFindReplaceStateChange(e => {
@@ -178,9 +178,9 @@ suite('Notebook Find', () => {
await found2;
assert.strictEqual(model.findMatches.length, 3);
assert.strictEqual(model.currentMatch, 0);
- model.find(true);
+ model.find({ previous: true });
assert.strictEqual(model.currentMatch, 3);
- model.find(true);
+ model.find({ previous: true });
assert.strictEqual(model.currentMatch, 2);
});
});
@@ -208,9 +208,9 @@ suite('Notebook Find', () => {
// find matches is not necessarily find results
assert.strictEqual(model.findMatches.length, 4);
assert.strictEqual(model.currentMatch, -1);
- model.find(false);
- model.find(false);
- model.find(false);
+ model.find({ previous: false });
+ model.find({ previous: false });
+ model.find({ previous: false });
assert.strictEqual(model.currentMatch, 2);
const found2 = new Promise<boolean>(resolve => state.onFindReplaceStateChange(e => {
if (e.matchesCount) { resolve(true); }
@@ -244,11 +244,11 @@ suite('Notebook Find', () => {
await found;
assert.strictEqual(model.findMatches.length, 2);
assert.strictEqual(model.currentMatch, -1);
- model.find(false);
+ model.find({ previous: false });
assert.strictEqual(model.currentMatch, 0);
- model.find(false);
+ model.find({ previous: false });
assert.strictEqual(model.currentMatch, 1);
- model.find(false);
+ model.find({ previous: false });
assert.strictEqual(model.currentMatch, 0);
assert.strictEqual(editor.textModel.length, 3);
diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts
index d71a25076b3..124729d29b4 100644
--- a/src/vs/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts
+++ b/src/vs/workbench/contrib/notebook/test/browser/notebookEditorModel.test.ts
@@ -10,7 +10,6 @@ import { isEqual } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { mock } from 'vs/base/test/common/mock';
import { IFileService } from 'vs/platform/files/common/files';
-import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
import { ILabelService } from 'vs/platform/label/common/label';
import { NullLogService } from 'vs/platform/log/common/log';
import { INotificationService } from 'vs/platform/notification/common/notification';
@@ -187,7 +186,6 @@ suite('NotebookFileWorkingCopyModel', function () {
suite('ComplexNotebookEditorModel', function () {
- const instaService = new InstantiationService();
const notebokService = new class extends mock<INotebookService>() { };
const backupService = new class extends mock<IWorkingCopyBackupService>() { };
const notificationService = new class extends mock<INotificationService>() { };
@@ -214,8 +212,8 @@ suite('ComplexNotebookEditorModel', function () {
}
};
- new ComplexNotebookEditorModel(r1, 'fff', notebookDataProvider, instaService, notebokService, workingCopyService, backupService, fileService, notificationService, new NullLogService(), untitledTextEditorService, labelService);
- new ComplexNotebookEditorModel(r2, 'fff', notebookDataProvider, instaService, notebokService, workingCopyService, backupService, fileService, notificationService, new NullLogService(), untitledTextEditorService, labelService);
+ new ComplexNotebookEditorModel(r1, 'fff', notebookDataProvider, notebokService, workingCopyService, backupService, fileService, notificationService, new NullLogService(), untitledTextEditorService, labelService);
+ new ComplexNotebookEditorModel(r2, 'fff', notebookDataProvider, notebokService, workingCopyService, backupService, fileService, notificationService, new NullLogService(), untitledTextEditorService, labelService);
assert.strictEqual(copies.length, 2);
assert.strictEqual(!isEqual(copies[0].resource, copies[1].resource), true);
diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts
index 6f65268830f..8d3bff7a83a 100644
--- a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts
+++ b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionService.test.ts
@@ -20,7 +20,7 @@ import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewMod
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { CellKind, IOutputDto, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
-import { INotebookKernel, INotebookKernelService, ISelectedNotebooksChangeEvent } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
+import { INotebookKernelService, IResolvedNotebookKernel, ISelectedNotebooksChangeEvent, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { setupInstantiationService, withTestNotebook as _withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor';
@@ -166,7 +166,8 @@ suite('NotebookExecutionService', () => {
});
});
-class TestNotebookKernel implements INotebookKernel {
+class TestNotebookKernel implements IResolvedNotebookKernel {
+ type: NotebookKernelType.Resolved = NotebookKernelType.Resolved;
id: string = 'test';
label: string = '';
viewType = '*';
diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts
index 887a4a05443..e5b7f84b8bf 100644
--- a/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts
+++ b/src/vs/workbench/contrib/notebook/test/browser/notebookExecutionStateService.test.ts
@@ -21,7 +21,7 @@ import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/no
import { CellEditType, CellKind, CellUri, IOutputDto, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookExecutionService } from 'vs/workbench/contrib/notebook/common/notebookExecutionService';
import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
-import { INotebookKernel, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
+import { INotebookKernelService, IResolvedNotebookKernel, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { setupInstantiationService, withTestNotebook as _withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor';
@@ -170,7 +170,8 @@ suite('NotebookExecutionStateService', () => {
});
});
-class TestNotebookKernel implements INotebookKernel {
+class TestNotebookKernel implements IResolvedNotebookKernel {
+ type: NotebookKernelType.Resolved = NotebookKernelType.Resolved;
id: string = 'test';
label: string = '';
viewType = '*';
diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts
index b1ebabc1db2..62d6afecb8b 100644
--- a/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts
+++ b/src/vs/workbench/contrib/notebook/test/browser/notebookKernelService.test.ts
@@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { setupInstantiationService, withTestNotebook as _withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor';
import { Emitter, Event } from 'vs/base/common/event';
-import { INotebookKernel, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
+import { INotebookKernelService, IResolvedNotebookKernel, NotebookKernelType } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
import { NotebookKernelService } from 'vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { mock } from 'vs/base/test/common/mock';
@@ -159,7 +159,8 @@ suite('NotebookKernelService', () => {
});
});
-class TestNotebookKernel implements INotebookKernel {
+class TestNotebookKernel implements IResolvedNotebookKernel {
+ type: NotebookKernelType.Resolved = NotebookKernelType.Resolved;
id: string = Math.random() + 'kernel';
label: string = 'test-label';
viewType = '*';
diff --git a/src/vs/workbench/contrib/output/browser/logViewer.ts b/src/vs/workbench/contrib/output/browser/logViewer.ts
index a542e7dbe91..3c78aa114d1 100644
--- a/src/vs/workbench/contrib/output/browser/logViewer.ts
+++ b/src/vs/workbench/contrib/output/browser/logViewer.ts
@@ -15,8 +15,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
import { TextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput';
import { URI } from 'vs/base/common/uri';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
-import { LOG_SCHEME } from 'vs/workbench/contrib/output/common/output';
-import { IFileOutputChannelDescriptor } from 'vs/workbench/services/output/common/output';
+import { LOG_SCHEME, IFileOutputChannelDescriptor } from 'vs/workbench/services/output/common/output';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
diff --git a/src/vs/workbench/contrib/output/browser/output.contribution.ts b/src/vs/workbench/contrib/output/browser/output.contribution.ts
index b6dbbb8caf7..f9e4814794e 100644
--- a/src/vs/workbench/contrib/output/browser/output.contribution.ts
+++ b/src/vs/workbench/contrib/output/browser/output.contribution.ts
@@ -12,7 +12,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
import { MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/actions';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { OutputService, LogContentProvider } from 'vs/workbench/contrib/output/browser/outputServices';
-import { OUTPUT_MODE_ID, OUTPUT_MIME, OUTPUT_VIEW_ID, IOutputService, CONTEXT_IN_OUTPUT, LOG_SCHEME, LOG_MODE_ID, LOG_MIME, CONTEXT_ACTIVE_LOG_OUTPUT, CONTEXT_OUTPUT_SCROLL_LOCK } from 'vs/workbench/contrib/output/common/output';
+import { OUTPUT_MODE_ID, OUTPUT_MIME, OUTPUT_VIEW_ID, IOutputService, CONTEXT_IN_OUTPUT, LOG_SCHEME, LOG_MODE_ID, LOG_MIME, CONTEXT_ACTIVE_LOG_OUTPUT, CONTEXT_OUTPUT_SCROLL_LOCK, IOutputChannelDescriptor, IFileOutputChannelDescriptor } from 'vs/workbench/services/output/common/output';
import { OutputViewPane } from 'vs/workbench/contrib/output/browser/outputView';
import { IEditorPaneRegistry, EditorPaneDescriptor } from 'vs/workbench/browser/editor';
import { LogViewer, LogViewerInput } from 'vs/workbench/contrib/output/browser/logViewer';
@@ -25,7 +25,6 @@ import { ViewContainer, IViewContainersRegistry, ViewContainerLocation, Extensio
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
-import { IOutputChannelDescriptor, IFileOutputChannelDescriptor } from 'vs/workbench/services/output/common/output';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { assertIsDefined } from 'vs/base/common/types';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
diff --git a/src/vs/workbench/contrib/output/browser/outputLinkProvider.ts b/src/vs/workbench/contrib/output/browser/outputLinkProvider.ts
index eb346086655..ca06b37e21f 100644
--- a/src/vs/workbench/contrib/output/browser/outputLinkProvider.ts
+++ b/src/vs/workbench/contrib/output/browser/outputLinkProvider.ts
@@ -8,7 +8,7 @@ import { RunOnceScheduler } from 'vs/base/common/async';
import { IModelService } from 'vs/editor/common/services/model';
import { ILink } from 'vs/editor/common/languages';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
-import { OUTPUT_MODE_ID, LOG_MODE_ID } from 'vs/workbench/contrib/output/common/output';
+import { OUTPUT_MODE_ID, LOG_MODE_ID } from 'vs/workbench/services/output/common/output';
import { MonacoWebWorker, createWebWorker } from 'vs/editor/browser/services/webWorker';
import { ICreateData, OutputLinkComputer } from 'vs/workbench/contrib/output/common/outputLinkComputer';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
diff --git a/src/vs/workbench/contrib/output/browser/outputServices.ts b/src/vs/workbench/contrib/output/browser/outputServices.ts
index 4901c9f23ce..dffb7046911 100644
--- a/src/vs/workbench/contrib/output/browser/outputServices.ts
+++ b/src/vs/workbench/contrib/output/browser/outputServices.ts
@@ -9,8 +9,7 @@ import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { Registry } from 'vs/platform/registry/common/platform';
-import { IOutputChannel, IOutputService, OUTPUT_VIEW_ID, OUTPUT_SCHEME, LOG_SCHEME, LOG_MIME, OUTPUT_MIME, OutputChannelUpdateMode } from 'vs/workbench/contrib/output/common/output';
-import { IOutputChannelDescriptor, Extensions, IOutputChannelRegistry } from 'vs/workbench/services/output/common/output';
+import { IOutputChannel, IOutputService, OUTPUT_VIEW_ID, OUTPUT_SCHEME, LOG_SCHEME, LOG_MIME, OUTPUT_MIME, OutputChannelUpdateMode, IOutputChannelDescriptor, Extensions, IOutputChannelRegistry } from 'vs/workbench/services/output/common/output';
import { OutputLinkProvider } from 'vs/workbench/contrib/output/browser/outputLinkProvider';
import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService';
import { ITextModel } from 'vs/editor/common/model';
diff --git a/src/vs/workbench/contrib/output/browser/outputView.ts b/src/vs/workbench/contrib/output/browser/outputView.ts
index ee9c7eadf5d..545d41fdcee 100644
--- a/src/vs/workbench/contrib/output/browser/outputView.ts
+++ b/src/vs/workbench/contrib/output/browser/outputView.ts
@@ -14,7 +14,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IEditorOpenContext } from 'vs/workbench/common/editor';
import { AbstractTextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor';
-import { OUTPUT_VIEW_ID, IOutputService, CONTEXT_IN_OUTPUT, IOutputChannel, CONTEXT_ACTIVE_LOG_OUTPUT, CONTEXT_OUTPUT_SCROLL_LOCK } from 'vs/workbench/contrib/output/common/output';
+import { OUTPUT_VIEW_ID, IOutputService, CONTEXT_IN_OUTPUT, IOutputChannel, CONTEXT_ACTIVE_LOG_OUTPUT, CONTEXT_OUTPUT_SCROLL_LOCK, IOutputChannelDescriptor, IOutputChannelRegistry, Extensions } from 'vs/workbench/services/output/common/output';
import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
@@ -27,7 +27,6 @@ import { IContextMenuService, IContextViewService } from 'vs/platform/contextvie
import { IViewDescriptorService } from 'vs/workbench/common/views';
import { TextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput';
import { IOpenerService } from 'vs/platform/opener/common/opener';
-import { IOutputChannelDescriptor, IOutputChannelRegistry, Extensions } from 'vs/workbench/services/output/common/output';
import { Registry } from 'vs/platform/registry/common/platform';
import { attachSelectBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler';
import { ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox';
diff --git a/src/vs/workbench/contrib/output/common/outputChannelModel.ts b/src/vs/workbench/contrib/output/common/outputChannelModel.ts
index cb24f441e21..4c01e67797c 100644
--- a/src/vs/workbench/contrib/output/common/outputChannelModel.ts
+++ b/src/vs/workbench/contrib/output/common/outputChannelModel.ts
@@ -21,7 +21,7 @@ import { Range } from 'vs/editor/common/core/range';
import { VSBuffer } from 'vs/base/common/buffer';
import { ILogger, ILoggerService, ILogService } from 'vs/platform/log/common/log';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
-import { OutputChannelUpdateMode } from 'vs/workbench/contrib/output/common/output';
+import { OutputChannelUpdateMode } from 'vs/workbench/services/output/common/output';
export interface IOutputChannelModel extends IDisposable {
readonly onDispose: Event<void>;
diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css
index 7145fe94aa9..a09f097b305 100644
--- a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css
+++ b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css
@@ -39,7 +39,7 @@
.settings-editor > .settings-header > .search-container > .settings-count-widget {
position: absolute;
- right: 35px;
+ right: 46px;
top: 0px;
margin: 4px 0px;
}
@@ -55,7 +55,7 @@
top: 0;
right: 0;
height: 100%;
- width: 30px;
+ width: 43px;
}
.settings-editor > .settings-header > .search-container > .settings-clear-widget .action-label {
diff --git a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts
index 42365a36d4f..b5230a5af22 100644
--- a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts
+++ b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts
@@ -34,7 +34,7 @@ import { ConfigureLanguageBasedSettingsAction } from 'vs/workbench/contrib/prefe
import { SettingsEditorContribution } from 'vs/workbench/contrib/preferences/browser/preferencesEditor';
import { preferencesOpenSettingsIcon } from 'vs/workbench/contrib/preferences/browser/preferencesIcons';
import { SettingsEditor2, SettingsFocusContext } from 'vs/workbench/contrib/preferences/browser/settingsEditor2';
-import { CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS, CONTEXT_KEYBINDING_FOCUS, CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_JSON_EDITOR, CONTEXT_SETTINGS_ROW_FOCUS, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, CONTEXT_WHEN_FOCUS, KEYBINDINGS_EDITOR_COMMAND_ADD, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, KEYBINDINGS_EDITOR_COMMAND_COPY, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND_TITLE, KEYBINDINGS_EDITOR_COMMAND_DEFINE, KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN, KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS, KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, KEYBINDINGS_EDITOR_COMMAND_REMOVE, KEYBINDINGS_EDITOR_COMMAND_RESET, KEYBINDINGS_EDITOR_COMMAND_SEARCH, KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR, KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE, KEYBINDINGS_EDITOR_SHOW_DEFAULT_KEYBINDINGS, KEYBINDINGS_EDITOR_SHOW_EXTENSION_KEYBINDINGS, KEYBINDINGS_EDITOR_SHOW_USER_KEYBINDINGS, MODIFIED_SETTING_TAG, REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU } from 'vs/workbench/contrib/preferences/common/preferences';
+import { CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS, CONTEXT_KEYBINDING_FOCUS, CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_JSON_EDITOR, CONTEXT_SETTINGS_ROW_FOCUS, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, CONTEXT_WHEN_FOCUS, KEYBINDINGS_EDITOR_COMMAND_ADD, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, KEYBINDINGS_EDITOR_COMMAND_COPY, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND_TITLE, KEYBINDINGS_EDITOR_COMMAND_DEFINE, KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN, KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS, KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, KEYBINDINGS_EDITOR_COMMAND_REMOVE, KEYBINDINGS_EDITOR_COMMAND_RESET, KEYBINDINGS_EDITOR_COMMAND_SEARCH, KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR, KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE, KEYBINDINGS_EDITOR_SHOW_DEFAULT_KEYBINDINGS, KEYBINDINGS_EDITOR_SHOW_EXTENSION_KEYBINDINGS, KEYBINDINGS_EDITOR_SHOW_USER_KEYBINDINGS, REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU } from 'vs/workbench/contrib/preferences/common/preferences';
import { PreferencesContribution } from 'vs/workbench/contrib/preferences/common/preferencesContribution';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
@@ -54,7 +54,6 @@ const SETTINGS_EDITOR_COMMAND_FOCUS_CONTROL = 'settings.action.focusSettingContr
const SETTINGS_EDITOR_COMMAND_FOCUS_UP = 'settings.action.focusLevelUp';
const SETTINGS_EDITOR_COMMAND_SWITCH_TO_JSON = 'settings.switchToJSON';
-const SETTINGS_EDITOR_COMMAND_FILTER_MODIFIED = 'settings.filterByModified';
const SETTINGS_EDITOR_COMMAND_FILTER_ONLINE = 'settings.filterByOnline';
const SETTINGS_EDITOR_COMMAND_FILTER_TELEMETRY = 'settings.filterByTelemetry';
const SETTINGS_EDITOR_COMMAND_FILTER_UNTRUSTED = 'settings.filterUntrusted';
@@ -396,33 +395,12 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon
registerAction2(class extends Action2 {
constructor() {
super({
- id: SETTINGS_EDITOR_COMMAND_FILTER_MODIFIED,
- title: { value: nls.localize('filterModifiedLabel', "Show modified settings"), original: 'Show modified settings' },
- menu: {
- id: MenuId.EditorTitle,
- group: '1_filter',
- order: 1,
- when: ContextKeyExpr.and(CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_JSON_EDITOR.toNegated())
- }
- });
- }
- run(accessor: ServicesAccessor, resource: URI) {
- const editorPane = accessor.get(IEditorService).activeEditorPane;
- if (editorPane instanceof SettingsEditor2) {
- editorPane.focusSearch(`@${MODIFIED_SETTING_TAG}`);
- }
- }
- });
- registerAction2(class extends Action2 {
- constructor() {
- super({
id: SETTINGS_EDITOR_COMMAND_FILTER_ONLINE,
- title: { value: nls.localize('filterOnlineServicesLabel', "Show settings for online services"), original: 'Show settings for online services' },
+ title: nls.localize({ key: 'miOpenOnlineSettings', comment: ['&& denotes a mnemonic'] }, "&&Online Services Settings"),
menu: {
- id: MenuId.EditorTitle,
- group: '1_filter',
+ id: MenuId.MenubarPreferencesMenu,
+ group: '1_settings',
order: 2,
- when: ContextKeyExpr.and(CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_JSON_EDITOR.toNegated())
}
});
}
@@ -435,22 +413,6 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon
}
}
});
- MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, {
- group: '1_settings',
- command: {
- id: SETTINGS_EDITOR_COMMAND_FILTER_ONLINE,
- title: nls.localize({ key: 'miOpenOnlineSettings', comment: ['&& denotes a mnemonic'] }, "&&Online Services Settings")
- },
- order: 2
- });
- MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
- group: '2_configuration',
- command: {
- id: SETTINGS_EDITOR_COMMAND_FILTER_ONLINE,
- title: nls.localize('onlineServices', "Online Services Settings")
- },
- order: 2
- });
registerAction2(class extends Action2 {
constructor() {
diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesIcons.ts b/src/vs/workbench/contrib/preferences/browser/preferencesIcons.ts
index 6692bf47bd4..0c31b455aaf 100644
--- a/src/vs/workbench/contrib/preferences/browser/preferencesIcons.ts
+++ b/src/vs/workbench/contrib/preferences/browser/preferencesIcons.ts
@@ -25,4 +25,5 @@ export const settingsRemoveIcon = registerIcon('settings-remove', Codicon.close,
export const settingsDiscardIcon = registerIcon('settings-discard', Codicon.discard, localize('preferencesDiscardIcon', 'Icon for the discard action in the Settings UI.'));
export const preferencesClearInputIcon = registerIcon('preferences-clear-input', Codicon.clearAll, localize('preferencesClearInput', 'Icon for clear input in the Settings and keybinding UI.'));
+export const preferencesFilterIcon = registerIcon('preferences-filter', Codicon.filter, localize('settingsFilter', 'Icon for the button that suggests filters for the Settings UI.'));
export const preferencesOpenSettingsIcon = registerIcon('preferences-open-settings', Codicon.goToFile, localize('preferencesOpenSettings', 'Icon for open settings commands.'));
diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts b/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts
index fbddbef9008..8f833f178f6 100644
--- a/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts
+++ b/src/vs/workbench/contrib/preferences/browser/preferencesSearch.ts
@@ -19,7 +19,7 @@ import { IExtensionManagementService, ILocalExtension } from 'vs/platform/extens
import { IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { ILogService } from 'vs/platform/log/common/log';
import { CancellationToken } from 'vs/base/common/cancellation';
-import { canceled } from 'vs/base/common/errors';
+import { CancellationError } from 'vs/base/common/errors';
import { ExtensionType } from 'vs/platform/extensions/common/extensions';
import { nullRange } from 'vs/workbench/services/preferences/common/preferencesModels';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@@ -191,7 +191,7 @@ class RemoteSearchProvider implements ISearchProvider {
}
if (token && token.isCancellationRequested) {
- throw canceled();
+ throw new CancellationError();
}
const resultKeys = Object.keys(remoteResult.scoredResults);
@@ -379,7 +379,7 @@ class RemoteSearchProvider implements ISearchProvider {
const hasMoreFilters = filters.length > (filterPage + 1) * RemoteSearchProvider.MAX_REQUEST_FILTERS;
const body = JSON.stringify({
- query: encodedQuery,
+ search: encodedQuery,
filters: encodeURIComponent(filterStr),
rawQuery: encodeURIComponent(verbatimQuery)
});
diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts
index 9ec6a0b1083..4c942330f52 100644
--- a/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts
+++ b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts
@@ -36,6 +36,7 @@ import { PANEL_ACTIVE_TITLE_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIV
import { settingsEditIcon, settingsScopeDropDownIcon } from 'vs/workbench/contrib/preferences/browser/preferencesIcons';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
+import { ILanguageService } from 'vs/editor/common/languages/language';
export class FolderSettingsActionViewItem extends BaseActionViewItem {
@@ -216,6 +217,7 @@ export class SettingsTargetsWidget extends Widget {
private userLocalSettings!: Action;
private userRemoteSettings!: Action;
private workspaceSettings!: Action;
+ private folderSettingsAction!: Action;
private folderSettings!: FolderSettingsActionViewItem;
private options: ISettingsTargetsWidgetOptions;
@@ -232,6 +234,7 @@ export class SettingsTargetsWidget extends Widget {
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
@ILabelService private readonly labelService: ILabelService,
@IPreferencesService private readonly preferencesService: IPreferencesService,
+ @ILanguageService private readonly languageService: ILanguageService,
) {
super();
this.options = options || {};
@@ -240,6 +243,15 @@ export class SettingsTargetsWidget extends Widget {
this._register(this.contextService.onDidChangeWorkspaceFolders(() => this.update()));
}
+ private resetLabels() {
+ const remoteAuthority = this.environmentService.remoteAuthority;
+ const hostLabel = remoteAuthority && this.labelService.getHostLabel(Schemas.vscodeRemote, remoteAuthority);
+ this.userLocalSettings.label = localize('userSettings', "User");
+ this.userRemoteSettings.label = localize('userSettingsRemote', "Remote") + (hostLabel ? ` [${hostLabel}]` : '');
+ this.workspaceSettings.label = localize('workspaceSettings', "Workspace");
+ this.folderSettingsAction.label = localize('folderSettings', "Folder");
+ }
+
private create(parent: HTMLElement): void {
const settingsTabsWidget = DOM.append(parent, DOM.$('.settings-tabs-widget'));
this.settingsSwitcherBar = this._register(new ActionBar(settingsTabsWidget, {
@@ -250,31 +262,28 @@ export class SettingsTargetsWidget extends Widget {
actionViewItemProvider: (action: IAction) => action.id === 'folderSettings' ? this.folderSettings : undefined
}));
- this.userLocalSettings = new Action('userSettings', localize('userSettings', "User"), '.settings-tab', true, () => this.updateTarget(ConfigurationTarget.USER_LOCAL));
+ this.userLocalSettings = new Action('userSettings', '', '.settings-tab', true, () => this.updateTarget(ConfigurationTarget.USER_LOCAL));
this.preferencesService.getEditableSettingsURI(ConfigurationTarget.USER_LOCAL).then(uri => {
// Don't wait to create UI on resolving remote
this.userLocalSettings.tooltip = uri?.fsPath || '';
});
- const remoteAuthority = this.environmentService.remoteAuthority;
- const hostLabel = remoteAuthority && this.labelService.getHostLabel(Schemas.vscodeRemote, remoteAuthority);
- const remoteSettingsLabel = localize('userSettingsRemote', "Remote") +
- (hostLabel ? ` [${hostLabel}]` : '');
- this.userRemoteSettings = new Action('userSettingsRemote', remoteSettingsLabel, '.settings-tab', true, () => this.updateTarget(ConfigurationTarget.USER_REMOTE));
+ this.userRemoteSettings = new Action('userSettingsRemote', '', '.settings-tab', true, () => this.updateTarget(ConfigurationTarget.USER_REMOTE));
this.preferencesService.getEditableSettingsURI(ConfigurationTarget.USER_REMOTE).then(uri => {
this.userRemoteSettings.tooltip = uri?.fsPath || '';
});
- this.workspaceSettings = new Action('workspaceSettings', localize('workspaceSettings', "Workspace"), '.settings-tab', false, () => this.updateTarget(ConfigurationTarget.WORKSPACE));
+ this.workspaceSettings = new Action('workspaceSettings', '', '.settings-tab', false, () => this.updateTarget(ConfigurationTarget.WORKSPACE));
- const folderSettingsAction = new Action('folderSettings', localize('folderSettings', "Folder"), '.settings-tab', false, async folder => {
+ this.folderSettingsAction = new Action('folderSettings', '', '.settings-tab', false, async folder => {
this.updateTarget(isWorkspaceFolder(folder) ? folder.uri : ConfigurationTarget.USER_LOCAL);
});
- this.folderSettings = this.instantiationService.createInstance(FolderSettingsActionViewItem, folderSettingsAction);
+ this.folderSettings = this.instantiationService.createInstance(FolderSettingsActionViewItem, this.folderSettingsAction);
+ this.resetLabels();
this.update();
- this.settingsSwitcherBar.push([this.userLocalSettings, this.userRemoteSettings, this.workspaceSettings, folderSettingsAction]);
+ this.settingsSwitcherBar.push([this.userLocalSettings, this.userRemoteSettings, this.workspaceSettings, this.folderSettingsAction]);
}
get settingsTarget(): SettingsTarget | null {
@@ -314,6 +323,19 @@ export class SettingsTargetsWidget extends Widget {
}
}
+ updateLanguageFilterIndicators(filter: string | undefined) {
+ this.resetLabels();
+ if (filter) {
+ const languageToUse = this.languageService.getLanguageName(filter);
+ if (languageToUse) {
+ this.userLocalSettings.label += ` [${languageToUse}]`;
+ this.userRemoteSettings.label += ` [${languageToUse}]`;
+ this.workspaceSettings.label += ` [${languageToUse}]`;
+ this.folderSettingsAction.label += ` [${languageToUse}]`;
+ }
+ }
+ }
+
private onWorkbenchStateChanged(): void {
this.folderSettings.folder = null;
this.update();
diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts
index 77942bbbf0a..abdaa391f5a 100644
--- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts
+++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts
@@ -43,14 +43,14 @@ import { commonlyUsedData, tocData } from 'vs/workbench/contrib/preferences/brow
import { AbstractSettingRenderer, HeightChangeParams, ISettingLinkClickEvent, ISettingOverrideClickEvent, resolveConfiguredUntrustedSettings, createTocTreeForExtensionSettings, resolveSettingsTree, SettingsTree, SettingTreeRenderers } from 'vs/workbench/contrib/preferences/browser/settingsTree';
import { ISettingsEditorViewState, parseQuery, SearchResultIdx, SearchResultModel, SettingsTreeElement, SettingsTreeGroupChild, SettingsTreeGroupElement, SettingsTreeModel, SettingsTreeSettingElement } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels';
import { createTOCIterator, TOCTree, TOCTreeModel } from 'vs/workbench/contrib/preferences/browser/tocTree';
-import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_ROW_FOCUS, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, ENABLE_LANGUAGE_FILTER, EXTENSION_SETTING_TAG, FEATURE_SETTING_TAG, ID_SETTING_TAG, IPreferencesSearchService, ISearchProvider, LANGUAGE_SETTING_TAG, MODIFIED_SETTING_TAG, REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, WORKSPACE_TRUST_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences';
+import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_ROW_FOCUS, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, ENABLE_LANGUAGE_FILTER, EXTENSION_SETTING_TAG, FEATURE_SETTING_TAG, ID_SETTING_TAG, IPreferencesSearchService, ISearchProvider, LANGUAGE_SETTING_TAG, MODIFIED_SETTING_TAG, REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_SUGGEST_FILTERS, WORKSPACE_TRUST_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences';
import { settingsHeaderBorder, settingsSashBorder, settingsTextInputBorder } from 'vs/workbench/contrib/preferences/common/settingsEditorColorRegistry';
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IOpenSettingsOptions, IPreferencesService, ISearchResult, ISettingsEditorModel, ISettingsEditorOptions, SettingMatchType, SettingValueType, validateSettingsEditorOptions } from 'vs/workbench/services/preferences/common/preferences';
import { SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput';
import { Settings2EditorModel } from 'vs/workbench/services/preferences/common/preferencesModels';
import { IUserDataSyncWorkbenchService } from 'vs/workbench/services/userDataSync/common/userDataSync';
-import { preferencesClearInputIcon } from 'vs/workbench/contrib/preferences/browser/preferencesIcons';
+import { preferencesClearInputIcon, preferencesFilterIcon } from 'vs/workbench/contrib/preferences/browser/preferencesIcons';
import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust';
import { IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration';
@@ -58,6 +58,8 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten
import { Orientation, Sizing, SplitView } from 'vs/base/browser/ui/splitview/splitview';
import { Color } from 'vs/base/common/color';
import { ILanguageService } from 'vs/editor/common/languages/language';
+import { SettingsSearchFilterDropdownMenuActionViewItem } from 'vs/workbench/contrib/preferences/browser/settingsSearchMenu';
+import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
export const enum SettingsFocusContext {
Search,
@@ -201,6 +203,8 @@ export class SettingsEditor2 extends EditorPane {
private settingsTreeScrollTop = 0;
private dimension!: DOM.Dimension;
+ private installedExtensionIds: string[] = [];
+
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IWorkbenchConfigurationService private readonly configurationService: IWorkbenchConfigurationService,
@@ -217,7 +221,8 @@ export class SettingsEditor2 extends EditorPane {
@IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService,
@IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService,
@IExtensionService private readonly extensionService: IExtensionService,
- @ILanguageService private readonly languageService: ILanguageService
+ @ILanguageService private readonly languageService: ILanguageService,
+ @IExtensionManagementService extensionManagementService: IExtensionManagementService
) {
super(SettingsEditor2.ID, telemetryService, themeService, storageService);
this.delayedFilterLogging = new Delayer<void>(1000);
@@ -268,6 +273,12 @@ export class SettingsEditor2 extends EditorPane {
if (ENABLE_LANGUAGE_FILTER && !SettingsEditor2.SUGGESTIONS.includes(`@${LANGUAGE_SETTING_TAG}`)) {
SettingsEditor2.SUGGESTIONS.push(`@${LANGUAGE_SETTING_TAG}`);
}
+
+ extensionManagementService.getInstalled().then(extensions => {
+ this.installedExtensionIds = extensions
+ .filter(ext => ext.manifest && ext.manifest.contributes && ext.manifest.contributes.configuration)
+ .map(ext => ext.identifier.id);
+ });
}
override get minimumWidth(): number { return SettingsEditor2.EDITOR_MIN_WIDTH; }
@@ -499,11 +510,11 @@ export class SettingsEditor2 extends EditorPane {
clearSearchFilters(): void {
let query = this.searchWidget.getValue();
- SettingsEditor2.SUGGESTIONS.forEach(suggestion => {
- query = query.replace(suggestion, '');
+ const splitQuery = query.split(' ').filter(word => {
+ return word.length && !SettingsEditor2.SUGGESTIONS.some(suggestion => word.startsWith(suggestion));
});
- this.searchWidget.setValue(query.trim());
+ this.searchWidget.setValue(splitQuery.join(' '));
}
private updateInputAriaLabel() {
@@ -525,7 +536,7 @@ export class SettingsEditor2 extends EditorPane {
const searchContainer = DOM.append(this.headerContainer, $('.search-container'));
const clearInputAction = new Action(SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, localize('clearInput', "Clear Settings Search Input"), ThemeIcon.asClassName(preferencesClearInputIcon), false, async () => this.clearSearchResults());
-
+ const filterAction = new Action(SETTINGS_EDITOR_COMMAND_SUGGEST_FILTERS, localize('filterInput', "Filter Settings"), ThemeIcon.asClassName(preferencesFilterIcon));
this.searchWidget = this._register(this.instantiationService.createInstance(SuggestEnabledInput, `${SettingsEditor2.ID}.searchbox`, searchContainer, {
triggerCharacters: ['@', ':'],
provideResults: (query: string) => {
@@ -533,9 +544,15 @@ export class SettingsEditor2 extends EditorPane {
// for the ':' trigger, only return suggestions if there was a '@' before it in the same word.
const queryParts = query.split(/\s/g);
if (queryParts[queryParts.length - 1].startsWith(`@${LANGUAGE_SETTING_TAG}`)) {
- return this.languageService.getRegisteredLanguageIds().map(languageId => {
+ const sortedLanguages = this.languageService.getRegisteredLanguageIds().map(languageId => {
return `@${LANGUAGE_SETTING_TAG}${languageId} `;
}).sort();
+ return sortedLanguages.filter(langFilter => !query.includes(langFilter));
+ } else if (queryParts[queryParts.length - 1].startsWith(`@${EXTENSION_SETTING_TAG}`)) {
+ const installedExtensionsTags = this.installedExtensionIds.map(extensionId => {
+ return `@${EXTENSION_SETTING_TAG}${extensionId} `;
+ }).sort();
+ return installedExtensionsTags.filter(extFilter => !query.includes(extFilter));
} else if (queryParts[queryParts.length - 1].startsWith('@')) {
return SettingsEditor2.SUGGESTIONS.filter(tag => !query.includes(tag)).map(tag => tag.endsWith(':') ? tag : tag + ' ');
}
@@ -603,10 +620,15 @@ export class SettingsEditor2 extends EditorPane {
const actionBar = this._register(new ActionBar(this.controlsElement, {
animated: false,
- actionViewItemProvider: (_action) => { return undefined; }
+ actionViewItemProvider: (action) => {
+ if (action.id === filterAction.id) {
+ return this.instantiationService.createInstance(SettingsSearchFilterDropdownMenuActionViewItem, action, this.actionRunner, this.searchWidget);
+ }
+ return undefined;
+ }
}));
- actionBar.push([clearInputAction], { label: false, icon: true });
+ actionBar.push([clearInputAction, filterAction], { label: false, icon: true });
}
private onDidSettingsTargetChange(target: SettingsTarget): void {
@@ -831,7 +853,11 @@ export class SettingsEditor2 extends EditorPane {
}
}));
this._register(this.settingRenderers.onApplyLanguageFilter((lang: string) => {
- this.focusSearch(`@${LANGUAGE_SETTING_TAG}${lang}`);
+ if (this.searchWidget) {
+ // Prepend the language filter to the query.
+ const newQuery = `@${LANGUAGE_SETTING_TAG}${lang} ${this.searchWidget.getValue().trimStart()}`;
+ this.focusSearch(newQuery, false);
+ }
}));
this.settingsTree = this._register(this.instantiationService.createInstance(SettingsTree,
@@ -1018,13 +1044,15 @@ export class SettingsEditor2 extends EditorPane {
target: string;
};
type SettingsEditorModifiedSettingClassification = {
- key: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; owner: 'rzhao271'; comment: 'The setting that is being modified.' };
- groupId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; owner: 'rzhao271'; comment: 'Whether the setting is from the local search or remote search provider, if applicable.' };
- nlpIndex: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; owner: 'rzhao271'; comment: 'The index of the setting in the remote search provider results, if applicable.' };
- displayIndex: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; owner: 'rzhao271'; comment: 'The index of the setting in the combined search results, if applicable.' };
- showConfiguredOnly: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; 'owner': 'rzhao271'; comment: 'Whether the user is in the modified view, which shows configured settings only.' };
- isReset: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; owner: 'rzhao271'; comment: 'Identifies whether a setting was reset to its default value.' };
- target: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; owner: 'rzhao271'; comment: 'The scope of the setting, such as user or workspace.' };
+ key: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The setting that is being modified.' };
+ groupId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the setting is from the local search or remote search provider, if applicable.' };
+ nlpIndex: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The index of the setting in the remote search provider results, if applicable.' };
+ displayIndex: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The index of the setting in the combined search results, if applicable.' };
+ showConfiguredOnly: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the user is in the modified view, which shows configured settings only.' };
+ isReset: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Identifies whether a setting was reset to its default value.' };
+ target: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The scope of the setting, such as user or workspace.' };
+ owner: 'rzhao271';
+ comment: 'Event which fires when the user modifies a setting in the settings editor';
};
this.pendingSettingUpdate = null;
@@ -1304,6 +1332,8 @@ export class SettingsEditor2 extends EditorPane {
this.viewState.languageFilter = parsedQuery.languageFilter;
}
+ this.settingsTargetsWidget.updateLanguageFilterIndicators(this.viewState.languageFilter);
+
if (query && query !== '@') {
query = this.parseSettingFromJSON(query) || query;
return this.triggerFilterPreferences(query);
@@ -1376,10 +1406,12 @@ export class SettingsEditor2 extends EditorPane {
'requestCount': number | undefined;
};
type SettingsEditorFilterClassification = {
- 'durations.nlpResult': { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; owner: 'rzhao271'; 'comment': 'How long the remote search provider took, if applicable.' };
- 'counts.nlpResult': { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; owner: 'rzhao271'; 'comment': 'The number of matches found by the remote search provider, if applicable.' };
- 'counts.filterResult': { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; owner: 'rzhao271'; 'comment': 'The number of matches found by the local search provider, if applicable.' };
- 'requestCount': { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; owner: 'rzhao271'; 'comment': 'The number of requests sent to Bing, if applicable.' };
+ 'durations.nlpResult': { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; 'comment': 'How long the remote search provider took, if applicable.' };
+ 'counts.nlpResult': { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; 'comment': 'The number of matches found by the remote search provider, if applicable.' };
+ 'counts.filterResult': { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; 'comment': 'The number of matches found by the local search provider, if applicable.' };
+ 'requestCount': { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; 'comment': 'The number of requests sent to Bing, if applicable.' };
+ owner: 'rzhao271';
+ comment: 'Tracks the number of requests and performance of the built-in search providers';
};
const nlpResult = results[SearchResultIdx.Remote];
diff --git a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts
index d631ff74b4a..9d2c8958ba0 100644
--- a/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts
+++ b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts
@@ -145,7 +145,7 @@ export const tocData: ITOCEntry<string> = {
},
{
id: 'features/scm',
- label: localize('scm', "SCM"),
+ label: localize('scm', "Source Control"),
settings: ['scm.*']
},
{
diff --git a/src/vs/workbench/contrib/preferences/browser/settingsSearchMenu.ts b/src/vs/workbench/contrib/preferences/browser/settingsSearchMenu.ts
new file mode 100644
index 00000000000..e39d23eb58e
--- /dev/null
+++ b/src/vs/workbench/contrib/preferences/browser/settingsSearchMenu.ts
@@ -0,0 +1,141 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
+import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem';
+import { IAction, IActionRunner } from 'vs/base/common/actions';
+import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController';
+import { localize } from 'vs/nls';
+import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
+import { SuggestEnabledInput } from 'vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput';
+import { EXTENSION_SETTING_TAG, FEATURE_SETTING_TAG, GENERAL_TAG_SETTING_TAG, LANGUAGE_SETTING_TAG, MODIFIED_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences';
+
+export class SettingsSearchFilterDropdownMenuActionViewItem extends DropdownMenuActionViewItem {
+ private readonly suggestController: SuggestController | null;
+
+ constructor(
+ action: IAction,
+ actionRunner: IActionRunner | undefined,
+ private readonly searchWidget: SuggestEnabledInput,
+ @IContextMenuService contextMenuService: IContextMenuService
+ ) {
+ super(action,
+ { getActions: () => this.getActions() },
+ contextMenuService,
+ {
+ actionRunner,
+ classNames: action.class,
+ anchorAlignmentProvider: () => AnchorAlignment.RIGHT,
+ menuAsChild: true
+ }
+ );
+
+ this.suggestController = SuggestController.get(this.searchWidget.inputWidget);
+ }
+
+ override render(container: HTMLElement): void {
+ super.render(container);
+ }
+
+ private doSearchWidgetAction(queryToAppend: string, triggerSuggest: boolean) {
+ this.searchWidget.setValue(this.searchWidget.getValue().trimEnd() + ' ' + queryToAppend);
+ this.searchWidget.focus();
+ if (triggerSuggest && this.suggestController) {
+ this.suggestController.triggerSuggest();
+ }
+ }
+
+ /**
+ * The created action appends a query to the search widget search string. It optionally triggers suggestions.
+ */
+ private createAction(id: string, label: string, tooltip: string, queryToAppend: string, triggerSuggest: boolean): IAction {
+ return {
+ id,
+ label,
+ tooltip,
+ class: undefined,
+ enabled: true,
+ checked: false,
+ run: () => { this.doSearchWidgetAction(queryToAppend, triggerSuggest); },
+ dispose: () => { }
+ };
+ }
+
+ /**
+ * The created action appends a query to the search widget search string, if the query does not exist.
+ * Otherwise, it removes the query from the search widget search string.
+ * The action does not trigger suggestions after adding or removing the query.
+ */
+ private createToggleAction(id: string, label: string, tooltip: string, queryToAppend: string): IAction {
+ const splitCurrentQuery = this.searchWidget.getValue().split(' ');
+ const queryContainsQueryToAppend = splitCurrentQuery.includes(queryToAppend);
+ return {
+ id,
+ label,
+ tooltip,
+ class: undefined,
+ enabled: true,
+ checked: queryContainsQueryToAppend,
+ run: () => {
+ if (!queryContainsQueryToAppend) {
+ const trimmedCurrentQuery = this.searchWidget.getValue().trimEnd();
+ const newQuery = trimmedCurrentQuery ? trimmedCurrentQuery + ' ' + queryToAppend : queryToAppend;
+ this.searchWidget.setValue(newQuery);
+ } else {
+ const queryWithRemovedTags = this.searchWidget.getValue().split(' ')
+ .filter(word => word !== queryToAppend).join(' ');
+ this.searchWidget.setValue(queryWithRemovedTags);
+ }
+ this.searchWidget.focus();
+ },
+ dispose: () => { }
+ };
+ }
+
+ getActions(): IAction[] {
+ return [
+ this.createToggleAction(
+ 'modifiedSettingsSearch',
+ localize('modifiedSettingsSearch', "Modified"),
+ localize('modifiedSettingsSearchTooltip', "Add or remove modified settings filter"),
+ `@${MODIFIED_SETTING_TAG}`
+ ),
+ this.createAction(
+ 'extSettingsSearch',
+ localize('extSettingsSearch', "Extension ID..."),
+ localize('extSettingsSearchTooltip', "Add extension ID filter"),
+ `@${EXTENSION_SETTING_TAG}`,
+ true
+ ),
+ this.createAction(
+ 'featuresSettingsSearch',
+ localize('featureSettingsSearch', "Feature..."),
+ localize('featureSettingsSearchTooltip', "Add feature filter"),
+ `@${FEATURE_SETTING_TAG}`,
+ true
+ ),
+ this.createAction(
+ 'tagSettingsSearch',
+ localize('tagSettingsSearch', "Tag..."),
+ localize('tagSettingsSearchTooltip', "Add tag filter"),
+ `@${GENERAL_TAG_SETTING_TAG}`,
+ true
+ ),
+ this.createAction(
+ 'langSettingsSearch',
+ localize('langSettingsSearch', "Language..."),
+ localize('langSettingsSearchTooltip', "Add language ID filter"),
+ `@${LANGUAGE_SETTING_TAG}`,
+ true
+ ),
+ this.createToggleAction(
+ 'onlineSettingsSearch',
+ localize('onlineSettingsSearch', "Online services"),
+ localize('onlineSettingsSearchTooltip', "Show settings for online services"),
+ '@tag:usesOnlineServices'
+ )
+ ];
+ }
+}
diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts
index f4b4b41ad94..7b551ab1466 100644
--- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts
+++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts
@@ -2234,13 +2234,6 @@ export class SettingsTreeFilter implements ITreeFilter<SettingsTreeElement> {
}
}
- // @modified or tag
- if (element instanceof SettingsTreeSettingElement && this.viewState.tagFilters) {
- if (!element.matchesAllTags(this.viewState.tagFilters)) {
- return false;
- }
- }
-
// Group with no visible children
if (element instanceof SettingsTreeGroupElement) {
if (typeof element.count === 'number') {
diff --git a/src/vs/workbench/contrib/preferences/common/preferences.ts b/src/vs/workbench/contrib/preferences/common/preferences.ts
index ec3a14f1bf4..6138098d36f 100644
--- a/src/vs/workbench/contrib/preferences/common/preferences.ts
+++ b/src/vs/workbench/contrib/preferences/common/preferences.ts
@@ -42,6 +42,7 @@ export interface ISearchProvider {
export const SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS = 'settings.action.clearSearchResults';
export const SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU = 'settings.action.showContextMenu';
+export const SETTINGS_EDITOR_COMMAND_SUGGEST_FILTERS = 'settings.action.suggestFilters';
export const CONTEXT_SETTINGS_EDITOR = new RawContextKey<boolean>('inSettingsEditor', false);
export const CONTEXT_SETTINGS_JSON_EDITOR = new RawContextKey<boolean>('inSettingsJSONEditor', false);
@@ -76,6 +77,7 @@ export const EXTENSION_SETTING_TAG = 'ext:';
export const FEATURE_SETTING_TAG = 'feature:';
export const ID_SETTING_TAG = 'id:';
export const LANGUAGE_SETTING_TAG = 'lang:';
+export const GENERAL_TAG_SETTING_TAG = 'tag:';
export const WORKSPACE_TRUST_SETTING_TAG = 'workspaceTrust';
export const REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG = 'requireTrustedWorkspace';
export const KEYBOARD_LAYOUT_OPEN_PICKER = 'workbench.action.openKeyboardLayoutPicker';
diff --git a/src/vs/workbench/contrib/profiles/common/profiles.contribution.ts b/src/vs/workbench/contrib/profiles/common/profiles.contribution.ts
new file mode 100644
index 00000000000..facfa51c4a3
--- /dev/null
+++ b/src/vs/workbench/contrib/profiles/common/profiles.contribution.ts
@@ -0,0 +1,6 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import './profilesActions';
diff --git a/src/vs/workbench/contrib/profiles/common/profilesActions.ts b/src/vs/workbench/contrib/profiles/common/profilesActions.ts
new file mode 100644
index 00000000000..bae901d966d
--- /dev/null
+++ b/src/vs/workbench/contrib/profiles/common/profilesActions.ts
@@ -0,0 +1,136 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+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 { 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';
+import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
+import { asJson, asText, IRequestService } from 'vs/platform/request/common/request';
+import { IProfile, isProfile, IWorkbenchProfileService, PROFILES_CATEGORY, PROFILE_EXTENSION, PROFILE_FILTER } from 'vs/workbench/services/profiles/common/profile';
+import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
+
+registerAction2(class ExportProfileAction extends Action2 {
+ constructor() {
+ super({
+ id: 'workbench.profiles.actions.exportProfile',
+ title: {
+ value: localize('export profile', "Export Settings as a Profile..."),
+ original: 'Export Settings as a Profile...'
+ },
+ category: PROFILES_CATEGORY,
+ f1: true
+ });
+ }
+
+ async run(accessor: ServicesAccessor) {
+ const textFileService = accessor.get(ITextFileService);
+ const fileDialogService = accessor.get(IFileDialogService);
+ const profileService = accessor.get(IWorkbenchProfileService);
+ const notificationService = accessor.get(INotificationService);
+
+ const profileLocation = await fileDialogService.showSaveDialog({
+ title: localize('export profile dialog', "Save Profile"),
+ filters: PROFILE_FILTER,
+ defaultUri: joinPath(await fileDialogService.defaultFilePath(), `profile.${PROFILE_EXTENSION}`),
+ });
+
+ if (!profileLocation) {
+ return;
+ }
+
+ const profile = await profileService.createProfile({ skipComments: true });
+ await textFileService.create([{ resource: profileLocation, value: JSON.stringify(profile), options: { overwrite: true } }]);
+
+ notificationService.info(localize('export success', "{0}: Exported successfully.", PROFILES_CATEGORY));
+ }
+});
+
+registerAction2(class ImportProfileAction extends Action2 {
+ constructor() {
+ super({
+ id: 'workbench.profiles.actions.importProfile',
+ title: {
+ value: localize('import profile', "Import Settings from a Profile..."),
+ original: 'Import Settings from a Profile...'
+ },
+ category: PROFILES_CATEGORY,
+ f1: true
+ });
+ }
+
+ async run(accessor: ServicesAccessor) {
+ const fileDialogService = accessor.get(IFileDialogService);
+ const quickInputService = accessor.get(IQuickInputService);
+ const fileService = accessor.get(IFileService);
+ const requestService = accessor.get(IRequestService);
+ const profileService = accessor.get(IWorkbenchProfileService);
+ const dialogService = accessor.get(IDialogService);
+
+ 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());
+ const updateQuickPickItems = (value?: string) => {
+ const selectFromFileItem: IQuickPickItem = { label: localize('select from file', "Import from profile file") };
+ quickPick.items = value ? [{ label: localize('select from url', "Import from URL"), description: quickPick.value }, selectFromFileItem] : [selectFromFileItem];
+ };
+ quickPick.title = localize('import profile quick pick title', "Import Settings from a Profile");
+ quickPick.placeholder = localize('import profile placeholder', "Provide profile URL or select profile file to import");
+ quickPick.ignoreFocusOut = true;
+ disposables.add(quickPick.onDidChangeValue(updateQuickPickItems));
+ updateQuickPickItems();
+ quickPick.matchOnLabel = false;
+ quickPick.matchOnDescription = false;
+ disposables.add(quickPick.onDidAccept(async () => {
+ quickPick.hide();
+ const profile = quickPick.selectedItems[0].description ? await this.getProfileFromURL(quickPick.value, requestService) : await this.getProfileFromFileSystem(fileDialogService, fileService);
+ if (profile) {
+ await profileService.setProfile(profile);
+ }
+ }));
+ disposables.add(quickPick.onDidHide(() => disposables.dispose()));
+ quickPick.show();
+ }
+
+ private async getProfileFromFileSystem(fileDialogService: IFileDialogService, fileService: IFileService): Promise<IProfile | null> {
+ const profileLocation = await fileDialogService.showOpenDialog({
+ canSelectFolders: false,
+ canSelectFiles: true,
+ canSelectMany: false,
+ filters: PROFILE_FILTER,
+ title: localize('import profile dialog', "Import Profile"),
+ });
+ if (!profileLocation) {
+ return null;
+ }
+ const content = (await fileService.readFile(profileLocation[0])).value.toString();
+ const parsed = JSON.parse(content);
+ return isProfile(parsed) ? parsed : null;
+ }
+
+ private async getProfileFromURL(url: string, requestService: IRequestService): Promise<IProfile | null> {
+ const options = { type: 'GET', url };
+ const context = await requestService.request(options, CancellationToken.None);
+ if (context.res.statusCode === 200) {
+ const result = await asJson(context);
+ return isProfile(result) ? result : null;
+ } else {
+ const message = await asText(context);
+ throw new Error(`Expected 200, got back ${context.res.statusCode} instead.\n\n${message}`);
+ }
+ }
+
+});
diff --git a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts
index 6d2258b96f1..0f8b4edc30d 100644
--- a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts
+++ b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts
@@ -160,12 +160,17 @@ export class ShowAllCommandsAction extends Action2 {
super({
id: ShowAllCommandsAction.ID,
title: { value: localize('showTriggerActions', "Show All Commands"), original: 'Show All Commands' },
- f1: true,
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
when: undefined,
primary: !isFirefox ? (KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyP) : undefined,
secondary: [KeyCode.F1]
+ },
+ f1: true,
+ menu: {
+ id: MenuId.TitleMenuQuickPick,
+ group: '1/workspaceNav',
+ order: 3
}
});
}
@@ -188,9 +193,23 @@ export class ClearCommandHistoryAction extends Action2 {
async run(accessor: ServicesAccessor): Promise<void> {
const configurationService = accessor.get(IConfigurationService);
const storageService = accessor.get(IStorageService);
+ const dialogService = accessor.get(IDialogService);
const commandHistoryLength = CommandsHistory.getConfiguredCommandHistoryLength(configurationService);
if (commandHistoryLength > 0) {
+
+ // Ask for confirmation
+ const { confirmed } = await dialogService.confirm({
+ message: localize('confirmClearMessage', "Do you want to clear the history of recently used commands?"),
+ detail: localize('confirmClearDetail', "This action is irreversible!"),
+ primaryButton: localize({ key: 'clearButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Clear"),
+ type: 'warning'
+ });
+
+ if (!confirmed) {
+ return;
+ }
+
CommandsHistory.clearHistory(configurationService, storageService);
}
}
diff --git a/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts
index d1a3d7680d5..4a278af2146 100644
--- a/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts
+++ b/src/vs/workbench/contrib/quickaccess/browser/viewQuickAccess.ts
@@ -7,7 +7,7 @@ import { localize } from 'vs/nls';
import { IQuickPickSeparator, IQuickInputService, ItemActivation } from 'vs/platform/quickinput/common/quickInput';
import { IPickerQuickAccessItem, PickerQuickAccessProvider } from 'vs/platform/quickinput/browser/pickerQuickAccess';
import { IViewDescriptorService, IViewsService, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views';
-import { IOutputService } from 'vs/workbench/contrib/output/common/output';
+import { IOutputService } from 'vs/workbench/services/output/common/output';
import { ITerminalGroupService, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { PaneCompositeDescriptor } from 'vs/workbench/browser/panecomposite';
diff --git a/src/vs/workbench/contrib/scm/browser/activity.ts b/src/vs/workbench/contrib/scm/browser/activity.ts
index 399e48dcce8..38a53b72c60 100644
--- a/src/vs/workbench/contrib/scm/browser/activity.ts
+++ b/src/vs/workbench/contrib/scm/browser/activity.ts
@@ -18,6 +18,7 @@ import { EditorResourceAccessor } from 'vs/workbench/common/editor';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
import { stripIcons } from 'vs/base/common/iconLabels';
import { Schemas } from 'vs/base/common/network';
+import { Iterable } from 'vs/base/common/iterator';
function getCount(repository: ISCMRepository): number {
if (typeof repository.provider.count === 'number') {
@@ -116,7 +117,7 @@ export class SCMStatusController implements IWorkbenchContribution {
return;
}
- this.focusRepository(this.scmService.repositories[0]);
+ this.focusRepository(Iterable.first(this.scmService.repositories));
}
private focusRepository(repository: ISCMRepository | undefined): void {
@@ -148,7 +149,8 @@ export class SCMStatusController implements IWorkbenchContribution {
: repository.provider.label;
const disposables = new DisposableStore();
- for (const command of commands) {
+ for (let index = 0; index < commands.length; index++) {
+ const command = commands[index];
const tooltip = `${label}${command.tooltip ? ` - ${command.tooltip}` : ''}`;
let ariaLabel = stripIcons(command.title).trim();
@@ -160,7 +162,7 @@ export class SCMStatusController implements IWorkbenchContribution {
ariaLabel: `${ariaLabel}${command.tooltip ? ` - ${command.tooltip}` : ''}`,
tooltip,
command: command.id ? command : undefined
- }, 'status.scm', MainThreadStatusBarAlignment.LEFT, 10000));
+ }, `status.scm.${index}`, MainThreadStatusBarAlignment.LEFT, 10000));
}
this.statusBarDisposable = disposables;
@@ -172,7 +174,7 @@ export class SCMStatusController implements IWorkbenchContribution {
let count = 0;
if (countBadgeType === 'all') {
- count = this.scmService.repositories.reduce((r, repository) => r + getCount(repository), 0);
+ count = Iterable.reduce(this.scmService.repositories, (r, repository) => r + getCount(repository), 0);
} else if (countBadgeType === 'focused' && this.focusedRepository) {
count = getCount(this.focusedRepository);
}
@@ -198,7 +200,7 @@ export class SCMStatusController implements IWorkbenchContribution {
export class SCMActiveResourceContextKeyController implements IWorkbenchContribution {
private activeResourceHasChangesContextKey: IContextKey<boolean>;
- private activeResourceRepositoryContextKey: IContextKey<ISCMRepository | undefined>;
+ private activeResourceRepositoryContextKey: IContextKey<string | undefined>;
private disposables = new DisposableStore();
private repositoryDisposables = new Set<IDisposable>();
@@ -239,11 +241,12 @@ export class SCMActiveResourceContextKeyController implements IWorkbenchContribu
const activeResource = EditorResourceAccessor.getOriginalUri(this.editorService.activeEditor);
if (activeResource?.scheme === Schemas.file || activeResource?.scheme === Schemas.vscodeRemote) {
- const activeResourceRepository = this.scmService.repositories
- .find(r => r.provider.rootUri &&
- this.uriIdentityService.extUri.isEqualOrParent(activeResource, r.provider.rootUri));
+ const activeResourceRepository = Iterable.find(
+ this.scmService.repositories,
+ r => Boolean(r.provider.rootUri && this.uriIdentityService.extUri.isEqualOrParent(activeResource, r.provider.rootUri))
+ );
- this.activeResourceRepositoryContextKey.set(activeResourceRepository);
+ this.activeResourceRepositoryContextKey.set(activeResourceRepository?.id);
for (const resourceGroup of activeResourceRepository?.provider.groups.elements ?? []) {
if (resourceGroup.elements
diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts
index 600b2af4b21..c05f0b2f4be 100644
--- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts
+++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts
@@ -20,7 +20,7 @@ import { URI } from 'vs/base/common/uri';
import { ISCMService, ISCMRepository, ISCMProvider } from 'vs/workbench/contrib/scm/common/scm';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
import { registerThemingParticipant, IColorTheme, ICssStyleCollector, themeColorFromId, IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
-import { editorBackground, editorErrorForeground, registerColor, transparent } from 'vs/platform/theme/common/colorRegistry';
+import { editorErrorForeground, registerColor, transparent } from 'vs/platform/theme/common/colorRegistry';
import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser';
import { registerEditorAction, registerEditorContribution, ServicesAccessor, EditorAction } from 'vs/editor/browser/editorExtensions';
import { PeekViewWidget, getOuterEditor, peekViewBorder, peekViewTitleBackground, peekViewTitleForeground, peekViewTitleInfoForeground } from 'vs/editor/contrib/peekView/browser/peekView';
@@ -52,6 +52,8 @@ import { TextCompareEditorActiveContext } from 'vs/workbench/common/contextkeys'
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
import { IChange } from 'vs/editor/common/diff/diffComputer';
import { Color } from 'vs/base/common/color';
+import { editorGutter } from 'vs/editor/common/core/editorColorRegistry';
+import { Iterable } from 'vs/base/common/iterator';
class DiffActionRunner extends ActionRunner {
@@ -608,7 +610,8 @@ export class DirtyDiffController extends Disposable implements IEditorContributi
}
.monaco-editor .margin-view-overlays .dirty-diff-glyph:hover::before {
- width: 9px;
+ height: 100%;
+ width: 6px;
left: -6px;
}
@@ -923,8 +926,10 @@ class DirtyDiffDecorator extends Disposable {
return ModelDecorationOptions.createDynamic(decorationOptions);
}
- private modifiedOptions: ModelDecorationOptions;
private addedOptions: ModelDecorationOptions;
+ private addedPatternOptions: ModelDecorationOptions;
+ private modifiedOptions: ModelDecorationOptions;
+ private modifiedPatternOptions: ModelDecorationOptions;
private deletedOptions: ModelDecorationOptions;
private decorations: string[] = [];
private editorModel: ITextModel | null;
@@ -932,25 +937,38 @@ class DirtyDiffDecorator extends Disposable {
constructor(
editorModel: ITextModel,
private model: DirtyDiffModel,
- @IConfigurationService configurationService: IConfigurationService
+ @IConfigurationService private readonly configurationService: IConfigurationService
) {
super();
this.editorModel = editorModel;
+
const decorations = configurationService.getValue<string>('scm.diffDecorations');
const gutter = decorations === 'all' || decorations === 'gutter';
const overview = decorations === 'all' || decorations === 'overview';
const minimap = decorations === 'all' || decorations === 'minimap';
+ this.addedOptions = DirtyDiffDecorator.createDecoration('dirty-diff-added', {
+ gutter,
+ overview: { active: overview, color: overviewRulerAddedForeground },
+ minimap: { active: minimap, color: minimapGutterAddedBackground },
+ isWholeLine: true
+ });
+ this.addedPatternOptions = DirtyDiffDecorator.createDecoration('dirty-diff-added-pattern', {
+ gutter,
+ overview: { active: overview, color: overviewRulerAddedForeground },
+ minimap: { active: minimap, color: minimapGutterAddedBackground },
+ isWholeLine: true
+ });
this.modifiedOptions = DirtyDiffDecorator.createDecoration('dirty-diff-modified', {
gutter,
overview: { active: overview, color: overviewRulerModifiedForeground },
minimap: { active: minimap, color: minimapGutterModifiedBackground },
isWholeLine: true
});
- this.addedOptions = DirtyDiffDecorator.createDecoration('dirty-diff-added', {
+ this.modifiedPatternOptions = DirtyDiffDecorator.createDecoration('dirty-diff-modified-pattern', {
gutter,
- overview: { active: overview, color: overviewRulerAddedForeground },
- minimap: { active: minimap, color: minimapGutterAddedBackground },
+ overview: { active: overview, color: overviewRulerModifiedForeground },
+ minimap: { active: minimap, color: minimapGutterModifiedBackground },
isWholeLine: true
});
this.deletedOptions = DirtyDiffDecorator.createDecoration('dirty-diff-deleted', {
@@ -960,6 +978,12 @@ class DirtyDiffDecorator extends Disposable {
isWholeLine: false
});
+ this._register(configurationService.onDidChangeConfiguration(e => {
+ if (e.affectsConfiguration('scm.diffDecorationsGutterPattern')) {
+ this.onDidChange();
+ }
+ }));
+
this._register(model.onDidChange(this.onDidChange, this));
}
@@ -967,6 +991,8 @@ class DirtyDiffDecorator extends Disposable {
if (!this.editorModel) {
return;
}
+
+ const pattern = this.configurationService.getValue<{ added: boolean; modified: boolean }>('scm.diffDecorationsGutterPattern');
const decorations = this.model.changes.map((change) => {
const changeType = getChangeType(change);
const startLineNumber = change.modifiedStartLineNumber;
@@ -979,7 +1005,7 @@ class DirtyDiffDecorator extends Disposable {
startLineNumber: startLineNumber, startColumn: 1,
endLineNumber: endLineNumber, endColumn: 1
},
- options: this.addedOptions
+ options: pattern.added ? this.addedPatternOptions : this.addedOptions
};
case ChangeType.Delete:
return {
@@ -995,7 +1021,7 @@ class DirtyDiffDecorator extends Disposable {
startLineNumber: startLineNumber, startColumn: 1,
endLineNumber: endLineNumber, endColumn: 1
},
- options: this.modifiedOptions
+ options: pattern.modified ? this.modifiedPatternOptions : this.modifiedOptions
};
}
});
@@ -1055,8 +1081,8 @@ export function createProviderComparer(uri: URI): (a: ISCMProvider, b: ISCMProvi
}
export async function getOriginalResource(scmService: ISCMService, uri: URI): Promise<URI | null> {
- const providers = scmService.repositories.map(r => r.provider);
- const rootedProviders = providers.filter(p => !!p.rootUri);
+ const providers = Iterable.map(scmService.repositories, r => r.provider);
+ const rootedProviders = Iterable.collect(Iterable.filter(providers, p => !!p.rootUri));
rootedProviders.sort(createProviderComparer(uri));
@@ -1066,8 +1092,8 @@ export async function getOriginalResource(scmService: ISCMService, uri: URI): Pr
return result;
}
- const nonRootedProviders = providers.filter(p => !p.rootUri);
- return first(nonRootedProviders.map(p => () => p.getOriginalResource(uri)));
+ const nonRootedProviders = Iterable.filter(providers, p => !p.rootUri);
+ return first(Iterable.collect(Iterable.map(nonRootedProviders, p => () => p.getOriginalResource(uri))));
}
export class DirtyDiffModel extends Disposable {
@@ -1108,7 +1134,7 @@ export class DirtyDiffModel extends Disposable {
)(this.triggerDiff, this)
);
this._register(scmService.onDidAddRepository(this.onDidAddRepository, this));
- scmService.repositories.forEach(r => this.onDidAddRepository(r));
+ Iterable.forEach(scmService.repositories, r => this.onDidAddRepository(r));
this._register(this._model.onDidChangeEncoding(() => {
this.diffDelayer.cancel();
@@ -1371,9 +1397,21 @@ export class DirtyDiffWorkbenchController extends Disposable implements ext.IWor
private setViewState(state: IViewState): void {
this.viewState = state;
this.stylesheet.textContent = `
- .monaco-editor .dirty-diff-modified { background-size: ${state.width}px 4.5px; }
- .monaco-editor .dirty-diff-modified, .monaco-editor .dirty-diff-added{border-left-width:${state.width}px;}
- .monaco-editor .dirty-diff-modified, .monaco-editor .dirty-diff-added, .monaco-editor .dirty-diff-deleted {
+ .monaco-editor .dirty-diff-added,
+ .monaco-editor .dirty-diff-modified {
+ border-left-width:${state.width}px;
+ }
+ .monaco-editor .dirty-diff-added-pattern,
+ .monaco-editor .dirty-diff-added-pattern:before,
+ .monaco-editor .dirty-diff-modified-pattern,
+ .monaco-editor .dirty-diff-modified-pattern:before {
+ background-size: ${state.width}px ${state.width}px;
+ }
+ .monaco-editor .dirty-diff-added,
+ .monaco-editor .dirty-diff-added-pattern,
+ .monaco-editor .dirty-diff-modified,
+ .monaco-editor .dirty-diff-modified-pattern,
+ .monaco-editor .dirty-diff-deleted {
opacity: ${state.visibility === 'always' ? 1 : 0};
}
`;
@@ -1468,39 +1506,61 @@ export class DirtyDiffWorkbenchController extends Disposable implements ext.IWor
registerEditorContribution(DirtyDiffController.ID, DirtyDiffController);
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
- const editorBackgroundColor = theme.getColor(editorBackground);
+ const editorGutterBackgroundColor = theme.getColor(editorGutter);
const editorGutterModifiedBackgroundColor = theme.getColor(editorGutterModifiedBackground);
- const linearGradient = `-45deg, ${editorGutterModifiedBackgroundColor} 25%, ${editorBackgroundColor} 25%, ${editorBackgroundColor} 50%, ${editorGutterModifiedBackgroundColor} 50%, ${editorGutterModifiedBackgroundColor} 75%, ${editorBackgroundColor} 75%, ${editorBackgroundColor}`;
- if (editorGutterModifiedBackgroundColor) {
+ const getLinearGradient = (color: Color): string => {
+ return `-45deg, ${color} 25%, ${editorGutterBackgroundColor} 25%, ${editorGutterBackgroundColor} 50%, ${color} 50%, ${color} 75%, ${editorGutterBackgroundColor} 75%, ${editorGutterBackgroundColor}`;
+ };
+
+ if (editorGutterBackgroundColor && editorGutterModifiedBackgroundColor) {
collector.addRule(`
.monaco-editor .dirty-diff-modified {
- background-repeat: repeat-y;
- background-image: linear-gradient(${linearGradient});
+ border-left-color: ${editorGutterModifiedBackgroundColor};
+ border-left-style: solid;
transition: opacity 0.5s;
}
.monaco-editor .dirty-diff-modified:before {
+ background: ${editorGutterModifiedBackgroundColor};
+ }
+ .monaco-editor .dirty-diff-modified-pattern {
+ background-image: linear-gradient(${getLinearGradient(editorGutterModifiedBackgroundColor)});
+ background-repeat: repeat-y;
+ transition: opacity 0.5s;
+ }
+ .monaco-editor .dirty-diff-modified-pattern:before {
+ background-image: linear-gradient(${getLinearGradient(editorGutterModifiedBackgroundColor)});
transform: translateX(3px);
- background-size: 3px 3px;
- background-image: linear-gradient(${linearGradient});
}
- .monaco-editor .margin:hover .dirty-diff-modified {
+ .monaco-editor .margin:hover .dirty-diff-modified,
+ .monaco-editor .margin:hover .dirty-diff-modified-pattern {
opacity: 1;
}
`);
}
const editorGutterAddedBackgroundColor = theme.getColor(editorGutterAddedBackground);
- if (editorGutterAddedBackgroundColor) {
+ if (editorGutterBackgroundColor && editorGutterAddedBackgroundColor) {
collector.addRule(`
.monaco-editor .dirty-diff-added {
- border-left: 3px solid ${editorGutterAddedBackgroundColor};
+ border-left-color: ${editorGutterAddedBackgroundColor};
+ border-left-style: solid;
transition: opacity 0.5s;
}
.monaco-editor .dirty-diff-added:before {
background: ${editorGutterAddedBackgroundColor};
}
- .monaco-editor .margin:hover .dirty-diff-added {
+ .monaco-editor .dirty-diff-added-pattern {
+ background-image: linear-gradient(${getLinearGradient(editorGutterAddedBackgroundColor)});
+ background-repeat: repeat-y;
+ transition: opacity 0.5s;
+ }
+ .monaco-editor .dirty-diff-added-pattern:before {
+ background-image: linear-gradient(${getLinearGradient(editorGutterAddedBackgroundColor)});
+ transform: translateX(3px);
+ }
+ .monaco-editor .margin:hover .dirty-diff-added,
+ .monaco-editor .margin:hover .dirty-diff-added-pattern {
opacity: 1;
}
`);
diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts
index 27fa26a947d..956e867a21f 100644
--- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts
+++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts
@@ -7,7 +7,7 @@ import { localize } from 'vs/nls';
import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
import { DirtyDiffWorkbenchController } from './dirtydiffDecorator';
-import { VIEWLET_ID, ISCMRepository, ISCMService, VIEW_PANE_ID, ISCMProvider, ISCMViewService, REPOSITORIES_VIEW_PANE_ID } from 'vs/workbench/contrib/scm/common/scm';
+import { VIEWLET_ID, ISCMService, VIEW_PANE_ID, ISCMProvider, ISCMViewService, REPOSITORIES_VIEW_PANE_ID } from 'vs/workbench/contrib/scm/common/scm';
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
import { SCMActiveResourceContextKeyController, SCMStatusController } from './activity';
@@ -83,7 +83,7 @@ viewsRegistry.registerViews([{
containerIcon: sourceControlViewIcon,
openCommandActionDescriptor: {
id: viewContainer.id,
- mnemonicTitle: localize({ key: 'miViewSCM', comment: ['&& denotes a mnemonic'] }, "S&&CM"),
+ mnemonicTitle: localize({ key: 'miViewSCM', comment: ['&& denotes a mnemonic'] }, "Source &&Control"),
keybindings: {
primary: 0,
win: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyG },
@@ -118,7 +118,7 @@ Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).registerConfiguration({
id: 'scm',
order: 5,
- title: localize('scmConfigurationTitle', "SCM"),
+ title: localize('scmConfigurationTitle', "Source Control"),
type: 'object',
scope: ConfigurationScope.RESOURCE,
properties: {
@@ -161,6 +161,25 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).regis
description: localize('scm.diffDecorationsGutterAction', "Controls the behavior of Source Control diff gutter decorations."),
default: 'diff'
},
+ 'scm.diffDecorationsGutterPattern': {
+ type: 'object',
+ description: localize('diffGutterPattern', "Controls whether a pattern is used for the diff decorations in gutter."),
+ additionalProperties: false,
+ properties: {
+ 'added': {
+ type: 'boolean',
+ description: localize('diffGutterPatternAdded', "Use pattern for the diff decorations in gutter for added lines."),
+ },
+ 'modified': {
+ type: 'boolean',
+ description: localize('diffGutterPatternModifed', "Use pattern for the diff decorations in gutter for modified lines."),
+ },
+ },
+ default: {
+ 'added': false,
+ 'modified': true
+ }
+ },
'scm.diffDecorationsIgnoreTrimWhitespace': {
type: 'string',
enum: ['true', 'false', 'inherit'],
@@ -215,14 +234,14 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).regis
enumDescriptions: [
localize('scm.defaultViewSortKey.name', "Sort the repository changes by file name."),
localize('scm.defaultViewSortKey.path', "Sort the repository changes by path."),
- localize('scm.defaultViewSortKey.status', "Sort the repository changes by SCM status.")
+ localize('scm.defaultViewSortKey.status', "Sort the repository changes by Source Control status.")
],
- description: localize('scm.defaultViewSortKey', "Controls the default Source Control repository sort mode."),
+ description: localize('scm.defaultViewSortKey', "Controls the default Source Control repository changes sort order when viewed as a list."),
default: 'path'
},
'scm.autoReveal': {
type: 'boolean',
- description: localize('autoReveal', "Controls whether the SCM view should automatically reveal and select files when opening them."),
+ description: localize('autoReveal', "Controls whether the Source Control view should automatically reveal and select files when opening them."),
default: true
},
'scm.inputFontFamily': {
@@ -237,9 +256,20 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).regis
},
'scm.alwaysShowRepositories': {
type: 'boolean',
- markdownDescription: localize('alwaysShowRepository', "Controls whether repositories should always be visible in the SCM view."),
+ markdownDescription: localize('alwaysShowRepository', "Controls whether repositories should always be visible in the Source Control view."),
default: false
},
+ 'scm.repositories.sortOrder': {
+ type: 'string',
+ enum: ['discovery time', 'name', 'path'],
+ enumDescriptions: [
+ localize('scm.repositoriesSortOrder.discoveryTime', "Repositories in the Source Control Repositories view are sorted by discovery time. Repositories in the Source Control view are sorted in the order that they were selected."),
+ localize('scm.repositoriesSortOrder.name', "Repositories in the Source Control Repositories and Source Control views are sorted by repository name."),
+ localize('scm.repositoriesSortOrder.path', "Repositories in the Source Control Repositories and Source Control views are sorted by repository path.")
+ ],
+ description: localize('repositoriesSortOrder', "Controls the sort order of the repositories in the source control repositories view."),
+ default: 'discovery time'
+ },
'scm.repositories.visible': {
type: 'number',
description: localize('providersVisible', "Controls how many repositories are visible in the Source Control Repositories section. Set to `0` to be able to manually resize the view."),
@@ -247,7 +277,7 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).regis
},
'scm.showActionButton': {
type: 'boolean',
- markdownDescription: localize('showActionButton', "Controls whether an action button can be shown in the SCM view."),
+ markdownDescription: localize('showActionButton', "Controls whether an action button can be shown in the Source Control view."),
default: true
}
}
@@ -255,44 +285,56 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).regis
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'scm.acceptInput',
- description: { description: localize('scm accept', "SCM: Accept Input"), args: [] },
+ description: { description: localize('scm accept', "Source Control: Accept Input"), args: [] },
weight: KeybindingWeight.WorkbenchContrib,
when: ContextKeyExpr.has('scmRepository'),
primary: KeyMod.CtrlCmd | KeyCode.Enter,
handler: accessor => {
const contextKeyService = accessor.get(IContextKeyService);
const context = contextKeyService.getContext(document.activeElement);
- const repository = context.getValue<ISCMRepository>('scmRepository');
+ const repositoryId = context.getValue<string | undefined>('scmRepository');
+
+ if (!repositoryId) {
+ return Promise.resolve(null);
+ }
+
+ const scmService = accessor.get(ISCMService);
+ const repository = scmService.getRepository(repositoryId);
- if (!repository || !repository.provider.acceptInputCommand) {
+ if (!repository?.provider.acceptInputCommand) {
return Promise.resolve(null);
}
+
const id = repository.provider.acceptInputCommand.id;
const args = repository.provider.acceptInputCommand.arguments;
-
const commandService = accessor.get(ICommandService);
+
return commandService.executeCommand(id, ...(args || []));
}
});
const viewNextCommitCommand = {
- description: { description: localize('scm view next commit', "SCM: View Next Commit"), args: [] },
+ description: { description: localize('scm view next commit', "Source Control: View Next Commit"), args: [] },
weight: KeybindingWeight.WorkbenchContrib,
handler: (accessor: ServicesAccessor) => {
const contextKeyService = accessor.get(IContextKeyService);
+ const scmService = accessor.get(ISCMService);
const context = contextKeyService.getContext(document.activeElement);
- const repository = context.getValue<ISCMRepository>('scmRepository');
+ const repositoryId = context.getValue<string | undefined>('scmRepository');
+ const repository = repositoryId ? scmService.getRepository(repositoryId) : undefined;
repository?.input.showNextHistoryValue();
}
};
const viewPreviousCommitCommand = {
- description: { description: localize('scm view previous commit', "SCM: View Previous Commit"), args: [] },
+ description: { description: localize('scm view previous commit', "Source Control: View Previous Commit"), args: [] },
weight: KeybindingWeight.WorkbenchContrib,
handler: (accessor: ServicesAccessor) => {
const contextKeyService = accessor.get(IContextKeyService);
+ const scmService = accessor.get(ISCMService);
const context = contextKeyService.getContext(document.activeElement);
- const repository = context.getValue<ISCMRepository>('scmRepository');
+ const repositoryId = context.getValue<string | undefined>('scmRepository');
+ const repository = repositoryId ? scmService.getRepository(repositoryId) : undefined;
repository?.input.showPreviousHistoryValue();
}
};
diff --git a/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts
index e66a0f24fd5..3772fac9387 100644
--- a/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts
+++ b/src/vs/workbench/contrib/scm/browser/scmRepositoriesViewPane.ts
@@ -23,6 +23,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { RepositoryRenderer } from 'vs/workbench/contrib/scm/browser/scmRepositoryRenderer';
import { collectContextMenuActions, getActionViewItemProvider } from 'vs/workbench/contrib/scm/browser/util';
import { Orientation } from 'vs/base/browser/ui/sash/sash';
+import { Iterable } from 'vs/base/common/iterator';
class ListDelegate implements IListVirtualDelegate<ISCMRepository> {
@@ -155,23 +156,28 @@ export class SCMRepositoriesViewPane extends ViewPane {
}
private updateListSelection(): void {
- const set = new Set();
+ const oldSelection = this.list.getSelection();
+ const oldSet = new Set(Iterable.map(oldSelection, i => this.list.element(i)));
+ const set = new Set(this.scmViewService.visibleRepositories);
+ const added = new Set(Iterable.filter(set, r => !oldSet.has(r)));
+ const removed = new Set(Iterable.filter(oldSet, r => !set.has(r)));
- for (const repository of this.scmViewService.visibleRepositories) {
- set.add(repository);
+ if (added.size === 0 && removed.size === 0) {
+ return;
}
- const selection: number[] = [];
+ const selection = oldSelection
+ .filter(i => !removed.has(this.list.element(i)));
for (let i = 0; i < this.list.length; i++) {
- if (set.has(this.list.element(i))) {
+ if (added.has(this.list.element(i))) {
selection.push(i);
}
}
this.list.setSelection(selection);
- if (selection.length > 0) {
+ if (selection.length > 0 && selection.indexOf(this.list.getFocus()[0]) === -1) {
this.list.setAnchor(selection[0]);
this.list.setFocus([selection[0]]);
}
diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts
index 79cce93a380..7e87c10e0fc 100644
--- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts
+++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts
@@ -10,7 +10,7 @@ import { IDisposable, Disposable, DisposableStore, combinedDisposable, dispose,
import { ViewPane, IViewPaneOptions, ViewAction } from 'vs/workbench/browser/parts/views/viewPane';
import { append, $, Dimension, asCSSUrl, trackFocus, clearNode } from 'vs/base/browser/dom';
import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list';
-import { ISCMResourceGroup, ISCMResource, InputValidationType, ISCMRepository, ISCMInput, IInputValidation, ISCMViewService, ISCMViewVisibleRepositoryChangeEvent, ISCMService, SCMInputChangeReason, VIEW_PANE_ID, ISCMActionButton, ISCMActionButtonDescriptor } from 'vs/workbench/contrib/scm/common/scm';
+import { ISCMResourceGroup, ISCMResource, InputValidationType, ISCMRepository, ISCMInput, IInputValidation, ISCMViewService, ISCMViewVisibleRepositoryChangeEvent, ISCMService, SCMInputChangeReason, VIEW_PANE_ID, ISCMActionButton, ISCMActionButtonDescriptor, ISCMRepositorySortKey, REPOSITORIES_VIEW_PANE_ID } from 'vs/workbench/contrib/scm/common/scm';
import { ResourceLabels, IResourceLabel, IFileLabelOptions } from 'vs/workbench/browser/labels';
import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
@@ -84,6 +84,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace
import { MarkdownRenderer } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer';
import { Button, ButtonWithDescription } from 'vs/base/browser/ui/button/button';
import { INotificationService } from 'vs/platform/notification/common/notification';
+import { RepositoryContextKeys } from 'vs/workbench/contrib/scm/browser/scmViewService';
type TreeElement = ISCMRepository | ISCMInput | ISCMActionButton | ISCMResourceGroup | IResourceNode<ISCMResource, ISCMResourceGroup> | ISCMResource;
@@ -944,7 +945,7 @@ class RepositoryVisibilityAction extends Action2 {
f1: false,
precondition: ContextKeyExpr.or(ContextKeys.RepositoryVisibilityCount.notEqualsTo(1), ContextKeys.RepositoryVisibility(repository).isEqualTo(false)),
toggled: ContextKeys.RepositoryVisibility(repository).isEqualTo(true),
- menu: { id: Menus.Repositories }
+ menu: { id: Menus.Repositories, group: '0_repositories' }
});
this.repository = repository;
}
@@ -1069,9 +1070,14 @@ class ViewModel {
}
}
+ // Update sort key based on view mode
+ this.sortKey = this.getViewModelSortKey();
+
this.refresh();
this._onDidChangeMode.fire(mode);
this.modeContextKey.set(mode);
+
+ this.storageService.store(`scm.viewMode`, mode, StorageScope.WORKSPACE, StorageTarget.USER);
}
get sortKey(): ViewModelSortKey { return this._sortKey; }
@@ -1085,6 +1091,10 @@ class ViewModel {
this.refresh();
this._onDidChangeSortKey.fire(sortKey);
this.sortKeyContextKey.set(sortKey);
+
+ if (this._mode === ViewModelMode.List) {
+ this.storageService.store(`scm.viewSortKey`, sortKey, StorageScope.WORKSPACE, StorageTarget.USER);
+ }
}
private _treeViewStateIsStale = false;
@@ -1113,23 +1123,37 @@ class ViewModel {
private scmProviderRootUriContextKey: IContextKey<string | undefined>;
private scmProviderHasRootUriContextKey: IContextKey<boolean>;
+ private _mode: ViewModelMode;
+ private _sortKey: ViewModelSortKey;
+ private _treeViewState: ITreeViewState | undefined;
+
constructor(
private tree: WorkbenchCompressibleObjectTree<TreeElement, FuzzyScore>,
private inputRenderer: InputRenderer,
- private _mode: ViewModelMode,
- private _sortKey: ViewModelSortKey,
- private _treeViewState: ITreeViewState | undefined,
@IInstantiationService protected instantiationService: IInstantiationService,
@IEditorService protected editorService: IEditorService,
@IConfigurationService protected configurationService: IConfigurationService,
@ISCMViewService private scmViewService: ISCMViewService,
+ @IStorageService private storageService: IStorageService,
@IUriIdentityService private uriIdentityService: IUriIdentityService,
@IContextKeyService contextKeyService: IContextKeyService
) {
+ // View mode and sort key
+ this._mode = this.getViewModelMode();
+ this._sortKey = this.getViewModelSortKey();
+
+ // TreeView state
+ const storageViewState = this.storageService.get(`scm.viewState`, StorageScope.WORKSPACE);
+ if (storageViewState) {
+ try {
+ this._treeViewState = JSON.parse(storageViewState);
+ } catch {/* noop */ }
+ }
+
this.modeContextKey = ContextKeys.ViewModelMode.bindTo(contextKeyService);
- this.modeContextKey.set(_mode);
+ this.modeContextKey.set(this._mode);
this.sortKeyContextKey = ContextKeys.ViewModelSortKey.bindTo(contextKeyService);
- this.sortKeyContextKey.set(_sortKey);
+ this.sortKeyContextKey.set(this._sortKey);
this.areAllRepositoriesCollapsedContextKey = ContextKeys.ViewModelAreAllRepositoriesCollapsed.bindTo(contextKeyService);
this.isAnyRepositoryCollapsibleContextKey = ContextKeys.ViewModelIsAnyRepositoryCollapsible.bindTo(contextKeyService);
this.scmProviderContextKey = ContextKeys.SCMProvider.bindTo(contextKeyService);
@@ -1143,6 +1167,12 @@ class ViewModel {
(this.updateRepositoryCollapseAllContextKeys, this, this.disposables);
this.disposables.add(this.tree.onDidChangeCollapseState(() => this._treeViewStateIsStale = true));
+
+ this.storageService.onWillSaveState(e => {
+ if (e.reason === WillSaveStateReason.SHUTDOWN) {
+ this.storageService.store(`scm.viewState`, JSON.stringify(this.treeViewState), StorageScope.WORKSPACE, StorageTarget.MACHINE);
+ }
+ });
}
private onDidChangeConfiguration(e?: IConfigurationChangeEvent): void {
@@ -1432,6 +1462,45 @@ class ViewModel {
}
}
+ private getViewModelMode(): ViewModelMode {
+ let mode = this.configurationService.getValue<'tree' | 'list'>('scm.defaultViewMode') === 'list' ? ViewModelMode.List : ViewModelMode.Tree;
+ const storageMode = this.storageService.get(`scm.viewMode`, StorageScope.WORKSPACE) as ViewModelMode;
+ if (typeof storageMode === 'string') {
+ mode = storageMode;
+ }
+
+ return mode;
+ }
+
+ private getViewModelSortKey(): ViewModelSortKey {
+ // Tree
+ if (this._mode === ViewModelMode.Tree) {
+ return ViewModelSortKey.Path;
+ }
+
+ // List
+ let viewSortKey: ViewModelSortKey;
+ const viewSortKeyString = this.configurationService.getValue<'path' | 'name' | 'status'>('scm.defaultViewSortKey');
+ switch (viewSortKeyString) {
+ case 'name':
+ viewSortKey = ViewModelSortKey.Name;
+ break;
+ case 'status':
+ viewSortKey = ViewModelSortKey.Status;
+ break;
+ default:
+ viewSortKey = ViewModelSortKey.Path;
+ break;
+ }
+
+ const storageSortKey = this.storageService.get(`scm.viewSortKey`, StorageScope.WORKSPACE) as ViewModelSortKey;
+ if (typeof storageSortKey === 'string') {
+ viewSortKey = storageSortKey;
+ }
+
+ return viewSortKey;
+ }
+
dispose(): void {
this.visibilityDisposables.dispose();
this.disposables.dispose();
@@ -1503,6 +1572,56 @@ registerAction2(SetTreeViewModeAction);
registerAction2(SetListViewModeNavigationAction);
registerAction2(SetTreeViewModeNavigationAction);
+abstract class RepositorySortAction extends ViewAction<SCMViewPane> {
+ constructor(private sortKey: ISCMRepositorySortKey, title: string) {
+ super({
+ id: `workbench.scm.action.repositories.setSortKey.${sortKey}`,
+ title,
+ viewId: VIEW_PANE_ID,
+ f1: false,
+ toggled: RepositoryContextKeys.RepositorySortKey.isEqualTo(sortKey),
+ menu: [
+ {
+ id: Menus.Repositories,
+ group: '1_sort'
+ },
+ {
+ id: MenuId.ViewTitle,
+ when: ContextKeyExpr.equals('view', REPOSITORIES_VIEW_PANE_ID),
+ group: '1_sort',
+ },
+ ]
+ });
+ }
+
+ runInView(accessor: ServicesAccessor) {
+ accessor.get(ISCMViewService).toggleSortKey(this.sortKey);
+ }
+}
+
+
+class RepositorySortByDiscoveryTimeAction extends RepositorySortAction {
+ constructor() {
+ super(ISCMRepositorySortKey.DiscoveryTime, localize('repositorySortByDiscoveryTime', "Sort by Discovery Time"));
+ }
+}
+
+class RepositorySortByNameAction extends RepositorySortAction {
+ constructor() {
+ super(ISCMRepositorySortKey.Name, localize('repositorySortByName', "Sort by Name"));
+ }
+}
+
+class RepositorySortByPathAction extends RepositorySortAction {
+ constructor() {
+ super(ISCMRepositorySortKey.Path, localize('repositorySortByPath', "Sort by Path"));
+ }
+}
+
+registerAction2(RepositorySortByDiscoveryTimeAction);
+registerAction2(RepositorySortByNameAction);
+registerAction2(RepositorySortByPathAction);
+
abstract class SetSortKeyAction extends ViewAction<SCMViewPane> {
constructor(private sortKey: ViewModelSortKey, title: string) {
super({
@@ -1511,6 +1630,7 @@ abstract class SetSortKeyAction extends ViewAction<SCMViewPane> {
viewId: VIEW_PANE_ID,
f1: false,
toggled: ContextKeys.ViewModelSortKey.isEqualTo(sortKey),
+ precondition: ContextKeys.ViewModelMode.isEqualTo(ViewModelMode.List),
menu: { id: Menus.ViewSort, group: '2_sort' }
});
}
@@ -1522,19 +1642,19 @@ abstract class SetSortKeyAction extends ViewAction<SCMViewPane> {
class SetSortByNameAction extends SetSortKeyAction {
constructor() {
- super(ViewModelSortKey.Name, localize('sortByName', "Sort by Name"));
+ super(ViewModelSortKey.Name, localize('sortChangesByName', "Sort Changes by Name"));
}
}
class SetSortByPathAction extends SetSortKeyAction {
constructor() {
- super(ViewModelSortKey.Path, localize('sortByPath', "Sort by Path"));
+ super(ViewModelSortKey.Path, localize('sortChangesByPath', "Sort Changes by Path"));
}
}
class SetSortByStatusAction extends SetSortKeyAction {
constructor() {
- super(ViewModelSortKey.Status, localize('sortByStatus', "Sort by Status"));
+ super(ViewModelSortKey.Status, localize('sortChangesByStatus', "Sort Changes by Status"));
}
}
@@ -1604,7 +1724,7 @@ class SCMInputWidget extends Disposable {
private inputEditor: CodeEditorWidget;
private model: { readonly input: ISCMInput; readonly textModel: ITextModel } | undefined;
- private repositoryContextKey: IContextKey<ISCMRepository | undefined>;
+ private repositoryIdContextKey: IContextKey<string | undefined>;
private repositoryDisposables = new DisposableStore();
private validation: IInputValidation | undefined;
@@ -1633,7 +1753,7 @@ class SCMInputWidget extends Disposable {
this.repositoryDisposables.dispose();
this.repositoryDisposables = new DisposableStore();
- this.repositoryContextKey.set(input?.repository);
+ this.repositoryIdContextKey.set(input?.repository.id);
if (!input) {
this.model?.textModel.dispose();
@@ -1799,7 +1919,7 @@ class SCMInputWidget extends Disposable {
this.setPlaceholderFontStyles(fontFamily, fontSize, lineHeight);
const contextKeyService2 = contextKeyService.createScoped(this.element);
- this.repositoryContextKey = contextKeyService2.createKey('scmRepository', undefined);
+ this.repositoryIdContextKey = contextKeyService2.createKey('scmRepository', undefined);
const editorOptions: IEditorConstructionOptions = {
...getSimpleEditorOptions(),
@@ -2058,7 +2178,6 @@ export class SCMViewPane extends ViewPane {
@IConfigurationService configurationService: IConfigurationService,
@IContextKeyService contextKeyService: IContextKeyService,
@IMenuService private menuService: IMenuService,
- @IStorageService private storageService: IStorageService,
@IOpenerService openerService: IOpenerService,
@ITelemetryService telemetryService: ITelemetryService,
) {
@@ -2145,44 +2264,9 @@ export class SCMViewPane extends ViewPane {
append(this.listContainer, overflowWidgetsDomNode);
- let viewMode = this.configurationService.getValue<'tree' | 'list'>('scm.defaultViewMode') === 'list' ? ViewModelMode.List : ViewModelMode.Tree;
-
- const storageMode = this.storageService.get(`scm.viewMode`, StorageScope.WORKSPACE) as ViewModelMode;
- if (typeof storageMode === 'string') {
- viewMode = storageMode;
- }
-
- let viewSortKey: ViewModelSortKey;
- const viewSortKeyString = this.configurationService.getValue<'path' | 'name' | 'status'>('scm.defaultViewSortKey');
- switch (viewSortKeyString) {
- case 'name':
- viewSortKey = ViewModelSortKey.Name;
- break;
- case 'status':
- viewSortKey = ViewModelSortKey.Status;
- break;
- default:
- viewSortKey = ViewModelSortKey.Path;
- break;
- }
-
- const storageSortKey = this.storageService.get(`scm.viewSortKey`, StorageScope.WORKSPACE) as ViewModelSortKey;
- if (typeof storageSortKey === 'string') {
- viewSortKey = storageSortKey;
- }
-
- let viewState: ITreeViewState | undefined;
-
- const storageViewState = this.storageService.get(`scm.viewState`, StorageScope.WORKSPACE);
- if (storageViewState) {
- try {
- viewState = JSON.parse(storageViewState);
- } catch {/* noop */ }
- }
-
this._register(this.instantiationService.createInstance(RepositoryVisibilityActionController));
- this._viewModel = this.instantiationService.createInstance(ViewModel, this.tree, this.inputRenderer, viewMode, viewSortKey, viewState);
+ this._viewModel = this.instantiationService.createInstance(ViewModel, this.tree, this.inputRenderer);
this._register(this._viewModel);
this.listContainer.classList.add('file-icon-themable-tree');
@@ -2191,18 +2275,11 @@ export class SCMViewPane extends ViewPane {
this.updateIndentStyles(this.themeService.getFileIconTheme());
this._register(this.themeService.onDidFileIconThemeChange(this.updateIndentStyles, this));
this._register(this._viewModel.onDidChangeMode(this.onDidChangeMode, this));
- this._register(this._viewModel.onDidChangeSortKey(this.onDidChangeSortKey, this));
this._register(this.onDidChangeBodyVisibility(this._viewModel.setVisible, this._viewModel));
this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.alwaysShowRepositories'))(this.updateActions, this));
this.updateActions();
-
- this._register(this.storageService.onWillSaveState(e => {
- if (e.reason === WillSaveStateReason.SHUTDOWN) {
- this.storageService.store(`scm.viewState`, JSON.stringify(this._viewModel.treeViewState), StorageScope.WORKSPACE, StorageTarget.MACHINE);
- }
- }));
}
private updateIndentStyles(theme: IFileIconTheme): void {
@@ -2214,11 +2291,6 @@ export class SCMViewPane extends ViewPane {
private onDidChangeMode(): void {
this.updateIndentStyles(this.themeService.getFileIconTheme());
- this.storageService.store(`scm.viewMode`, this._viewModel.mode, StorageScope.WORKSPACE, StorageTarget.USER);
- }
-
- private onDidChangeSortKey(): void {
- this.storageService.store(`scm.viewSortKey`, this._viewModel.sortKey, StorageScope.WORKSPACE, StorageTarget.USER);
}
override layoutBody(height: number | undefined = this.layoutCache.height, width: number | undefined = this.layoutCache.width): void {
@@ -2254,14 +2326,14 @@ export class SCMViewPane extends ViewPane {
return;
} else if (isSCMResourceGroup(e.element)) {
const provider = e.element.provider;
- const repository = this.scmService.repositories.find(r => r.provider === provider);
+ const repository = Iterable.find(this.scmService.repositories, r => r.provider === provider);
if (repository) {
this.scmViewService.focus(repository);
}
return;
} else if (ResourceTree.isResourceNode(e.element)) {
const provider = e.element.context.provider;
- const repository = this.scmService.repositories.find(r => r.provider === provider);
+ const repository = Iterable.find(this.scmService.repositories, r => r.provider === provider);
if (repository) {
this.scmViewService.focus(repository);
}
@@ -2304,7 +2376,7 @@ export class SCMViewPane extends ViewPane {
}
const provider = e.element.resourceGroup.provider;
- const repository = this.scmService.repositories.find(r => r.provider === provider);
+ const repository = Iterable.find(this.scmService.repositories, r => r.provider === provider);
if (repository) {
this.scmViewService.focus(repository);
@@ -2379,7 +2451,11 @@ export class SCMViewPane extends ViewPane {
}
override shouldShowWelcome(): boolean {
- return this.scmService.repositories.length === 0;
+ return this.scmService.repositoryCount === 0;
+ }
+
+ override getActionsContext(): unknown {
+ return this.scmViewService.visibleRepositories.length === 1 ? this.scmViewService.visibleRepositories[0].provider : undefined;
}
}
diff --git a/src/vs/workbench/contrib/scm/browser/scmViewService.ts b/src/vs/workbench/contrib/scm/browser/scmViewService.ts
index 757c5526c7a..be0176c4919 100644
--- a/src/vs/workbench/contrib/scm/browser/scmViewService.ts
+++ b/src/vs/workbench/contrib/scm/browser/scmViewService.ts
@@ -5,7 +5,7 @@
import { DisposableStore } from 'vs/base/common/lifecycle';
import { Emitter, Event } from 'vs/base/common/event';
-import { ISCMViewService, ISCMRepository, ISCMService, ISCMViewVisibleRepositoryChangeEvent, ISCMMenus, ISCMProvider } from 'vs/workbench/contrib/scm/common/scm';
+import { ISCMViewService, ISCMRepository, ISCMService, ISCMViewVisibleRepositoryChangeEvent, ISCMMenus, ISCMProvider, ISCMRepositorySortKey } from 'vs/workbench/contrib/scm/common/scm';
import { Iterable } from 'vs/base/common/iterator';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { SCMMenus } from 'vs/workbench/contrib/scm/browser/menus';
@@ -15,6 +15,8 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace
import { compareFileNames, comparePaths } from 'vs/base/common/comparers';
import { basename } from 'vs/base/common/resources';
import { binarySearch } from 'vs/base/common/arrays';
+import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
+import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
function getProviderStorageKey(provider: ISCMProvider): string {
return `${provider.contextValue}:${provider.label}${provider.rootUri ? `:${provider.rootUri.toString()}` : ''}`;
@@ -29,8 +31,20 @@ function getRepositoryName(workspaceContextService: IWorkspaceContextService, re
return folder?.uri.toString() === repository.provider.rootUri.toString() ? folder.name : basename(repository.provider.rootUri);
}
+export const RepositoryContextKeys = {
+ RepositorySortKey: new RawContextKey<ISCMRepositorySortKey>('scmRepositorySortKey', ISCMRepositorySortKey.DiscoveryTime),
+};
+
+interface ISCMRepositoryView {
+ readonly repository: ISCMRepository;
+ readonly discoveryTime: number;
+ focused: boolean;
+ selectionIndex: number;
+}
+
export interface ISCMViewServiceState {
readonly all: string[];
+ readonly sortKey: ISCMRepositorySortKey;
readonly visible: number[];
}
@@ -45,20 +59,24 @@ export class SCMViewService implements ISCMViewService {
private previousState: ISCMViewServiceState | undefined;
private disposables = new DisposableStore();
- private _repositories: ISCMRepository[] = [];
+ private _repositories: ISCMRepositoryView[] = [];
get repositories(): ISCMRepository[] {
- return this._repositories;
+ return this._repositories.map(r => r.repository);
}
- private _onDidChangeRepositories = new Emitter<ISCMViewVisibleRepositoryChangeEvent>();
- readonly onDidChangeRepositories = this._onDidChangeRepositories.event;
-
- private _visibleRepositoriesSet = new Set<ISCMRepository>();
- private _visibleRepositories: ISCMRepository[] = [];
-
get visibleRepositories(): ISCMRepository[] {
- return this._visibleRepositories;
+ // In order to match the legacy behaviour, when the repositories are sorted by discovery time,
+ // the visible repositories are sorted by the selection index instead of the discovery time.
+ if (this._repositoriesSortKey === ISCMRepositorySortKey.DiscoveryTime) {
+ return this._repositories.filter(r => r.selectionIndex !== -1)
+ .sort((r1, r2) => r1.selectionIndex - r2.selectionIndex)
+ .map(r => r.repository);
+ }
+
+ return this._repositories
+ .filter(r => r.selectionIndex !== -1)
+ .map(r => r.repository);
}
set visibleRepositories(visibleRepositories: ISCMRepository[]) {
@@ -66,15 +84,18 @@ export class SCMViewService implements ISCMViewService {
const added = new Set<ISCMRepository>();
const removed = new Set<ISCMRepository>();
- for (const repository of visibleRepositories) {
- if (!this._visibleRepositoriesSet.has(repository)) {
- added.add(repository);
+ for (const repositoryView of this._repositories) {
+ // Selected -> !Selected
+ if (!set.has(repositoryView.repository) && repositoryView.selectionIndex !== -1) {
+ repositoryView.selectionIndex = -1;
+ removed.add(repositoryView.repository);
}
- }
-
- for (const repository of this._visibleRepositories) {
- if (!set.has(repository)) {
- removed.add(repository);
+ // Selected | !Selected -> Selected
+ if (set.has(repositoryView.repository)) {
+ if (repositoryView.selectionIndex === -1) {
+ added.add(repositoryView.repository);
+ }
+ repositoryView.selectionIndex = visibleRepositories.indexOf(repositoryView.repository);
}
}
@@ -82,15 +103,17 @@ export class SCMViewService implements ISCMViewService {
return;
}
- this._visibleRepositories = visibleRepositories.sort(this._compareRepositories);
- this._visibleRepositoriesSet = set;
this._onDidSetVisibleRepositories.fire({ added, removed });
- if (this._focusedRepository && removed.has(this._focusedRepository)) {
- this.focus(this._visibleRepositories[0]);
+ // Update focus if the focused repository is not visible anymore
+ if (this._repositories.find(r => r.focused && r.selectionIndex === -1)) {
+ this.focus(this._repositories.find(r => r.selectionIndex !== -1)?.repository);
}
}
+ private _onDidChangeRepositories = new Emitter<ISCMViewVisibleRepositoryChangeEvent>();
+ readonly onDidChangeRepositories = this._onDidChangeRepositories.event;
+
private _onDidSetVisibleRepositories = new Emitter<ISCMViewVisibleRepositoryChangeEvent>();
readonly onDidChangeVisibleRepositories = Event.any(
this._onDidSetVisibleRepositories.event,
@@ -101,53 +124,61 @@ export class SCMViewService implements ISCMViewService {
return e;
}
- return {
- added: Iterable.concat(last.added, e.added),
- removed: Iterable.concat(last.removed, e.removed),
- };
+ const added = new Set(last.added);
+ const removed = new Set(last.removed);
+
+ for (const repository of e.added) {
+ if (removed.has(repository)) {
+ removed.delete(repository);
+ } else {
+ added.add(repository);
+ }
+ }
+ for (const repository of e.removed) {
+ if (added.has(repository)) {
+ added.delete(repository);
+ } else {
+ removed.add(repository);
+ }
+ }
+
+ return { added, removed };
}, 0)
);
- private _focusedRepository: ISCMRepository | undefined;
-
get focusedRepository(): ISCMRepository | undefined {
- return this._focusedRepository;
+ return this._repositories.find(r => r.focused)?.repository;
}
private _onDidFocusRepository = new Emitter<ISCMRepository | undefined>();
readonly onDidFocusRepository = this._onDidFocusRepository.event;
- private _compareRepositories: (op1: ISCMRepository, op2: ISCMRepository) => number;
+ private _repositoriesSortKey: ISCMRepositorySortKey;
+ private _sortKeyContextKey: IContextKey<ISCMRepositorySortKey>;
constructor(
- @ISCMService private readonly scmService: ISCMService,
+ @ISCMService scmService: ISCMService,
+ @IContextKeyService contextKeyService: IContextKeyService,
@IInstantiationService instantiationService: IInstantiationService,
+ @IConfigurationService private readonly configurationService: IConfigurationService,
@IStorageService private readonly storageService: IStorageService,
- @IWorkspaceContextService workspaceContextService: IWorkspaceContextService
+ @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService
) {
this.menus = instantiationService.createInstance(SCMMenus);
- this._compareRepositories = (op1: ISCMRepository, op2: ISCMRepository): number => {
- const name1 = getRepositoryName(workspaceContextService, op1);
- const name2 = getRepositoryName(workspaceContextService, op2);
-
- const nameComparison = compareFileNames(name1, name2);
- if (nameComparison === 0 && op1.provider.rootUri && op2.provider.rootUri) {
- return comparePaths(op1.provider.rootUri.fsPath, op2.provider.rootUri.fsPath);
- }
-
- return nameComparison;
- };
-
- scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables);
- scmService.onDidRemoveRepository(this.onDidRemoveRepository, this, this.disposables);
-
try {
this.previousState = JSON.parse(storageService.get('scm:view:visibleRepositories', StorageScope.WORKSPACE, ''));
} catch {
// noop
}
+ this._repositoriesSortKey = this.previousState?.sortKey ?? this.getViewSortOrder();
+ this._sortKeyContextKey = RepositoryContextKeys.RepositorySortKey.bindTo(contextKeyService);
+ this._sortKeyContextKey.set(this._repositoriesSortKey);
+
+ scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables);
+ scmService.onDidRemoveRepository(this.onDidRemoveRepository, this, this.disposables);
+
for (const repository of scmService.repositories) {
this.onDidAddRepository(repository);
}
@@ -160,50 +191,61 @@ export class SCMViewService implements ISCMViewService {
this.eventuallyFinishLoading();
}
- this.insertRepository(this._repositories, repository);
+ const repositoryView: ISCMRepositoryView = {
+ repository, discoveryTime: Date.now(), focused: false, selectionIndex: -1
+ };
+
let removed: Iterable<ISCMRepository> = Iterable.empty();
if (this.previousState) {
const index = this.previousState.all.indexOf(getProviderStorageKey(repository.provider));
- if (index === -1) { // saw a repo we did not expect
+ if (index === -1) {
+ // This repository is not part of the previous state which means that it
+ // was either manually closed in the previous session, or the repository
+ // was added after the previous session.In this case, we should select all
+ // of the repositories.
const added: ISCMRepository[] = [];
- for (const repo of this.scmService.repositories) { // all should be visible
- if (!this._visibleRepositoriesSet.has(repo)) {
- added.push(repository);
+
+ this.insertRepositoryView(this._repositories, repositoryView);
+ this._repositories.forEach((repositoryView, index) => {
+ if (repositoryView.selectionIndex === -1) {
+ added.push(repositoryView.repository);
}
- }
+ repositoryView.selectionIndex = index;
+ });
- this._visibleRepositoriesSet = new Set(this.scmService.repositories);
- this._visibleRepositories = [...this.scmService.repositories.sort(this._compareRepositories)];
this._onDidChangeRepositories.fire({ added, removed: Iterable.empty() });
- this.finishLoading();
+ this.didSelectRepository = false;
return;
}
- if (this.previousState.visible.indexOf(index) > -1) {
- // First visible repository
- if (!this.didSelectRepository) {
- removed = this._visibleRepositories;
-
- this._visibleRepositories = [];
- this._visibleRepositoriesSet = new Set();
- this.didSelectRepository = true;
- }
- } else {
+ if (this.previousState.visible.indexOf(index) === -1) {
// Explicit selection started
if (this.didSelectRepository) {
+ this.insertRepositoryView(this._repositories, repositoryView);
this._onDidChangeRepositories.fire({ added: Iterable.empty(), removed: Iterable.empty() });
return;
}
+ } else {
+ // First visible repository
+ if (!this.didSelectRepository) {
+ removed = [...this.visibleRepositories];
+ this._repositories.forEach(r => {
+ r.focused = false;
+ r.selectionIndex = -1;
+ });
+
+ this.didSelectRepository = true;
+ }
}
}
- this._visibleRepositoriesSet.add(repository);
- this.insertRepository(this._visibleRepositories, repository);
- this._onDidChangeRepositories.fire({ added: [repository], removed });
+ const maxSelectionIndex = this.getMaxSelectionIndex();
+ this.insertRepositoryView(this._repositories, { ...repositoryView, selectionIndex: maxSelectionIndex + 1 });
+ this._onDidChangeRepositories.fire({ added: [repositoryView.repository], removed });
- if (!this._focusedRepository) {
+ if (!this._repositories.find(r => r.focused)) {
this.focus(repository);
}
}
@@ -213,39 +255,29 @@ export class SCMViewService implements ISCMViewService {
this.eventuallyFinishLoading();
}
- let added: Iterable<ISCMRepository> = Iterable.empty();
+ const repositoriesIndex = this._repositories.findIndex(r => r.repository === repository);
- const repositoriesIndex = this._repositories.indexOf(repository);
- const visibleRepositoriesIndex = this._visibleRepositories.indexOf(repository);
-
- if (repositoriesIndex > -1) {
- this._repositories.splice(repositoriesIndex, 1);
+ if (repositoriesIndex === -1) {
+ return;
}
- if (visibleRepositoriesIndex > -1) {
- this._visibleRepositories.splice(visibleRepositoriesIndex, 1);
- this._visibleRepositoriesSet.delete(repository);
-
- if (this._repositories.length > 0 && this._visibleRepositories.length === 0) {
- const first = this._repositories[0];
+ let added: Iterable<ISCMRepository> = Iterable.empty();
+ const repositoryView = this._repositories.splice(repositoriesIndex, 1);
- this._visibleRepositories.push(first);
- this._visibleRepositoriesSet.add(first);
- added = [first];
- }
+ if (this._repositories.length > 0 && this.visibleRepositories.length === 0) {
+ this._repositories[0].selectionIndex = 0;
+ added = [this._repositories[0].repository];
}
- if (repositoriesIndex > -1 || visibleRepositoriesIndex > -1) {
- this._onDidChangeRepositories.fire({ added, removed: [repository] });
- }
+ this._onDidChangeRepositories.fire({ added, removed: repositoryView.map(r => r.repository) });
- if (this._focusedRepository === repository) {
- this.focus(this._visibleRepositories[0]);
+ if (repositoryView.length === 1 && repositoryView[0].focused && this.visibleRepositories.length > 0) {
+ this.focus(this.visibleRepositories[0]);
}
}
isVisible(repository: ISCMRepository): boolean {
- return this._visibleRepositoriesSet.has(repository);
+ return this._repositories.find(r => r.repository === repository)?.selectionIndex !== -1;
}
toggleVisibility(repository: ISCMRepository, visible?: boolean): void {
@@ -269,18 +301,71 @@ export class SCMViewService implements ISCMViewService {
}
}
+ toggleSortKey(sortKey: ISCMRepositorySortKey): void {
+ this._repositoriesSortKey = sortKey;
+ this._sortKeyContextKey.set(this._repositoriesSortKey);
+ this._repositories.sort(this.compareRepositories.bind(this));
+
+ this._onDidChangeRepositories.fire({ added: Iterable.empty(), removed: Iterable.empty() });
+ }
+
focus(repository: ISCMRepository | undefined): void {
- if (repository && !this.visibleRepositories.includes(repository)) {
+ if (repository && !this.isVisible(repository)) {
return;
}
- this._focusedRepository = repository;
- this._onDidFocusRepository.fire(repository);
+ this._repositories.forEach(r => r.focused = r.repository === repository);
+
+ if (this._repositories.find(r => r.focused)) {
+ this._onDidFocusRepository.fire(repository);
+ }
+ }
+
+ private compareRepositories(op1: ISCMRepositoryView, op2: ISCMRepositoryView): number {
+ // Sort by discovery time
+ if (this._repositoriesSortKey === ISCMRepositorySortKey.DiscoveryTime) {
+ return op1.discoveryTime - op2.discoveryTime;
+ }
+
+ // Sort by path
+ if (this._repositoriesSortKey === 'path' && op1.repository.provider.rootUri && op2.repository.provider.rootUri) {
+ return comparePaths(op1.repository.provider.rootUri.fsPath, op2.repository.provider.rootUri.fsPath);
+ }
+
+ // Sort by name, path
+ const name1 = getRepositoryName(this.workspaceContextService, op1.repository);
+ const name2 = getRepositoryName(this.workspaceContextService, op2.repository);
+
+ const nameComparison = compareFileNames(name1, name2);
+ if (nameComparison === 0 && op1.repository.provider.rootUri && op2.repository.provider.rootUri) {
+ return comparePaths(op1.repository.provider.rootUri.fsPath, op2.repository.provider.rootUri.fsPath);
+ }
+
+ return nameComparison;
+ }
+
+ private getMaxSelectionIndex(): number {
+ return this._repositories.length === 0 ? -1 :
+ Math.max(...this._repositories.map(r => r.selectionIndex));
+ }
+
+ private getViewSortOrder(): ISCMRepositorySortKey {
+ const sortOder = this.configurationService.getValue<'discovery time' | 'name' | 'path'>('scm.repositories.sortOrder');
+ switch (sortOder) {
+ case 'discovery time':
+ return ISCMRepositorySortKey.DiscoveryTime;
+ case 'name':
+ return ISCMRepositorySortKey.Name;
+ case 'path':
+ return ISCMRepositorySortKey.Path;
+ default:
+ return ISCMRepositorySortKey.DiscoveryTime;
+ }
}
- private insertRepository(repositories: ISCMRepository[], repository: ISCMRepository): void {
- const index = binarySearch(repositories, repository, this._compareRepositories);
- repositories.splice(index < 0 ? ~index : index, 0, repository);
+ private insertRepositoryView(repositories: ISCMRepositoryView[], repositoryView: ISCMRepositoryView): void {
+ const index = binarySearch(repositories, repositoryView, this.compareRepositories.bind(this));
+ repositories.splice(index < 0 ? ~index : index, 0, repositoryView);
}
private onWillSaveState(): void {
@@ -290,7 +375,7 @@ export class SCMViewService implements ISCMViewService {
const all = this.repositories.map(r => getProviderStorageKey(r.provider));
const visible = this.visibleRepositories.map(r => all.indexOf(getProviderStorageKey(r.provider)));
- const raw = JSON.stringify({ all, visible });
+ const raw = JSON.stringify({ all, sortKey: this._repositoriesSortKey, visible });
this.storageService.store('scm:view:visibleRepositories', raw, StorageScope.WORKSPACE, StorageTarget.MACHINE);
}
diff --git a/src/vs/workbench/contrib/scm/common/scm.ts b/src/vs/workbench/contrib/scm/common/scm.ts
index c46af5c54b5..e180e8e9d3c 100644
--- a/src/vs/workbench/contrib/scm/common/scm.ts
+++ b/src/vs/workbench/contrib/scm/common/scm.ts
@@ -135,6 +135,7 @@ export interface ISCMInput {
}
export interface ISCMRepository extends IDisposable {
+ readonly id: string;
readonly provider: ISCMProvider;
readonly input: ISCMInput;
}
@@ -144,9 +145,11 @@ export interface ISCMService {
readonly _serviceBrand: undefined;
readonly onDidAddRepository: Event<ISCMRepository>;
readonly onDidRemoveRepository: Event<ISCMRepository>;
- readonly repositories: ISCMRepository[];
+ readonly repositories: Iterable<ISCMRepository>;
+ readonly repositoryCount: number;
registerSCMProvider(provider: ISCMProvider): ISCMRepository;
+ getRepository(id: string): ISCMRepository | undefined;
}
export interface ISCMTitleMenu {
@@ -168,6 +171,12 @@ export interface ISCMMenus {
getRepositoryMenus(provider: ISCMProvider): ISCMRepositoryMenus;
}
+export const enum ISCMRepositorySortKey {
+ DiscoveryTime = 'discoveryTime',
+ Name = 'name',
+ Path = 'path'
+}
+
export const ISCMViewService = createDecorator<ISCMViewService>('scmView');
export interface ISCMViewVisibleRepositoryChangeEvent {
@@ -189,6 +198,8 @@ export interface ISCMViewService {
isVisible(repository: ISCMRepository): boolean;
toggleVisibility(repository: ISCMRepository, visible?: boolean): void;
+ toggleSortKey(sortKey: ISCMRepositorySortKey): void;
+
readonly focusedRepository: ISCMRepository | undefined;
readonly onDidFocusRepository: Event<ISCMRepository | undefined>;
focus(repository: ISCMRepository): void;
diff --git a/src/vs/workbench/contrib/scm/common/scmService.ts b/src/vs/workbench/contrib/scm/common/scmService.ts
index 8fbe71b55e8..7877ba1d34e 100644
--- a/src/vs/workbench/contrib/scm/common/scmService.ts
+++ b/src/vs/workbench/contrib/scm/common/scmService.ts
@@ -175,10 +175,10 @@ class SCMInput implements ISCMInput {
const history = [...this.historyNavigator].map(s => s ?? '');
if (history.length === 0 || (history.length === 1 && history[0] === '')) {
- return;
+ storageService.remove(key, StorageScope.GLOBAL);
+ } else {
+ storageService.store(key, JSON.stringify({ timestamp: new Date().getTime(), history }), StorageScope.GLOBAL, StorageTarget.MACHINE);
}
-
- storageService.store(key, JSON.stringify({ timestamp: new Date().getTime(), history }), StorageScope.GLOBAL, StorageTarget.MACHINE);
this.didChangeHistory = false;
});
}
@@ -242,6 +242,7 @@ class SCMRepository implements ISCMRepository {
readonly input: ISCMInput = new SCMInput(this, this.storageService);
constructor(
+ public readonly id: string,
public readonly provider: ISCMProvider,
private disposable: IDisposable,
@IStorageService private storageService: IStorageService
@@ -266,9 +267,9 @@ export class SCMService implements ISCMService {
declare readonly _serviceBrand: undefined;
- private _providerIds = new Set<string>();
- private _repositories: ISCMRepository[] = [];
- get repositories(): ISCMRepository[] { return [...this._repositories]; }
+ private _repositories = new Map<string, ISCMRepository>();
+ get repositories(): Iterable<ISCMRepository> { return this._repositories.values(); }
+ get repositoryCount(): number { return this._repositories.size; }
private providerCount: IContextKey<number>;
@@ -289,31 +290,25 @@ export class SCMService implements ISCMService {
registerSCMProvider(provider: ISCMProvider): ISCMRepository {
this.logService.trace('SCMService#registerSCMProvider');
- if (this._providerIds.has(provider.id)) {
+ if (this._repositories.has(provider.id)) {
throw new Error(`SCM Provider ${provider.id} already exists.`);
}
- this._providerIds.add(provider.id);
-
const disposable = toDisposable(() => {
- const index = this._repositories.indexOf(repository);
-
- if (index < 0) {
- return;
- }
-
- this._providerIds.delete(provider.id);
- this._repositories.splice(index, 1);
+ this._repositories.delete(provider.id);
this._onDidRemoveProvider.fire(repository);
-
- this.providerCount.set(this._repositories.length);
+ this.providerCount.set(this._repositories.size);
});
- const repository = new SCMRepository(provider, disposable, this.storageService);
- this._repositories.push(repository);
+ const repository = new SCMRepository(provider.id, provider, disposable, this.storageService);
+ this._repositories.set(provider.id, repository);
this._onDidAddProvider.fire(repository);
- this.providerCount.set(this._repositories.length);
+ this.providerCount.set(this._repositories.size);
return repository;
}
+
+ getRepository(id: string): ISCMRepository | undefined {
+ return this._repositories.get(id);
+ }
}
diff --git a/src/vs/workbench/contrib/search/browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts
index 4ed1c60a5ca..fb3d698f583 100644
--- a/src/vs/workbench/contrib/search/browser/search.contribution.ts
+++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts
@@ -3,7 +3,6 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { Action } from 'vs/base/common/actions';
import { onUnexpectedError } from 'vs/base/common/errors';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import * as platform from 'vs/base/common/platform';
@@ -594,25 +593,37 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, {
when: ContextKeyExpr.and(ExplorerRootContext, ExplorerFolderContext.toNegated())
});
-
-class ShowAllSymbolsAction extends Action {
+registerAction2(class ShowAllSymbolsAction extends Action2 {
static readonly ID = 'workbench.action.showAllSymbols';
static readonly LABEL = nls.localize('showTriggerActions', "Go to Symbol in Workspace...");
static readonly ALL_SYMBOLS_PREFIX = '#';
constructor(
- actionId: string,
- actionLabel: string,
- @IQuickInputService private readonly quickInputService: IQuickInputService
) {
- super(actionId, actionLabel);
+ super({
+ id: 'workbench.action.showAllSymbols',
+ title: {
+ value: nls.localize('showTriggerActions', "Go to Symbol in Workspace..."),
+ original: 'Go to Symbol in Workspace...'
+ },
+ f1: true,
+ menu: {
+ id: MenuId.TitleMenuQuickPick,
+ group: '1/workspaceNav',
+ order: 2
+ },
+ keybinding: {
+ weight: KeybindingWeight.WorkbenchContrib,
+ primary: KeyMod.CtrlCmd | KeyCode.KeyT
+ }
+ });
}
- override async run(): Promise<void> {
- this.quickInputService.quickAccess.show(ShowAllSymbolsAction.ALL_SYMBOLS_PREFIX);
+ override async run(accessor: ServicesAccessor): Promise<void> {
+ accessor.get(IQuickInputService).quickAccess.show(ShowAllSymbolsAction.ALL_SYMBOLS_PREFIX);
}
-}
+});
const SEARCH_MODE_CONFIG = 'search.mode';
@@ -793,7 +804,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
}
});
-registry.registerWorkbenchAction(SyncActionDescriptor.from(ShowAllSymbolsAction, { primary: KeyMod.CtrlCmd | KeyCode.KeyT }), 'Go to Symbol in Workspace...');
registry.registerWorkbenchAction(SyncActionDescriptor.from(ToggleSearchOnTypeAction), 'Search: Toggle Search on Type', category.value);
// Register Quick Access Handler
diff --git a/src/vs/workbench/contrib/search/browser/searchResultsView.ts b/src/vs/workbench/contrib/search/browser/searchResultsView.ts
index eebc83d22a0..976f351c60e 100644
--- a/src/vs/workbench/contrib/search/browser/searchResultsView.ts
+++ b/src/vs/workbench/contrib/search/browser/searchResultsView.ts
@@ -54,8 +54,10 @@ interface IMatchTemplate {
export class SearchDelegate implements IListVirtualDelegate<RenderableMatch> {
+ public static ITEM_HEIGHT = 22;
+
getHeight(element: RenderableMatch): number {
- return 22;
+ return SearchDelegate.ITEM_HEIGHT;
}
getTemplateId(element: RenderableMatch): string {
diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts
index 90411d0caec..17baf536043 100644
--- a/src/vs/workbench/contrib/search/browser/searchView.ts
+++ b/src/vs/workbench/contrib/search/browser/searchView.ts
@@ -8,7 +8,6 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import * as aria from 'vs/base/browser/ui/aria/aria';
import { MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
import { IIdentityProvider } from 'vs/base/browser/ui/list/list';
-import { Orientation } from 'vs/base/browser/ui/sash/sash';
import { ITreeContextMenuEvent, ITreeElement } from 'vs/base/browser/ui/tree/tree';
import { IAction } from 'vs/base/common/actions';
import { Delayer } from 'vs/base/common/async';
@@ -45,20 +44,24 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { getSelectionKeyboardEvent, WorkbenchObjectTree } from 'vs/platform/list/browser/listService';
-import { INotificationService, } from 'vs/platform/notification/common/notification';
+import { INotificationService } from 'vs/platform/notification/common/notification';
import { IOpenerService, withSelection } from 'vs/platform/opener/common/opener';
import { IProgress, IProgressService, IProgressStep } from 'vs/platform/progress/common/progress';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, editorFindMatchHighlight, editorFindMatchHighlightBorder, foreground, listActiveSelectionForeground, textLinkActiveForeground, textLinkForeground, toolbarActiveBackground, toolbarHoverBackground } from 'vs/platform/theme/common/colorRegistry';
+import { isHighContrast } from 'vs/platform/theme/common/theme';
import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { OpenFileFolderAction, OpenFolderAction } from 'vs/workbench/browser/actions/workspaceActions';
+import { ResourceListDnDHandler } from 'vs/workbench/browser/dnd';
import { ResourceLabels } from 'vs/workbench/browser/labels';
import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPane';
import { IEditorPane } from 'vs/workbench/common/editor';
import { Memento, MementoObject } from 'vs/workbench/common/memento';
import { IViewDescriptorService } from 'vs/workbench/common/views';
+import { NotebookFindWidget } from 'vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget';
+import { NotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookEditor';
import { ExcludePatternInputWidget, IncludePatternInputWidget } from 'vs/workbench/contrib/search/browser/patternInputWidget';
import { appendKeyBindingLabel, IFindInFilesArgs } from 'vs/workbench/contrib/search/browser/searchActions';
import { searchDetailsIcon } from 'vs/workbench/contrib/search/browser/searchIcons';
@@ -66,7 +69,6 @@ import { renderSearchMessage } from 'vs/workbench/contrib/search/browser/searchM
import { FileMatchRenderer, FolderMatchRenderer, MatchRenderer, SearchAccessibilityProvider, SearchDelegate } from 'vs/workbench/contrib/search/browser/searchResultsView';
import { ISearchWidgetOptions, SearchWidget } from 'vs/workbench/contrib/search/browser/searchWidget';
import * as Constants from 'vs/workbench/contrib/search/common/constants';
-import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/services/search/common/queryBuilder';
import { IReplaceService } from 'vs/workbench/contrib/search/common/replace';
import { getOutOfWorkspaceEditorResources, SearchStateKey, SearchUIState } from 'vs/workbench/contrib/search/common/search';
import { ISearchHistoryService, ISearchHistoryValues } from 'vs/workbench/contrib/search/common/searchHistoryService';
@@ -74,11 +76,10 @@ import { FileMatch, FileMatchOrMatch, FolderMatch, FolderMatchWithResource, ICha
import { createEditorFromSearchResult } from 'vs/workbench/contrib/searchEditor/browser/searchEditorActions';
import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { IPreferencesService, ISettingsEditorOptions } from 'vs/workbench/services/preferences/common/preferences';
+import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/services/search/common/queryBuilder';
import { IPatternInfo, ISearchComplete, ISearchConfiguration, ISearchConfigurationProperties, ITextQuery, SearchCompletionExitCode, SearchSortOrder, TextSearchCompleteMessageType } from 'vs/workbench/services/search/common/search';
import { TextSearchCompleteMessage } from 'vs/workbench/services/search/common/searchExtTypes';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
-import { ResourceListDnDHandler } from 'vs/workbench/browser/dnd';
-import { isHighContrast } from 'vs/platform/theme/common/theme';
const $ = dom.$;
@@ -739,7 +740,8 @@ export class SearchView extends ViewPane {
selectionNavigation: true,
overrideStyles: {
listBackground: this.getBackgroundColor()
- }
+ },
+ additionalScrollHeight: SearchDelegate.ITEM_HEIGHT
}));
this._register(this.tree.onContextMenu(e => this.onContextMenu(e)));
const updateHasSomeCollapsible = () => this.toggleCollapseStateDelayer.trigger(() => this.hasSomeCollapsibleResultKey.set(this.hasSomeCollapsible()));
@@ -1065,7 +1067,9 @@ export class SearchView extends ViewPane {
this.inputPatternExcludes.setWidth(this.size.width - 28 /* container margin */);
this.inputPatternIncludes.setWidth(this.size.width - 28 /* container margin */);
- this.tree.layout(); // The tree will measure its container
+ const widgetHeight = dom.getTotalHeight(this.searchWidgetsContainerElement);
+ const messagesHeight = dom.getTotalHeight(this.messagesElement);
+ this.tree.layout(this.size.height - widgetHeight - messagesHeight, this.size.width - 28);
}
protected override layoutBody(height: number, width: number): void {
@@ -1282,7 +1286,7 @@ export class SearchView extends ViewPane {
}
if (!skipLayout && this.size) {
- this.layout(this._orientation === Orientation.VERTICAL ? this.size.height : this.size.width);
+ this.reLayout();
}
}
@@ -1710,18 +1714,22 @@ export class SearchView extends ViewPane {
this.open(lineMatch, preserveFocus, sideBySide, pinned);
}
- open(element: FileMatchOrMatch, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise<void> {
+ async open(element: FileMatchOrMatch, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise<void> {
const selection = this.getSelectionFrom(element);
const resource = element instanceof Match ? element.parent().resource : (<FileMatch>element).resource;
- return this.editorService.openEditor({
- resource: resource,
- options: {
- preserveFocus,
- pinned,
- selection,
- revealIfVisible: true
- }
- }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP).then(editor => {
+
+ let editor: IEditorPane | undefined;
+ try {
+ editor = await this.editorService.openEditor({
+ resource: resource,
+ options: {
+ preserveFocus,
+ pinned,
+ selection,
+ revealIfVisible: true
+ }
+ }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP);
+
const editorControl = editor?.getControl();
if (element instanceof Match && preserveFocus && isCodeEditor(editorControl)) {
this.viewModel.searchResult.rangeHighlightDecorations.highlightRange(
@@ -1731,7 +1739,16 @@ export class SearchView extends ViewPane {
} else {
this.viewModel.searchResult.rangeHighlightDecorations.removeHighlightRange();
}
- }, errors.onUnexpectedError);
+ } catch (err) {
+ errors.onUnexpectedError(err);
+ return;
+ }
+
+ if (editor instanceof NotebookEditor) {
+ const controller = editor.getControl()?.getContribution<NotebookFindWidget>(NotebookFindWidget.id);
+ const matchIndex = element instanceof Match ? element.parent().matches().findIndex(e => e.id() === element.id()) : undefined;
+ controller?.show(this.searchWidget.searchInput.getValue(), { matchIndex, focus: false });
+ }
}
openEditorWithMultiCursor(element: FileMatchOrMatch): Promise<void> {
diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts
index d9d0f908be9..aed2458e00b 100644
--- a/src/vs/workbench/contrib/search/browser/searchWidget.ts
+++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts
@@ -366,7 +366,12 @@ export class SearchWidget extends Widget {
this.contextLinesInput = new InputBox(searchInputContainer, this.contextViewService, { type: 'number' });
this.contextLinesInput.element.classList.add('context-lines-input');
this.contextLinesInput.value = '' + (this.configurationService.getValue<ISearchConfigurationProperties>('search').searchEditor.defaultNumberOfContextLines ?? 1);
- this._register(this.contextLinesInput.onDidChange(() => this.onContextLinesChanged()));
+ this._register(this.contextLinesInput.onDidChange((value: string) => {
+ if (value !== '0') {
+ this.showContextToggle.checked = true;
+ }
+ this.onContextLinesChanged();
+ }));
this._register(attachInputBoxStyler(this.contextLinesInput, this.themeService));
dom.append(searchInputContainer, this.showContextToggle.domNode);
}
diff --git a/src/vs/workbench/contrib/search/test/electron-browser/textsearch.perf.integrationTest.ts b/src/vs/workbench/contrib/search/test/electron-browser/textsearch.perf.integrationTest.ts
index d1a74339e79..cfcbec62d50 100644
--- a/src/vs/workbench/contrib/search/test/electron-browser/textsearch.perf.integrationTest.ts
+++ b/src/vs/workbench/contrib/search/test/electron-browser/textsearch.perf.integrationTest.ts
@@ -43,6 +43,7 @@ import { TestContextService, TestTextResourcePropertiesService } from 'vs/workbe
import { TestEnvironmentService } from 'vs/workbench/test/electron-browser/workbenchTestServices';
import { LanguageFeatureDebounceService } from 'vs/editor/common/services/languageFeatureDebounce';
import { LanguageFeaturesService } from 'vs/editor/common/services/languageFeaturesService';
+import { staticObservableValue } from 'vs/base/common/observableValue';
// declare var __dirname: string;
@@ -182,7 +183,7 @@ suite.skip('TextSearch performance (integration)', () => {
class TestTelemetryService implements ITelemetryService {
public _serviceBrand: undefined;
- public telemetryLevel = TelemetryLevel.USAGE;
+ public telemetryLevel = staticObservableValue(TelemetryLevel.USAGE);
public sendErrorTelemetry = true;
public events: any[] = [];
diff --git a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts
index 4d0742dc00a..ed23a5f2632 100644
--- a/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts
+++ b/src/vs/workbench/contrib/searchEditor/browser/searchEditor.contribution.ts
@@ -121,7 +121,7 @@ class SearchEditorInputSerializer implements IEditorSerializer {
const config = input.tryReadConfigSync();
const dirty = input.isDirty();
- const matchRanges = input.getMatchRanges();
+ const matchRanges = dirty ? input.getMatchRanges() : [];
const backingUri = input.backingUri;
return JSON.stringify({ modelUri, dirty, config, name: input.getName(), matchRanges, backingUri: backingUri?.toString() } as SerializedSearchEditor);
diff --git a/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts b/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts
index bbee0e94ef5..9b5871c2e89 100644
--- a/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts
+++ b/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts
@@ -4,11 +4,10 @@
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
-import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { ILanguageService } from 'vs/editor/common/languages/language';
import { extname } from 'vs/base/common/path';
-import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
+import { MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/actions';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { URI } from 'vs/base/common/uri';
import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets.contribution';
@@ -19,8 +18,7 @@ import { IFileService } from 'vs/platform/files/common/files';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { isValidBasename } from 'vs/base/common/extpath';
import { joinPath, basename } from 'vs/base/common/resources';
-
-const id = 'workbench.action.openSnippets';
+import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
namespace ISnippetPick {
export function is(thing: object | undefined): thing is ISnippetPick {
@@ -201,82 +199,80 @@ async function createLanguageSnippetFile(pick: ISnippetPick, fileService: IFileS
await textFileService.write(pick.filepath, contents);
}
-CommandsRegistry.registerCommand(id, async (accessor): Promise<any> => {
-
- const snippetService = accessor.get(ISnippetsService);
- const quickInputService = accessor.get(IQuickInputService);
- const opener = accessor.get(IOpenerService);
- const languageService = accessor.get(ILanguageService);
- const envService = accessor.get(IEnvironmentService);
- const workspaceService = accessor.get(IWorkspaceContextService);
- const fileService = accessor.get(IFileService);
- const textFileService = accessor.get(ITextFileService);
-
- const picks = await computePicks(snippetService, envService, languageService);
- const existing: QuickPickInput[] = picks.existing;
-
- type SnippetPick = IQuickPickItem & { uri: URI } & { scope: string };
- const globalSnippetPicks: SnippetPick[] = [{
- scope: nls.localize('new.global_scope', 'global'),
- label: nls.localize('new.global', "New Global Snippets file..."),
- uri: envService.snippetsHome
- }];
-
- const workspaceSnippetPicks: SnippetPick[] = [];
- for (const folder of workspaceService.getWorkspace().folders) {
- workspaceSnippetPicks.push({
- scope: nls.localize('new.workspace_scope', "{0} workspace", folder.name),
- label: nls.localize('new.folder', "New Snippets file for '{0}'...", folder.name),
- uri: folder.toResource('.vscode')
+registerAction2(class ConfigureSnippets extends Action2 {
+
+ constructor() {
+ super({
+ id: 'workbench.action.openSnippets',
+ title: {
+ value: nls.localize('openSnippet.label', "Configure User Snippets"),
+ original: 'Configure User Snippets'
+ },
+ shortTitle: {
+ value: nls.localize('userSnippets', "User Snippets"),
+ mnemonicTitle: nls.localize({ key: 'miOpenSnippets', comment: ['&& denotes a mnemonic'] }, "User &&Snippets"),
+ original: 'User Snippets'
+ },
+ menu: [
+ { id: MenuId.CommandPalette },
+ { id: MenuId.MenubarPreferencesMenu, group: '3_snippets', order: 1 },
+ { id: MenuId.GlobalActivity, group: '3_snippets', order: 1 },
+ ]
});
}
- if (existing.length > 0) {
- existing.unshift({ type: 'separator', label: nls.localize('group.global', "Existing Snippets") });
- existing.push({ type: 'separator', label: nls.localize('new.global.sep', "New Snippets") });
- } else {
- existing.push({ type: 'separator', label: nls.localize('new.global.sep', "New Snippets") });
- }
-
- const pick = await quickInputService.pick(([] as QuickPickInput[]).concat(existing, globalSnippetPicks, workspaceSnippetPicks, picks.future), {
- placeHolder: nls.localize('openSnippet.pickLanguage', "Select Snippets File or Create Snippets"),
- matchOnDescription: true
- });
+ async run(accessor: ServicesAccessor, ...args: any[]): Promise<any> {
+
+ const snippetService = accessor.get(ISnippetsService);
+ const quickInputService = accessor.get(IQuickInputService);
+ const opener = accessor.get(IOpenerService);
+ const languageService = accessor.get(ILanguageService);
+ const envService = accessor.get(IEnvironmentService);
+ const workspaceService = accessor.get(IWorkspaceContextService);
+ const fileService = accessor.get(IFileService);
+ const textFileService = accessor.get(ITextFileService);
+
+ const picks = await computePicks(snippetService, envService, languageService);
+ const existing: QuickPickInput[] = picks.existing;
+
+ type SnippetPick = IQuickPickItem & { uri: URI } & { scope: string };
+ const globalSnippetPicks: SnippetPick[] = [{
+ scope: nls.localize('new.global_scope', 'global'),
+ label: nls.localize('new.global', "New Global Snippets file..."),
+ uri: envService.snippetsHome
+ }];
+
+ const workspaceSnippetPicks: SnippetPick[] = [];
+ for (const folder of workspaceService.getWorkspace().folders) {
+ workspaceSnippetPicks.push({
+ scope: nls.localize('new.workspace_scope', "{0} workspace", folder.name),
+ label: nls.localize('new.folder', "New Snippets file for '{0}'...", folder.name),
+ uri: folder.toResource('.vscode')
+ });
+ }
- if (globalSnippetPicks.indexOf(pick as SnippetPick) >= 0) {
- return createSnippetFile((pick as SnippetPick).scope, (pick as SnippetPick).uri, quickInputService, fileService, textFileService, opener);
- } else if (workspaceSnippetPicks.indexOf(pick as SnippetPick) >= 0) {
- return createSnippetFile((pick as SnippetPick).scope, (pick as SnippetPick).uri, quickInputService, fileService, textFileService, opener);
- } else if (ISnippetPick.is(pick)) {
- if (pick.hint) {
- await createLanguageSnippetFile(pick, fileService, textFileService);
+ if (existing.length > 0) {
+ existing.unshift({ type: 'separator', label: nls.localize('group.global', "Existing Snippets") });
+ existing.push({ type: 'separator', label: nls.localize('new.global.sep', "New Snippets") });
+ } else {
+ existing.push({ type: 'separator', label: nls.localize('new.global.sep', "New Snippets") });
}
- return opener.open(pick.filepath);
- }
-});
-MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
- command: {
- id,
- title: { value: nls.localize('openSnippet.label', "Configure User Snippets"), original: 'Configure User Snippets' },
- category: { value: nls.localize('preferences', "Preferences"), original: 'Preferences' }
- }
-});
+ const pick = await quickInputService.pick(([] as QuickPickInput[]).concat(existing, globalSnippetPicks, workspaceSnippetPicks, picks.future), {
+ placeHolder: nls.localize('openSnippet.pickLanguage', "Select Snippets File or Create Snippets"),
+ matchOnDescription: true
+ });
-MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, {
- group: '3_snippets',
- command: {
- id,
- title: nls.localize({ key: 'miOpenSnippets', comment: ['&& denotes a mnemonic'] }, "User &&Snippets")
- },
- order: 1
-});
+ if (globalSnippetPicks.indexOf(pick as SnippetPick) >= 0) {
+ return createSnippetFile((pick as SnippetPick).scope, (pick as SnippetPick).uri, quickInputService, fileService, textFileService, opener);
+ } else if (workspaceSnippetPicks.indexOf(pick as SnippetPick) >= 0) {
+ return createSnippetFile((pick as SnippetPick).scope, (pick as SnippetPick).uri, quickInputService, fileService, textFileService, opener);
+ } else if (ISnippetPick.is(pick)) {
+ if (pick.hint) {
+ await createLanguageSnippetFile(pick, fileService, textFileService);
+ }
+ return opener.open(pick.filepath);
+ }
-MenuRegistry.appendMenuItem(MenuId.GlobalActivity, {
- group: '3_snippets',
- command: {
- id,
- title: nls.localize('userSnippets', "User Snippets")
- },
- order: 1
+ }
});
diff --git a/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts b/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts
index 782cb1493fd..3a0088a1f48 100644
--- a/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts
+++ b/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts
@@ -112,7 +112,7 @@ class InsertSnippetAction extends EditorAction {
}
languageId = langId;
} else {
- editor.getModel().tokenizeIfCheap(lineNumber);
+ editor.getModel().tokenization.tokenizeIfCheap(lineNumber);
languageId = editor.getModel().getLanguageIdAtPosition(lineNumber, column);
// validate the `languageId` to ensure this is a user
diff --git a/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts
index aa6e96d9afe..2b7c3c8ed98 100644
--- a/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts
+++ b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts
@@ -18,6 +18,7 @@ import { isPatternInWord } from 'vs/base/common/filters';
import { StopWatch } from 'vs/base/common/stopwatch';
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
import { getWordAtText } from 'vs/editor/common/core/wordHelper';
+import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
export class SnippetCompletion implements CompletionItem {
@@ -29,6 +30,7 @@ export class SnippetCompletion implements CompletionItem {
sortText: string;
kind: CompletionItemKind;
insertTextRules: CompletionItemInsertTextRule;
+ extensionId?: ExtensionIdentifier;
constructor(
readonly snippet: Snippet,
@@ -37,6 +39,7 @@ export class SnippetCompletion implements CompletionItem {
this.label = { label: snippet.prefix, description: snippet.name };
this.detail = localize('detail.snippet', "{0} ({1})", snippet.description || snippet.name, snippet.source);
this.insertText = snippet.codeSnippet;
+ this.extensionId = snippet.extensionId;
this.range = range;
this.sortText = `${snippet.snippetSource === SnippetSource.Extension ? 'z' : 'a'}-${snippet.prefix}`;
this.kind = CompletionItemKind.Snippet;
@@ -182,7 +185,7 @@ export class SnippetCompletionProvider implements CompletionItemProvider {
// validate the `languageId` to ensure this is a user
// facing language with a name and the chance to have
// snippets, else fall back to the outer language
- model.tokenizeIfCheap(position.lineNumber);
+ model.tokenization.tokenizeIfCheap(position.lineNumber);
let languageId = model.getLanguageIdAtPosition(position.lineNumber, position.column);
if (!this._languageService.getLanguageName(languageId)) {
languageId = model.getLanguageId();
diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts b/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts
index 25fafbfeda6..84f407b5f3e 100644
--- a/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts
+++ b/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts
@@ -12,7 +12,7 @@ import { KnownSnippetVariableNames } from 'vs/editor/contrib/snippet/browser/sni
import { isFalsyOrWhitespace } from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
import { IFileService } from 'vs/platform/files/common/files';
-import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
+import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { IdleValue } from 'vs/base/common/async';
import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader';
import { relativePath } from 'vs/base/common/resources';
@@ -114,7 +114,8 @@ export class Snippet {
readonly body: string,
readonly source: string,
readonly snippetSource: SnippetSource,
- readonly snippetIdentifier?: string
+ readonly snippetIdentifier?: string,
+ readonly extensionId?: ExtensionIdentifier,
) {
this.prefixLow = prefix.toLowerCase();
this._bodyInsights = new IdleValue(() => new SnippetBodyInsights(this.body));
@@ -332,7 +333,8 @@ export class SnippetFile {
body,
source,
this.source,
- this._extension && `${relativePath(this._extension.extensionLocation, this.location)}/${name}`
+ this._extension && `${relativePath(this._extension.extensionLocation, this.location)}/${name}`,
+ this._extension?.identifier,
));
}
}
diff --git a/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts b/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts
index 96c1ecfc5a7..80d25b05968 100644
--- a/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts
+++ b/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts
@@ -38,7 +38,7 @@ registerAction2(class SurroundWithAction extends EditorAction2 {
}
const { lineNumber, column } = editor.getPosition();
- editor.getModel().tokenizeIfCheap(lineNumber);
+ editor.getModel().tokenization.tokenizeIfCheap(lineNumber);
const languageId = editor.getModel().getLanguageIdAtPosition(lineNumber, column);
const allSnippets = await snippetService.getSnippets(languageId, { includeNoPrefixSnippets: true, includeDisabledSnippets: true });
diff --git a/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts b/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts
index 504da91fa71..1f4afaa0a94 100644
--- a/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts
+++ b/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts
@@ -91,7 +91,7 @@ export class TabCompletionController implements IEditorContribution {
// lots of dance for getting the
const selection = this._editor.getSelection();
const model = this._editor.getModel();
- model.tokenizeIfCheap(selection.positionLineNumber);
+ model.tokenization.tokenizeIfCheap(selection.positionLineNumber);
const id = model.getLanguageIdAtPosition(selection.positionLineNumber, selection.positionColumn);
const snippets = this._snippetService.getSnippetsSync(id);
@@ -147,7 +147,7 @@ export class TabCompletionController implements IEditorContribution {
}
};
const registration = this._languageFeaturesService.completionProvider.register(
- { language: model.getLanguageId(), pattern: model.uri.path, scheme: model.uri.scheme },
+ { language: model.getLanguageId(), pattern: model.uri.fsPath, scheme: model.uri.scheme },
this._completionProvider
);
}
diff --git a/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts
index 540e3fce155..9a602558814 100644
--- a/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts
+++ b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts
@@ -6,15 +6,15 @@
import * as assert from 'assert';
import { SnippetCompletion, SnippetCompletionProvider } from 'vs/workbench/contrib/snippets/browser/snippetCompletionProvider';
import { Position } from 'vs/editor/common/core/position';
-import { ModesRegistry } from 'vs/editor/common/languages/modesRegistry';
-import { LanguageService } from 'vs/editor/common/services/languageService';
-import { createTextModel } from 'vs/editor/test/common/testTextModel';
+import { createModelServices, instantiateTextModel } from 'vs/editor/test/common/testTextModel';
import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets.contribution';
import { Snippet, SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile';
import { CompletionContext, CompletionItemLabel, CompletionItemRanges, CompletionTriggerKind } from 'vs/editor/common/languages';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService';
import { EditOperation } from 'vs/editor/common/core/editOperation';
+import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
+import { ILanguageService } from 'vs/editor/common/languages/language';
class SimpleSnippetService implements ISnippetsService {
declare readonly _serviceBrand: undefined;
@@ -37,27 +37,21 @@ class SimpleSnippetService implements ISnippetsService {
}
suite('SnippetsService', function () {
- const disposableStore: DisposableStore = new DisposableStore();
const context: CompletionContext = { triggerKind: CompletionTriggerKind.Invoke };
- suiteSetup(function () {
- disposableStore.add(ModesRegistry.registerLanguage({
- id: 'fooLang',
- extensions: ['.fooLang',]
- }));
- });
-
- suiteTeardown(function () {
- disposableStore.dispose();
- });
-
let disposables: DisposableStore;
- let languageService: LanguageService;
+ let instantiationService: TestInstantiationService;
+ let languageService: ILanguageService;
let snippetService: ISnippetsService;
setup(function () {
disposables = new DisposableStore();
- languageService = disposables.add(new LanguageService());
+ instantiationService = createModelServices(disposables);
+ languageService = instantiationService.get(ILanguageService);
+ disposables.add(languageService.registerLanguage({
+ id: 'fooLang',
+ extensions: ['.fooLang',]
+ }));
snippetService = new SimpleSnippetService([new Snippet(
['fooLang'],
'barTest',
@@ -84,7 +78,7 @@ suite('SnippetsService', function () {
test('snippet completions - simple', function () {
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
- const model = disposables.add(createTextModel('', 'fooLang'));
+ const model = disposables.add(instantiateTextModel(instantiationService, '', 'fooLang'));
return provider.provideCompletionItems(model, new Position(1, 1), context)!.then(result => {
assert.strictEqual(result.incomplete, undefined);
@@ -95,7 +89,7 @@ suite('SnippetsService', function () {
test('snippet completions - simple 2', async function () {
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
- const model = disposables.add(createTextModel('hello ', 'fooLang'));
+ const model = disposables.add(instantiateTextModel(instantiationService, 'hello ', 'fooLang'));
await provider.provideCompletionItems(model, new Position(1, 6) /* hello| */, context)!.then(result => {
assert.strictEqual(result.incomplete, undefined);
@@ -111,7 +105,7 @@ suite('SnippetsService', function () {
test('snippet completions - with prefix', function () {
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
- const model = disposables.add(createTextModel('bar', 'fooLang'));
+ const model = disposables.add(instantiateTextModel(instantiationService, 'bar', 'fooLang'));
return provider.provideCompletionItems(model, new Position(1, 4), context)!.then(result => {
assert.strictEqual(result.incomplete, undefined);
@@ -146,7 +140,7 @@ suite('SnippetsService', function () {
)]);
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
- const model = disposables.add(createTextModel('bar-bar', 'fooLang'));
+ const model = disposables.add(instantiateTextModel(instantiationService, 'bar-bar', 'fooLang'));
await provider.provideCompletionItems(model, new Position(1, 3), context)!.then(result => {
assert.strictEqual(result.incomplete, undefined);
@@ -217,19 +211,19 @@ suite('SnippetsService', function () {
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
- let model = createTextModel('\t<?php', 'fooLang');
+ let model = instantiateTextModel(instantiationService, '\t<?php', 'fooLang');
return provider.provideCompletionItems(model, new Position(1, 7), context)!.then(result => {
assert.strictEqual(result.suggestions.length, 1);
model.dispose();
- model = createTextModel('\t<?', 'fooLang');
+ model = instantiateTextModel(instantiationService, '\t<?', 'fooLang');
return provider.provideCompletionItems(model, new Position(1, 4), context)!;
}).then(result => {
assert.strictEqual(result.suggestions.length, 1);
assert.strictEqual((result.suggestions[0].range as any).insert.startColumn, 2);
model.dispose();
- model = createTextModel('a<?', 'fooLang');
+ model = instantiateTextModel(instantiationService, 'a<?', 'fooLang');
return provider.provideCompletionItems(model, new Position(1, 4), context)!;
}).then(result => {
assert.strictEqual(result.suggestions.length, 1);
@@ -252,7 +246,7 @@ suite('SnippetsService', function () {
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
- let model = disposables.add(createTextModel('<head>\n\t\n>/head>', 'fooLang'));
+ let model = disposables.add(instantiateTextModel(instantiationService, '<head>\n\t\n>/head>', 'fooLang'));
return provider.provideCompletionItems(model, new Position(1, 1), context)!.then(result => {
assert.strictEqual(result.suggestions.length, 1);
return provider.provideCompletionItems(model, new Position(2, 2), context)!;
@@ -282,7 +276,7 @@ suite('SnippetsService', function () {
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
- let model = disposables.add(createTextModel('', 'fooLang'));
+ let model = disposables.add(instantiateTextModel(instantiationService, '', 'fooLang'));
return provider.provideCompletionItems(model, new Position(1, 1), context)!.then(result => {
assert.strictEqual(result.suggestions.length, 2);
let [first, second] = result.suggestions;
@@ -309,7 +303,7 @@ suite('SnippetsService', function () {
)]);
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
- let model = disposables.add(createTextModel('p-', 'fooLang'));
+ let model = disposables.add(instantiateTextModel(instantiationService, 'p-', 'fooLang'));
let result = await provider.provideCompletionItems(model, new Position(1, 2), context)!;
assert.strictEqual(result.suggestions.length, 1);
@@ -334,7 +328,7 @@ suite('SnippetsService', function () {
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
- let model = disposables.add(createTextModel('Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea b', 'fooLang'));
+ let model = disposables.add(instantiateTextModel(instantiationService, 'Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea b', 'fooLang'));
let result = await provider.provideCompletionItems(model, new Position(1, 158), context)!;
assert.strictEqual(result.suggestions.length, 1);
@@ -353,7 +347,7 @@ suite('SnippetsService', function () {
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
- let model = disposables.add(createTextModel(':', 'fooLang'));
+ let model = disposables.add(instantiateTextModel(instantiationService, ':', 'fooLang'));
let result = await provider.provideCompletionItems(model, new Position(1, 2), context)!;
assert.strictEqual(result.suggestions.length, 0);
@@ -372,7 +366,7 @@ suite('SnippetsService', function () {
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
- let model = disposables.add(createTextModel('template', 'fooLang'));
+ let model = disposables.add(instantiateTextModel(instantiationService, 'template', 'fooLang'));
let result = await provider.provideCompletionItems(model, new Position(1, 9), context)!;
assert.strictEqual(result.suggestions.length, 1);
@@ -395,7 +389,7 @@ suite('SnippetsService', function () {
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
- let model = disposables.add(createTextModel('Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea b text_after_b', 'fooLang'));
+ let model = disposables.add(instantiateTextModel(instantiationService, 'Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea b text_after_b', 'fooLang'));
let result = await provider.provideCompletionItems(model, new Position(1, 158), context)!;
assert.strictEqual(result.suggestions.length, 1);
@@ -403,7 +397,7 @@ suite('SnippetsService', function () {
test('issue #61296: VS code freezes when editing CSS file with emoji', async function () {
const languageConfigurationService = new TestLanguageConfigurationService();
- disposableStore.add(languageConfigurationService.register('fooLang', {
+ disposables.add(languageConfigurationService.register('fooLang', {
wordPattern: /(#?-?\d*\.\d\w*%?)|(::?[\w-]*(?=[^,{;]*[,{]))|(([@#.!])?[\w-?]+%?|[@#!.])/g
}));
@@ -419,7 +413,7 @@ suite('SnippetsService', function () {
const provider = new SnippetCompletionProvider(languageService, snippetService, languageConfigurationService);
- let model = disposables.add(createTextModel('.🐷-a-b', 'fooLang'));
+ let model = disposables.add(instantiateTextModel(instantiationService, '.🐷-a-b', 'fooLang'));
let result = await provider.provideCompletionItems(model, new Position(1, 8), context)!;
assert.strictEqual(result.suggestions.length, 1);
@@ -438,7 +432,7 @@ suite('SnippetsService', function () {
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
- let model = disposables.add(createTextModel('a ', 'fooLang'));
+ let model = disposables.add(instantiateTextModel(instantiationService, 'a ', 'fooLang'));
let result = await provider.provideCompletionItems(model, new Position(1, 3), context)!;
assert.strictEqual(result.suggestions.length, 1);
@@ -465,7 +459,7 @@ suite('SnippetsService', function () {
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
- let model = createTextModel(' <', 'fooLang');
+ let model = instantiateTextModel(instantiationService, ' <', 'fooLang');
let result = await provider.provideCompletionItems(model, new Position(1, 3), context)!;
assert.strictEqual(result.suggestions.length, 1);
@@ -473,7 +467,7 @@ suite('SnippetsService', function () {
assert.strictEqual((first.range as any).insert.startColumn, 2);
model.dispose();
- model = createTextModel('1', 'fooLang');
+ model = instantiateTextModel(instantiationService, '1', 'fooLang');
result = await provider.provideCompletionItems(model, new Position(1, 2), context)!;
assert.strictEqual(result.suggestions.length, 1);
@@ -495,7 +489,7 @@ suite('SnippetsService', function () {
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
- let model = createTextModel('not wordFoo bar', 'fooLang');
+ let model = instantiateTextModel(instantiationService, 'not wordFoo bar', 'fooLang');
let result = await provider.provideCompletionItems(model, new Position(1, 3), context)!;
assert.strictEqual(result.suggestions.length, 1);
@@ -504,7 +498,7 @@ suite('SnippetsService', function () {
assert.strictEqual((first.range as any).replace.endColumn, 9);
model.dispose();
- model = createTextModel('not woFoo bar', 'fooLang');
+ model = instantiateTextModel(instantiationService, 'not woFoo bar', 'fooLang');
result = await provider.provideCompletionItems(model, new Position(1, 3), context)!;
assert.strictEqual(result.suggestions.length, 1);
@@ -513,7 +507,7 @@ suite('SnippetsService', function () {
assert.strictEqual((first.range as any).replace.endColumn, 3);
model.dispose();
- model = createTextModel('not word', 'fooLang');
+ model = instantiateTextModel(instantiationService, 'not word', 'fooLang');
result = await provider.provideCompletionItems(model, new Position(1, 1), context)!;
assert.strictEqual(result.suggestions.length, 1);
@@ -537,7 +531,7 @@ suite('SnippetsService', function () {
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
- let model = createTextModel('filler e KEEP ng filler', 'fooLang');
+ let model = instantiateTextModel(instantiationService, 'filler e KEEP ng filler', 'fooLang');
let result = await provider.provideCompletionItems(model, new Position(1, 9), context)!;
assert.strictEqual(result.suggestions.length, 1);
@@ -549,7 +543,7 @@ suite('SnippetsService', function () {
test('Snippet will replace auto-closing pair if specified in prefix', async function () {
const languageConfigurationService = new TestLanguageConfigurationService();
- disposableStore.add(languageConfigurationService.register('fooLang', {
+ disposables.add(languageConfigurationService.register('fooLang', {
brackets: [
['{', '}'],
['[', ']'],
@@ -569,7 +563,7 @@ suite('SnippetsService', function () {
const provider = new SnippetCompletionProvider(languageService, snippetService, languageConfigurationService);
- let model = createTextModel('[psc]', 'fooLang');
+ let model = instantiateTextModel(instantiationService, '[psc]', 'fooLang');
let result = await provider.provideCompletionItems(model, new Position(1, 5), context)!;
assert.strictEqual(result.suggestions.length, 1);
@@ -594,7 +588,7 @@ suite('SnippetsService', function () {
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
- let model = createTextModel(' ci', 'fooLang');
+ let model = instantiateTextModel(instantiationService, ' ci', 'fooLang');
let result = await provider.provideCompletionItems(model, new Position(1, 4), context)!;
assert.strictEqual(result.suggestions.length, 1);
@@ -615,7 +609,7 @@ suite('SnippetsService', function () {
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
- let model = createTextModel('\'\'', 'fooLang');
+ let model = instantiateTextModel(instantiationService, '\'\'', 'fooLang');
let result = await provider.provideCompletionItems(
model,
new Position(1, 2),
@@ -637,7 +631,7 @@ suite('SnippetsService', function () {
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
- let model = createTextModel('\'\'', 'fooLang');
+ let model = instantiateTextModel(instantiationService, '\'\'', 'fooLang');
let result = await provider.provideCompletionItems(
model,
@@ -657,7 +651,7 @@ suite('SnippetsService', function () {
]);
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
- let model = createTextModel('\'hellot\'', 'fooLang');
+ let model = instantiateTextModel(instantiationService, '\'hellot\'', 'fooLang');
let result = await provider.provideCompletionItems(
model,
@@ -678,7 +672,7 @@ suite('SnippetsService', function () {
]);
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
- let model = createTextModel(')*&^', 'fooLang');
+ let model = instantiateTextModel(instantiationService, ')*&^', 'fooLang');
let result = await provider.provideCompletionItems(
model,
@@ -698,7 +692,7 @@ suite('SnippetsService', function () {
]);
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
- let model = createTextModel('foobar', 'fooLang');
+ let model = instantiateTextModel(instantiationService, 'foobar', 'fooLang');
let result = await provider.provideCompletionItems(
model,
@@ -718,7 +712,7 @@ suite('SnippetsService', function () {
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
- let model = createTextModel('function abc(w)', 'fooLang');
+ let model = instantiateTextModel(instantiationService, 'function abc(w)', 'fooLang');
let result = await provider.provideCompletionItems(
model,
new Position(1, 15),
@@ -737,7 +731,7 @@ suite('SnippetsService', function () {
]);
const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService());
- let model = createTextModel('di', 'fooLang');
+ let model = instantiateTextModel(instantiationService, 'di', 'fooLang');
let result = await provider.provideCompletionItems(
model,
new Position(1, 3),
diff --git a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTags.ts b/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTags.ts
index 395145ee8b4..8e263a87401 100644
--- a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTags.ts
+++ b/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTags.ts
@@ -36,7 +36,7 @@ export class WorkspaceTags implements IWorkbenchContribution {
@IProductService private readonly productService: IProductService,
@INativeHostService private readonly nativeHostService: INativeHostService
) {
- if (this.telemetryService.telemetryLevel === TelemetryLevel.USAGE) {
+ if (this.telemetryService.telemetryLevel.value === TelemetryLevel.USAGE) {
this.report();
}
}
diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts
index 70c269e4a69..5c133362dcb 100644
--- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts
+++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts
@@ -16,6 +16,7 @@ import { Event, Emitter } from 'vs/base/common/event';
import * as Types from 'vs/base/common/types';
import { TerminateResponseCode } from 'vs/base/common/processes';
import { ValidationStatus, ValidationState } from 'vs/base/common/parsers';
+import * as glob from 'vs/base/common/glob';
import * as UUID from 'vs/base/common/uuid';
import * as Platform from 'vs/base/common/platform';
import { LRUCache, Touch } from 'vs/base/common/map';
@@ -41,7 +42,7 @@ import { IConfigurationResolverService } from 'vs/workbench/services/configurati
import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder, IWorkspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
-import { IOutputService, IOutputChannel } from 'vs/workbench/contrib/output/common/output';
+import { IOutputService, IOutputChannel } from 'vs/workbench/services/output/common/output';
import { ITerminalGroupService, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { ITerminalProfileResolverService } from 'vs/workbench/contrib/terminal/common/terminal';
@@ -69,7 +70,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/
import { IPathService } from 'vs/workbench/services/path/common/pathService';
import { toFormattedString } from 'vs/base/common/jsonFormatter';
import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService';
-import { SaveReason } from 'vs/workbench/common/editor';
+import { EditorResourceAccessor, SaveReason } from 'vs/workbench/common/editor';
import { ITextEditorSelection, TextEditorSelectionRevealType } from 'vs/platform/editor/common/editor';
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
@@ -2753,11 +2754,21 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
});
}
- private splitPerGroupType(tasks: Task[]): { none: Task[]; defaults: Task[] } {
+ /**
+ *
+ * @param tasks - The tasks which need filtering from defaults and non-defaults
+ * @param defaultType - If there are globs want globs in the default list, otherwise only tasks with true
+ * @param taskGlobsInList - This tells splitPerGroupType to filter out globbed tasks (into default), otherwise fall back to boolean
+ * @returns
+ */
+ private splitPerGroupType(tasks: Task[], taskGlobsInList: boolean = false): { none: Task[]; defaults: Task[] } {
let none: Task[] = [];
let defaults: Task[] = [];
for (let task of tasks) {
- if ((task.configurationProperties.group as TaskGroup).isDefault) {
+ // At this point (assuming taskGlobsInList is true) there are tasks with matching globs, so only put those in defaults
+ if (taskGlobsInList && typeof (task.configurationProperties.group as TaskGroup).isDefault === 'string') {
+ defaults.push(task);
+ } else if (!taskGlobsInList && (task.configurationProperties.group as TaskGroup).isDefault === true) {
defaults.push(task);
} else {
none.push(task);
@@ -2766,54 +2777,38 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
return { none, defaults };
}
- private runBuildCommand(): void {
+ private runTaskGroupCommand(taskGroup: TaskGroup, strings: {
+ fetching: string;
+ select: string;
+ notFoundConfigure: string;
+ }, configure: () => void, legacyCommand: () => void): void {
if (!this.canRunCommand()) {
return;
}
if (this.schemaVersion === JsonSchemaVersion.V0_1_0) {
- this.build();
+ legacyCommand();
return;
}
let options: IProgressOptions = {
location: ProgressLocation.Window,
- title: nls.localize('TaskService.fetchingBuildTasks', 'Fetching build tasks...')
+ title: strings.fetching
};
let promise = (async () => {
- const buildTasks = await this._findWorkspaceTasksInGroup(TaskGroup.Build, false);
- async function runSingleBuildTask(task: Task | undefined, problemMatcherOptions: ProblemMatcherRunOptions | undefined, that: AbstractTaskService) {
+ let taskGroupTasks: (Task | ConfiguringTask)[] = [];
+
+ async function runSingleTask(task: Task | undefined, problemMatcherOptions: ProblemMatcherRunOptions | undefined, that: AbstractTaskService) {
that.run(task, problemMatcherOptions, TaskRunSource.User).then(undefined, reason => {
// eat the error, it has already been surfaced to the user and we don't care about it here
});
}
- if (buildTasks.length === 1) {
- const buildTask = buildTasks[0];
- if (ConfiguringTask.is(buildTask)) {
- return this.tryResolveTask(buildTask).then(resolvedTask => {
- runSingleBuildTask(resolvedTask, undefined, this);
- });
- } else {
- runSingleBuildTask(buildTask, undefined, this);
- return;
- }
- }
-
- return this.getTasksForGroup(TaskGroup.Build).then((tasks) => {
- if (tasks.length > 0) {
- let { none, defaults } = this.splitPerGroupType(tasks);
- if (defaults.length === 1) {
- runSingleBuildTask(defaults[0], undefined, this);
- return;
- } else if (defaults.length + none.length > 0) {
- tasks = defaults.concat(none);
- }
- }
+ const chooseAndRunTask = (tasks: Task[]) => {
this.showIgnoredFoldersMessage().then(() => {
this.showQuickPick(tasks,
- nls.localize('TaskService.pickBuildTask', 'Select the build task to run'),
+ strings.select,
{
- label: nls.localize('TaskService.noBuildTask', 'No build task to run found. Configure Build Task...'),
+ label: strings.notFoundConfigure,
task: null
},
true).then((entry) => {
@@ -2822,66 +2817,104 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer
return;
}
if (task === null) {
- this.runConfigureDefaultBuildTask();
+ configure();
return;
}
- runSingleBuildTask(task, { attachProblemMatcher: true }, this);
+ runSingleTask(task, { attachProblemMatcher: true }, this);
});
});
- });
- })();
- this.progressService.withProgress(options, () => promise);
- }
+ };
- private runTestCommand(): void {
- if (!this.canRunCommand()) {
- return;
- }
- if (this.schemaVersion === JsonSchemaVersion.V0_1_0) {
- this.runTest();
- return;
- }
- let options: IProgressOptions = {
- location: ProgressLocation.Window,
- title: nls.localize('TaskService.fetchingTestTasks', 'Fetching test tasks...')
- };
- let promise = this.getTasksForGroup(TaskGroup.Test).then((tasks) => {
- if (tasks.length > 0) {
- let { none, defaults } = this.splitPerGroupType(tasks);
- if (defaults.length === 1) {
- this.run(defaults[0], undefined, TaskRunSource.User).then(undefined, reason => {
- // eat the error, it has already been surfaced to the user and we don't care about it here
- });
- return;
- } else if (defaults.length + none.length > 0) {
- tasks = defaults.concat(none);
- }
- }
- this.showIgnoredFoldersMessage().then(() => {
- this.showQuickPick(tasks,
- nls.localize('TaskService.pickTestTask', 'Select the test task to run'),
- {
- label: nls.localize('TaskService.noTestTaskTerminal', 'No test task to run found. Configure Tasks...'),
- task: null
- }, true
- ).then((entry) => {
- let task: Task | undefined | null = entry ? entry.task : undefined;
- if (task === undefined) {
- return;
+ // First check for globs before checking for the default tasks of the task group
+ const absoluteURI = EditorResourceAccessor.getOriginalUri(this.editorService.activeEditor);
+ if (absoluteURI) {
+ const workspaceFolder = this.contextService.getWorkspaceFolder(absoluteURI);
+ // fallback to absolute path of the file if it is not in a workspace or relative path cannot be found
+ const relativePath = workspaceFolder?.uri ? (resources.relativePath(workspaceFolder.uri, absoluteURI) ?? absoluteURI.path) : absoluteURI.path;
+
+ taskGroupTasks = await this._findWorkspaceTasks((task) => {
+ const taskGroup = task.configurationProperties.group;
+ if (taskGroup && typeof taskGroup !== 'string' && typeof taskGroup.isDefault === 'string') {
+ return (taskGroup._id === taskGroup._id && glob.match(taskGroup.isDefault, relativePath));
}
- if (task === null) {
- this.runConfigureTasks();
- return;
+
+ return false;
+ });
+ }
+
+ const handleMultipleTasks = (areGlobTasks: boolean) => {
+ return this.getTasksForGroup(taskGroup).then((tasks) => {
+ if (tasks.length > 0) {
+ // If we're dealing with tasks that were chosen because of a glob match,
+ // then put globs in the defaults and everything else in none
+ let { none, defaults } = this.splitPerGroupType(tasks, areGlobTasks);
+ if (defaults.length === 1) {
+ runSingleTask(defaults[0], undefined, this);
+ return;
+ } else if (defaults.length + none.length > 0) {
+ tasks = defaults.concat(none);
+ }
}
- this.run(task, undefined, TaskRunSource.User).then(undefined, reason => {
- // eat the error, it has already been surfaced to the user and we don't care about it here
- });
+
+ // At this this point there are multiple tasks.
+ chooseAndRunTask(tasks);
});
- });
- });
+ };
+
+ const resolveTaskAndRun = (taskGroupTask: Task | ConfiguringTask) => {
+ if (ConfiguringTask.is(taskGroupTask)) {
+ this.tryResolveTask(taskGroupTask).then(resolvedTask => {
+ runSingleTask(resolvedTask, undefined, this);
+ });
+ } else {
+ runSingleTask(taskGroupTask, undefined, this);
+ }
+ };
+
+ // A single default glob task was returned, just run it directly
+ if (taskGroupTasks.length === 1) {
+ return resolveTaskAndRun(taskGroupTasks[0]);
+ }
+
+ // If there's multiple globs that match we want to show the quick picker for those tasks
+ // We will need to call splitPerGroupType putting globs in defaults and the remaining tasks in none.
+ // We don't need to carry on after here
+ if (taskGroupTasks.length > 1) {
+ return handleMultipleTasks(true);
+ }
+
+ // If no globs are found or matched fallback to checking for default tasks of the task group
+ if (!taskGroupTasks.length) {
+ taskGroupTasks = await this._findWorkspaceTasksInGroup(taskGroup, false);
+ }
+
+ // A single default task was returned, just run it directly
+ if (taskGroupTasks.length === 1) {
+ return resolveTaskAndRun(taskGroupTasks[0]);
+ }
+
+ // Multiple default tasks returned, show the quickPicker
+ return handleMultipleTasks(false);
+ })();
this.progressService.withProgress(options, () => promise);
}
+ private runBuildCommand(): void {
+ return this.runTaskGroupCommand(TaskGroup.Build, {
+ fetching: nls.localize('TaskService.fetchingBuildTasks', 'Fetching build tasks...'),
+ select: nls.localize('TaskService.pickBuildTask', 'Select the build task to run'),
+ notFoundConfigure: nls.localize('TaskService.noBuildTask', 'No build task to run found. Configure Build Task...')
+ }, this.runConfigureDefaultBuildTask, this.build);
+ }
+
+ private runTestCommand(): void {
+ return this.runTaskGroupCommand(TaskGroup.Test, {
+ fetching: nls.localize('TaskService.fetchingTestTasks', 'Fetching test tasks...'),
+ select: nls.localize('TaskService.pickTestTask', 'Select the test task to run'),
+ notFoundConfigure: nls.localize('TaskService.noTestTaskTerminal', 'No test task to run found. Configure Tasks...')
+ }, this.runConfigureDefaultTestTask, this.runTest);
+ }
+
private runTerminateCommand(arg?: any): void {
if (!this.canRunCommand()) {
return;
diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts
index c0547a2ae39..992ecb05aec 100644
--- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts
+++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts
@@ -29,7 +29,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import { ITerminalProfileResolverService, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal';
import { ITerminalService, ITerminalInstance, ITerminalGroupService } from 'vs/workbench/contrib/terminal/browser/terminal';
-import { IOutputService } from 'vs/workbench/contrib/output/common/output';
+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, ShellConfiguration, RuntimeType, PanelKind,
diff --git a/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts b/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts
index c3d81ff6df0..f0ba5e9fa63 100644
--- a/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts
+++ b/src/vs/workbench/contrib/tasks/common/jsonSchema_v2.ts
@@ -192,9 +192,9 @@ const group: IJSONSchema = {
properties: {
kind: groupStrings,
isDefault: {
- type: 'boolean',
+ type: ['boolean', 'string'],
default: false,
- description: nls.localize('JsonSchema.tasks.group.isDefault', 'Defines if this task is the default task in the group.')
+ description: nls.localize('JsonSchema.tasks.group.isDefault', 'Defines if this task is the default task in the group, or a glob to match the file which should trigger this task.')
}
}
},
diff --git a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts
index 7ab00229e89..9c6f0ba9e34 100644
--- a/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts
+++ b/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts
@@ -282,7 +282,7 @@ export interface CommandProperties extends BaseCommandProperties {
export interface GroupKind {
kind?: string;
- isDefault?: boolean;
+ isDefault?: boolean | string;
}
export interface ConfigurationProperties {
@@ -1245,7 +1245,7 @@ export namespace GroupKind {
return { _id: external, isDefault: false };
} else if (Types.isString(external.kind) && Tasks.TaskGroup.is(external.kind)) {
let group: string = external.kind;
- let isDefault: boolean = !!external.isDefault;
+ let isDefault: boolean | string = Types.isUndefined(external.isDefault) ? false : external.isDefault;
return { _id: group, isDefault };
}
@@ -1260,7 +1260,7 @@ export namespace GroupKind {
}
return {
kind: group._id,
- isDefault: group.isDefault
+ isDefault: group.isDefault,
};
}
}
diff --git a/src/vs/workbench/contrib/tasks/common/tasks.ts b/src/vs/workbench/contrib/tasks/common/tasks.ts
index cb475cae0ee..1e7e42b6c81 100644
--- a/src/vs/workbench/contrib/tasks/common/tasks.ts
+++ b/src/vs/workbench/contrib/tasks/common/tasks.ts
@@ -393,7 +393,7 @@ export namespace TaskGroup {
export interface TaskGroup {
_id: string;
- isDefault?: boolean;
+ isDefault?: boolean | string;
}
export const enum TaskScope {
diff --git a/src/vs/workbench/contrib/tasks/electron-sandbox/taskService.ts b/src/vs/workbench/contrib/tasks/electron-sandbox/taskService.ts
index 436f0194e19..a17d4185861 100644
--- a/src/vs/workbench/contrib/tasks/electron-sandbox/taskService.ts
+++ b/src/vs/workbench/contrib/tasks/electron-sandbox/taskService.ts
@@ -30,7 +30,7 @@ import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IViewsService, IViewDescriptorService } from 'vs/workbench/common/views';
-import { IOutputService } from 'vs/workbench/contrib/output/common/output';
+import { IOutputService } from 'vs/workbench/services/output/common/output';
import { ITerminalGroupService, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
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 5b7471bb284..0c5d62458d5 100755
--- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh
+++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh
@@ -10,6 +10,10 @@ if [ -z "$VSCODE_SHELL_LOGIN" ]; then
else
# Imitate -l because --init-file doesn't support it:
# run the first of these files that exists
+ if [ -f /etc/profile ]; then
+ . /etc/profile
+ fi
+ # exceute the first that exists
if [ -f ~/.bash_profile ]; then
. ~/.bash_profile
elif [ -f ~/.bash_login ]; then
@@ -21,48 +25,48 @@ else
fi
if [[ "$PROMPT_COMMAND" =~ .*(' '.*\;)|(\;.*' ').* ]]; then
- echo -e "\033[1;33mShell integration cannot be activated due to complex PROMPT_COMMAND: $PROMPT_COMMAND\033[0m"
+ builtin echo -e "\033[1;33mShell integration cannot be activated due to complex PROMPT_COMMAND: $PROMPT_COMMAND\033[0m"
VSCODE_SHELL_HIDE_WELCOME=""
- return;
+ builtin return
fi
if [ -z "$VSCODE_SHELL_INTEGRATION" ]; then
- return
+ builtin return
fi
__vsc_in_command_execution="1"
__vsc_last_history_id=$(history 1 | awk '{print $1;}')
__vsc_prompt_start() {
- printf "\033]633;A\007"
+ builtin printf "\033]633;A\007"
}
__vsc_prompt_end() {
- printf "\033]633;B\007"
+ builtin printf "\033]633;B\007"
}
__vsc_update_cwd() {
- printf "\033]633;P;Cwd=%s\007" "$PWD"
+ builtin printf "\033]633;P;Cwd=%s\007" "$PWD"
}
__vsc_command_output_start() {
- printf "\033]633;C\007"
+ builtin printf "\033]633;C\007"
}
__vsc_continuation_start() {
- printf "\033]633;F\007"
+ builtin printf "\033]633;F\007"
}
__vsc_continuation_end() {
- printf "\033]633;G\007"
+ builtin printf "\033]633;G\007"
}
__vsc_command_complete() {
- local __vsc_history_id=$(history 1 | awk '{print $1;}')
+ local __vsc_history_id=$(builtin history 1 | awk '{print $1;}')
if [[ "$__vsc_history_id" == "$__vsc_last_history_id" ]]; then
- printf "\033]633;D\007"
+ builtin printf "\033]633;D\007"
else
- printf "\033]633;D;%s\007" "$__vsc_status"
+ builtin printf "\033]633;D;%s\007" "$__vsc_status"
__vsc_last_history_id=$__vsc_history_id
fi
__vsc_update_cwd
@@ -95,17 +99,26 @@ __vsc_preexec() {
__vsc_update_prompt
__vsc_prompt_cmd_original() {
+ if [[ ${IFS+set} ]]; then
+ __vsc_original_ifs="$IFS"
+ fi
__vsc_status="$?"
if [[ "$__vsc_original_prompt_command" =~ .+\;.+ ]]; then
IFS=';'
else
IFS=' '
fi
- read -ra ADDR <<<"$__vsc_original_prompt_command"
+ builtin read -ra ADDR <<<"$__vsc_original_prompt_command"
+ if [[ ${__vsc_original_ifs+set} ]]; then
+ IFS="$__vsc_original_ifs"
+ unset __vsc_original_ifs
+ else
+ unset IFS
+ fi
for ((i = 0; i < ${#ADDR[@]}; i++)); do
- eval ${ADDR[i]}
+ # unset IFS
+ builtin eval ${ADDR[i]}
done
- IFS=''
__vsc_precmd
}
@@ -130,7 +143,7 @@ fi
trap '__vsc_preexec' DEBUG
if [ -z "$VSCODE_SHELL_HIDE_WELCOME" ]; then
- echo -e "\033[1;32mShell integration activated\033[0m"
+ builtin echo -e "\033[1;32mShell integration activated\033[0m"
else
VSCODE_SHELL_HIDE_WELCOME=""
fi
diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-env.zsh b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-env.zsh
new file mode 100644
index 00000000000..f676a0308bd
--- /dev/null
+++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-env.zsh
@@ -0,0 +1,8 @@
+# ---------------------------------------------------------------------------------------------
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License. See License.txt in the project root for license information.
+# ---------------------------------------------------------------------------------------------
+
+if [[ -f ~/.zshenv ]]; then
+ . ~/.zshenv
+fi
diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-profile.zsh b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-profile.zsh
new file mode 100644
index 00000000000..734bb831e11
--- /dev/null
+++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration-profile.zsh
@@ -0,0 +1,8 @@
+# ---------------------------------------------------------------------------------------------
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License. See License.txt in the project root for license information.
+# ---------------------------------------------------------------------------------------------
+
+if [[ $options[norcs] = off && -o "login" && -f ~/.zprofile ]]; then
+ . ~/.zprofile
+fi
diff --git a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.zsh b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.zsh
index be84dc9674f..2dec005daf4 100644
--- a/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.zsh
+++ b/src/vs/workbench/contrib/terminal/browser/media/shellIntegration.zsh
@@ -2,73 +2,68 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# ---------------------------------------------------------------------------------------------
-autoload -Uz add-zsh-hook
+builtin autoload -Uz add-zsh-hook
# Now that the init script is running, unset ZDOTDIR to ensure ~/.zlogout runs as expected as well
# as prevent problems that may occur if the user's init scripts depend on ZDOTDIR not being set.
-unset ZDOTDIR
+builtin unset ZDOTDIR
# This variable allows the shell to both detect that VS Code's shell integration is enabled as well
# as disable it by unsetting the variable.
VSCODE_SHELL_INTEGRATION=1
-if [ -f ~/.zshenv ]; then
- . ~/.zshenv
-fi
-if [[ -o "login" && -f ~/.zprofile ]]; then
- . ~/.zprofile
-fi
-if [ -f ~/.zshrc ]; then
+
+if [[ $options[norcs] = off && -f ~/.zshrc ]]; then
. ~/.zshrc
fi
# Shell integration was disabled by the shell, exit without warning assuming either the shell has
# explicitly disabled shell integration as it's incompatible or it implements the protocol.
if [ -z "$VSCODE_SHELL_INTEGRATION" ]; then
- return
+ builtin return
fi
__vsc_in_command_execution="1"
__vsc_last_history_id=0
__vsc_prompt_start() {
- printf "\033]633;A\007"
+ builtin printf "\033]633;A\007"
}
__vsc_prompt_end() {
- printf "\033]633;B\007"
+ builtin printf "\033]633;B\007"
}
__vsc_update_cwd() {
- printf "\033]633;P;Cwd=%s\007" "$PWD"
+ builtin printf "\033]633;P;Cwd=%s\007" "$PWD"
}
__vsc_command_output_start() {
- printf "\033]633;C\007"
+ builtin printf "\033]633;C\007"
}
__vsc_continuation_start() {
- printf "\033]633;F\007"
+ builtin printf "\033]633;F\007"
}
__vsc_continuation_end() {
- printf "\033]633;G\007"
+ builtin printf "\033]633;G\007"
}
__vsc_right_prompt_start() {
- printf "\033]633;H\007"
+ builtin printf "\033]633;H\007"
}
__vsc_right_prompt_end() {
- printf "\033]633;I\007"
+ builtin printf "\033]633;I\007"
}
__vsc_command_complete() {
- local __vsc_history_id=$(history | tail -n1 | awk '{print $1;}')
+ builtin local __vsc_history_id=$(builtin history | tail -n1 | awk '{print $1;}')
if [[ "$__vsc_history_id" == "$__vsc_last_history_id" ]]; then
- printf "\033]633;D\007"
+ builtin printf "\033]633;D\007"
else
- printf "\033]633;D;%s\007" "$__vsc_status"
+ builtin printf "\033]633;D;%s\007" "$__vsc_status"
__vsc_last_history_id=$__vsc_history_id
fi
__vsc_update_cwd
@@ -114,7 +109,7 @@ add-zsh-hook preexec __vsc_preexec
# Show the welcome message
if [ -z "${VSCODE_SHELL_HIDE_WELCOME-}" ]; then
- echo "\033[1;32mShell integration activated\033[0m"
+ builtin echo "\033[1;32mShell integration activated\033[0m"
else
VSCODE_SHELL_HIDE_WELCOME=""
fi
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalFindWidget.ts b/src/vs/workbench/contrib/terminal/browser/terminalFindWidget.ts
index 6168afff93b..4ba93e51fb3 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalFindWidget.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalFindWidget.ts
@@ -10,6 +10,9 @@ import { FindReplaceState } from 'vs/editor/contrib/find/browser/findState';
import { ITerminalGroupService, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey';
import { TerminalLocation } from 'vs/platform/terminal/common/terminal';
+import { IThemeService } from 'vs/platform/theme/common/themeService';
+import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
+import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
export class TerminalFindWidget extends SimpleFindWidget {
protected _findInputFocused: IContextKey<boolean>;
@@ -19,11 +22,14 @@ export class TerminalFindWidget extends SimpleFindWidget {
constructor(
findState: FindReplaceState,
@IContextViewService _contextViewService: IContextViewService,
+ @IKeybindingService keybindingService: IKeybindingService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@ITerminalService private readonly _terminalService: ITerminalService,
- @ITerminalGroupService private readonly _terminalGroupService: ITerminalGroupService
+ @ITerminalGroupService private readonly _terminalGroupService: ITerminalGroupService,
+ @IThemeService private readonly _themeService: IThemeService,
+ @IConfigurationService private readonly _configurationService: IConfigurationService
) {
- super(_contextViewService, _contextKeyService, findState, { showOptionButtons: true, showResultCount: true });
+ super(findState, { showOptionButtons: true, showResultCount: true, type: 'Terminal' }, _contextViewService, _contextKeyService, keybindingService);
this._register(findState.onFindReplaceStateChange(() => {
this.show();
@@ -31,15 +37,25 @@ export class TerminalFindWidget extends SimpleFindWidget {
this._findInputFocused = TerminalContextKeys.findInputFocus.bindTo(this._contextKeyService);
this._findWidgetFocused = TerminalContextKeys.findFocus.bindTo(this._contextKeyService);
this._findWidgetVisible = TerminalContextKeys.findVisible.bindTo(_contextKeyService);
+ this._register(this._themeService.onDidColorThemeChange(() => {
+ if (this._findWidgetVisible) {
+ this.find(true, true);
+ }
+ }));
+ this._register(this._configurationService.onDidChangeConfiguration((e) => {
+ if (e.affectsConfiguration('workbench.colorCustomizations') && this._findWidgetVisible) {
+ this.find(true, true);
+ }
+ }));
}
- find(previous: boolean) {
+ find(previous: boolean, update?: boolean) {
const instance = this._terminalService.activeInstance;
if (!instance) {
return;
}
if (previous) {
- instance.xterm?.findPrevious(this.inputValue, { regex: this._getRegexValue(), wholeWord: this._getWholeWordValue(), caseSensitive: this._getCaseSensitiveValue() });
+ instance.xterm?.findPrevious(this.inputValue, { regex: this._getRegexValue(), wholeWord: this._getWholeWordValue(), caseSensitive: this._getCaseSensitiveValue(), incremental: update });
} else {
instance.xterm?.findNext(this.inputValue, { regex: this._getRegexValue(), wholeWord: this._getWholeWordValue(), caseSensitive: this._getCaseSensitiveValue() });
}
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts
index 18be1c0ae4a..9414adfc86a 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts
@@ -460,6 +460,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
// Re-establish the title after reconnect
if (this.shellLaunchConfig.attachPersistentProcess) {
this.refreshTabLabels(this.shellLaunchConfig.attachPersistentProcess.title, this.shellLaunchConfig.attachPersistentProcess.titleSource);
+ this.setShellType(this.shellType);
}
if (this._fixedCols) {
@@ -1096,21 +1097,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
setTimeout(() => this._refreshSelectionContextKey(), 0);
}));
- this._register(dom.addDisposableListener(xterm.raw.textarea, 'focus', () => {
- this._terminalFocusContextKey.set(true);
- if (this.shellType) {
- this._terminalShellTypeContextKey.set(this.shellType.toString());
- } else {
- this._terminalShellTypeContextKey.reset();
- }
- this._onDidFocus.fire(this);
- }));
-
- this._register(dom.addDisposableListener(xterm.raw.textarea, 'blur', () => {
- this._terminalFocusContextKey.reset();
- this._onDidBlur.fire(this);
- this._refreshSelectionContextKey();
- }));
+ this._register(dom.addDisposableListener(xterm.raw.textarea, 'focus', () => this._setFocus(true)));
+ this._register(dom.addDisposableListener(xterm.raw.textarea, 'blur', () => this._setFocus(false)));
+ this._register(dom.addDisposableListener(xterm.raw.textarea, 'focusout', () => this._setFocus(false)));
this._initDragAndDrop(container);
@@ -1136,6 +1125,17 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
}
}
+ private _setFocus(focused?: boolean): void {
+ if (focused) {
+ this._terminalFocusContextKey.set(true);
+ this._onDidFocus.fire(this);
+ } else {
+ this._terminalFocusContextKey.reset();
+ this._onDidBlur.fire(this);
+ this._refreshSelectionContextKey();
+ }
+ }
+
private _initDragAndDrop(container: HTMLElement) {
this._dndObserver?.dispose();
const dndController = this._instantiationService.createInstance(TerminalInstanceDragAndDropController, container);
@@ -1157,6 +1157,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
if (asHtml) {
const textAsHtml = await xterm.getSelectionAsHtml(command);
function listener(e: any) {
+ if (!e.clipboardData.types.includes('text/plain')) {
+ e.clipboardData.setData('text/plain', command?.getOutput() ?? '');
+ }
e.clipboardData.setData('text/html', textAsHtml);
e.preventDefault();
}
@@ -1882,6 +1885,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
setShellType(shellType: TerminalShellType) {
this._shellType = shellType;
+ if (shellType) {
+ this._terminalShellTypeContextKey.set(shellType?.toString());
+ }
}
private _setAriaLabel(xterm: XTermTerminal | undefined, terminalId: number, title: string | undefined): void {
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts
index 5e749dcac5b..6c09333437c 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts
@@ -388,7 +388,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
baseEnv = await this._terminalProfileResolverService.getEnvironment(this.remoteAuthority);
}
- const env = terminalEnvironment.createTerminalEnvironment(shellLaunchConfig, envFromConfigValue, variableResolver, this._productService.version, this._configHelper.config.detectLocale, baseEnv);
+ const env = await terminalEnvironment.createTerminalEnvironment(shellLaunchConfig, envFromConfigValue, variableResolver, this._productService.version, this._configHelper.config.detectLocale, baseEnv);
if (!this._isDisposed && !shellLaunchConfig.strictEnv && !shellLaunchConfig.hideFromUser) {
this._extEnvironmentVariableCollection = this._environmentVariableService.mergedCollection;
this._register(this._environmentVariableService.onDidChangeCollections(newCollection => this._onEnvironmentVariableCollectionChange(newCollection)));
@@ -398,7 +398,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
// info widget. While technically these could differ due to the slight change of a race
// condition, the chance is minimal plus the impact on the user is also not that great
// if it happens - it's not worth adding plumbing to sync back the resolved collection.
- this._extEnvironmentVariableCollection.applyToProcessEnvironment(env, variableResolver);
+ await this._extEnvironmentVariableCollection.applyToProcessEnvironment(env, variableResolver);
if (this._extEnvironmentVariableCollection.map.size > 0) {
this.environmentVariableInfo = new EnvironmentVariableInfoChangesActive(this._extEnvironmentVariableCollection);
this._onEnvironmentVariableInfoChange.fire(this.environmentVariableInfo);
@@ -423,7 +423,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce
const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(Schemas.file);
- const initialCwd = terminalEnvironment.getCwd(
+ const initialCwd = await terminalEnvironment.getCwd(
shellLaunchConfig,
userHome,
variableResolver,
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts b/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts
index 5885b71b3a3..0849f6676b2 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalProfileResolverService.ts
@@ -355,25 +355,23 @@ export abstract class BaseTerminalProfileResolverService implements ITerminalPro
const env = await this._context.getEnvironment(options.remoteAuthority);
const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(options.remoteAuthority ? Schemas.vscodeRemote : Schemas.file);
const lastActiveWorkspace = activeWorkspaceRootUri ? withNullAsUndefined(this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri)) : undefined;
- profile.path = this._resolveVariables(profile.path, env, lastActiveWorkspace);
+ profile.path = await this._resolveVariables(profile.path, env, lastActiveWorkspace);
// Resolve args variables
if (profile.args) {
if (typeof profile.args === 'string') {
- profile.args = this._resolveVariables(profile.args, env, lastActiveWorkspace);
+ profile.args = await this._resolveVariables(profile.args, env, lastActiveWorkspace);
} else {
- for (let i = 0; i < profile.args.length; i++) {
- profile.args[i] = this._resolveVariables(profile.args[i], env, lastActiveWorkspace);
- }
+ profile.args = await Promise.all(profile.args.map(arg => this._resolveVariables(arg, env, lastActiveWorkspace)));
}
}
return profile;
}
- private _resolveVariables(value: string, env: IProcessEnvironment, lastActiveWorkspace: IWorkspaceFolder | undefined) {
+ private async _resolveVariables(value: string, env: IProcessEnvironment, lastActiveWorkspace: IWorkspaceFolder | undefined) {
try {
- value = this._configurationResolverService.resolveWithEnvironment(env, lastActiveWorkspace, value);
+ value = await this._configurationResolverService.resolveWithEnvironment(env, lastActiveWorkspace, value);
} catch (e) {
this._logService.error(`Could not resolve shell`, e);
}
diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts
index 8402e0e5362..62d52e82cc9 100644
--- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts
+++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts
@@ -52,6 +52,7 @@ export class TerminalService implements ITerminalService {
private _hostActiveTerminals: Map<ITerminalInstanceHost, ITerminalInstance | undefined> = new Map();
private _terminalEditorActive: IContextKey<boolean>;
+ private readonly _terminalShellTypeContextKey: IContextKey<string>;
private _escapeSequenceLoggingEnabled: boolean = false;
@@ -187,9 +188,15 @@ export class TerminalService implements ITerminalService {
if (!instance && !this._isShuttingDown) {
this._terminalGroupService.hidePanel();
}
+ if (instance?.shellType) {
+ this._terminalShellTypeContextKey.set(instance.shellType.toString());
+ } else if (!instance) {
+ this._terminalShellTypeContextKey.reset();
+ }
});
this._handleInstanceContextKeys();
+ this._terminalShellTypeContextKey = TerminalContextKeys.shellType.bindTo(this._contextKeyService);
this._processSupportContextKey = TerminalContextKeys.processSupported.bindTo(this._contextKeyService);
this._processSupportContextKey.set(!isWeb || this._remoteAgentService.getConnection() !== null);
this._terminalHasBeenCreated = TerminalContextKeys.terminalHasBeenCreated.bindTo(this._contextKeyService);
diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/commandNavigationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/commandNavigationAddon.ts
index c7b911c633d..60d82f8436d 100644
--- a/src/vs/workbench/contrib/terminal/browser/xterm/commandNavigationAddon.ts
+++ b/src/vs/workbench/contrib/terminal/browser/xterm/commandNavigationAddon.ts
@@ -34,6 +34,9 @@ export class CommandNavigationAddon extends Disposable implements ICommandTracke
activate(terminal: Terminal): void {
this._terminal = terminal;
+ 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 40b75ff1352..3f07b624fa7 100644
--- a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts
+++ b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts
@@ -73,6 +73,8 @@ export class DecorationAddon extends Disposable implements ITerminalAddon {
this._refreshStyles();
} else if (e.affectsConfiguration(TerminalSettingId.FontSize) || e.affectsConfiguration(TerminalSettingId.LineHeight)) {
this.refreshLayouts();
+ } else if (e.affectsConfiguration('workbench.colorCustomizations')) {
+ this._refreshStyles(true);
}
});
this._themeService.onDidColorThemeChange(() => this._refreshStyles(true));
@@ -94,10 +96,10 @@ export class DecorationAddon extends Disposable implements ITerminalAddon {
} else {
color = '';
}
- if (decoration.decoration.overviewRulerOptions) {
- decoration.decoration.overviewRulerOptions.color = color;
- } else {
- decoration.decoration.overviewRulerOptions = { color };
+ if (decoration.decoration.options?.overviewRulerOptions) {
+ decoration.decoration.options.overviewRulerOptions.color = color;
+ } else if (decoration.decoration.options) {
+ decoration.decoration.options.overviewRulerOptions = { color };
}
}
}
diff --git a/src/vs/workbench/contrib/terminal/common/environmentVariable.ts b/src/vs/workbench/contrib/terminal/common/environmentVariable.ts
index c307e138dd4..1f105bfaa94 100644
--- a/src/vs/workbench/contrib/terminal/common/environmentVariable.ts
+++ b/src/vs/workbench/contrib/terminal/common/environmentVariable.ts
@@ -7,6 +7,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
import { Event } from 'vs/base/common/event';
import { IProcessEnvironment } from 'vs/base/common/platform';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
+import { VariableResolver } from 'vs/workbench/contrib/terminal/common/terminalEnvironment';
export const IEnvironmentVariableService = createDecorator<IEnvironmentVariableService>('environmentVariableService');
@@ -51,7 +52,7 @@ export interface IMergedEnvironmentVariableCollection {
* @param variableResolver An optional function to use to resolve variables within the
* environment values.
*/
- applyToProcessEnvironment(env: IProcessEnvironment, variableResolver?: (str: string) => string): void;
+ applyToProcessEnvironment(env: IProcessEnvironment, variableResolver?: VariableResolver): Promise<void>;
/**
* Generates a diff of this connection against another. Returns undefined if the collections are
diff --git a/src/vs/workbench/contrib/terminal/common/environmentVariableCollection.ts b/src/vs/workbench/contrib/terminal/common/environmentVariableCollection.ts
index a64ef609d29..e67f607c506 100644
--- a/src/vs/workbench/contrib/terminal/common/environmentVariableCollection.ts
+++ b/src/vs/workbench/contrib/terminal/common/environmentVariableCollection.ts
@@ -5,6 +5,7 @@
import { IProcessEnvironment, isWindows } from 'vs/base/common/platform';
import { EnvironmentVariableMutatorType, IEnvironmentVariableCollection, IExtensionOwnedEnvironmentVariableMutator, IMergedEnvironmentVariableCollection, IMergedEnvironmentVariableCollectionDiff } from 'vs/workbench/contrib/terminal/common/environmentVariable';
+import { VariableResolver } from 'vs/workbench/contrib/terminal/common/terminalEnvironment';
export class MergedEnvironmentVariableCollection implements IMergedEnvironmentVariableCollection {
readonly map: Map<string, IExtensionOwnedEnvironmentVariableMutator[]> = new Map();
@@ -41,16 +42,16 @@ export class MergedEnvironmentVariableCollection implements IMergedEnvironmentVa
});
}
- applyToProcessEnvironment(env: IProcessEnvironment, variableResolver?: (str: string) => string): void {
+ async applyToProcessEnvironment(env: IProcessEnvironment, variableResolver?: VariableResolver): Promise<void> {
let lowerToActualVariableNames: { [lowerKey: string]: string | undefined } | undefined;
if (isWindows) {
lowerToActualVariableNames = {};
Object.keys(env).forEach(e => lowerToActualVariableNames![e.toLowerCase()] = e);
}
- this.map.forEach((mutators, variable) => {
+ for (const [variable, mutators] of this.map) {
const actualVariable = isWindows ? lowerToActualVariableNames![variable.toLowerCase()] || variable : variable;
- mutators.forEach(mutator => {
- const value = variableResolver ? variableResolver(mutator.value) : mutator.value;
+ for (const mutator of mutators) {
+ const value = variableResolver ? await variableResolver(mutator.value) : mutator.value;
switch (mutator.type) {
case EnvironmentVariableMutatorType.Append:
env[actualVariable] = (env[actualVariable] || '') + value;
@@ -62,8 +63,8 @@ export class MergedEnvironmentVariableCollection implements IMergedEnvironmentVa
env[actualVariable] = value;
break;
}
- });
- });
+ }
+ }
}
diff(other: IMergedEnvironmentVariableCollection): IMergedEnvironmentVariableCollectionDiff | undefined {
diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts
index 4020b3e6fa0..3ddae516bb0 100644
--- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts
+++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts
@@ -186,7 +186,7 @@ const terminalConfiguration: IConfigurationNode = {
default: DEFAULT_LINE_HEIGHT
},
[TerminalSettingId.MinimumContrastRatio]: {
- markdownDescription: localize('terminal.integrated.minimumContrastRatio', "When set the foreground color of each cell will change to try meet the contrast ratio specified. Example values:\n\n- 1: Do nothing and use the standard theme colors.\n- 4.5: [WCAG AA compliance (minimum)](https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-contrast.html) (default).\n- 7: [WCAG AAA compliance (enhanced)](https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast7.html).\n- 21: White on black or black on white."),
+ markdownDescription: localize('terminal.integrated.minimumContrastRatio', "When set, the foreground color of each cell will change to try meet the contrast ratio specified. Note that this will not apply to `powerline` characters per #146406. Example values:\n\n- 1: Do nothing and use the standard theme colors.\n- 4.5: [WCAG AA compliance (minimum)](https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-contrast.html) (default).\n- 7: [WCAG AAA compliance (enhanced)](https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast7.html).\n- 21: White on black or black on white."),
type: 'number',
default: 4.5
},
diff --git a/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts
index b1922c5cd52..80041aeb869 100644
--- a/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts
+++ b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts
@@ -78,17 +78,17 @@ function mergeNonNullKeys(env: IProcessEnvironment, other: ITerminalEnvironment
}
}
-function resolveConfigurationVariables(variableResolver: VariableResolver, env: ITerminalEnvironment): ITerminalEnvironment {
- Object.keys(env).forEach((key) => {
- const value = env[key];
+async function resolveConfigurationVariables(variableResolver: VariableResolver, env: ITerminalEnvironment): Promise<ITerminalEnvironment> {
+ await Promise.all(Object.entries(env).map(async ([key, value]) => {
if (typeof value === 'string') {
try {
- env[key] = variableResolver(value);
+ env[key] = await variableResolver(value);
} catch (e) {
env[key] = value;
}
}
- });
+ }));
+
return env;
}
@@ -179,17 +179,17 @@ export function getLangEnvVariable(locale?: string): string {
return parts.join('_') + '.UTF-8';
}
-export function getCwd(
+export async function getCwd(
shell: IShellLaunchConfig,
userHome: string | undefined,
variableResolver: VariableResolver | undefined,
root: Uri | undefined,
customCwd: string | undefined,
logService?: ILogService
-): string {
+): Promise<string> {
if (shell.cwd) {
const unresolved = (typeof shell.cwd === 'object') ? shell.cwd.fsPath : shell.cwd;
- const resolved = _resolveCwd(unresolved, variableResolver);
+ const resolved = await _resolveCwd(unresolved, variableResolver);
return _sanitizeCwd(resolved || unresolved);
}
@@ -197,7 +197,7 @@ export function getCwd(
if (!shell.ignoreConfigurationCwd && customCwd) {
if (variableResolver) {
- customCwd = _resolveCwd(customCwd, variableResolver, logService);
+ customCwd = await _resolveCwd(customCwd, variableResolver, logService);
}
if (customCwd) {
if (path.isAbsolute(customCwd)) {
@@ -216,10 +216,10 @@ export function getCwd(
return _sanitizeCwd(cwd);
}
-function _resolveCwd(cwd: string, variableResolver: VariableResolver | undefined, logService?: ILogService): string | undefined {
+async function _resolveCwd(cwd: string, variableResolver: VariableResolver | undefined, logService?: ILogService): Promise<string | undefined> {
if (variableResolver) {
try {
- return variableResolver(cwd);
+ return await variableResolver(cwd);
} catch (e) {
logService?.error('Could not resolve terminal cwd', e);
return undefined;
@@ -251,7 +251,7 @@ export type TerminalShellArgsSetting = (
| TerminalSettingId.ShellArgsLinux
);
-export type VariableResolver = (str: string) => string;
+export type VariableResolver = (str: string) => Promise<string>;
export function createVariableResolver(lastActiveWorkspace: IWorkspaceFolder | undefined, env: IProcessEnvironment, configurationResolverService: IConfigurationResolverService | undefined): VariableResolver | undefined {
if (!configurationResolverService) {
@@ -263,7 +263,7 @@ export function createVariableResolver(lastActiveWorkspace: IWorkspaceFolder | u
/**
* @deprecated Use ITerminalProfileResolverService
*/
-export function getDefaultShell(
+export async function getDefaultShell(
fetchSetting: (key: TerminalShellSetting) => string | undefined,
defaultShell: string,
isWoW64: boolean,
@@ -272,7 +272,7 @@ export function getDefaultShell(
logService: ILogService,
useAutomationShell: boolean,
platformOverride: Platform = platform
-): string {
+): Promise<string> {
let maybeExecutable: string | undefined;
if (useAutomationShell) {
// If automationShell is specified, this should override the normal setting
@@ -300,7 +300,7 @@ export function getDefaultShell(
if (variableResolver) {
try {
- executable = variableResolver(executable);
+ executable = await variableResolver(executable);
} catch (e) {
logService.error(`Could not resolve shell`, e);
}
@@ -312,13 +312,13 @@ export function getDefaultShell(
/**
* @deprecated Use ITerminalProfileResolverService
*/
-export function getDefaultShellArgs(
+export async function getDefaultShellArgs(
fetchSetting: (key: TerminalShellSetting | TerminalShellArgsSetting) => string | string[] | undefined,
useAutomationShell: boolean,
variableResolver: VariableResolver | undefined,
logService: ILogService,
platformOverride: Platform = platform,
-): string | string[] {
+): Promise<string | string[]> {
if (useAutomationShell) {
if (!!getShellSetting(fetchSetting, 'automationShell', platformOverride)) {
return [];
@@ -331,13 +331,13 @@ export function getDefaultShellArgs(
return [];
}
if (typeof args === 'string' && platformOverride === Platform.Windows) {
- return variableResolver ? variableResolver(args) : args;
+ return variableResolver ? await variableResolver(args) : args;
}
if (variableResolver) {
const resolvedArgs: string[] = [];
for (const arg of args) {
try {
- resolvedArgs.push(variableResolver(arg));
+ resolvedArgs.push(await variableResolver(arg));
} catch (e) {
logService.error(`Could not resolve ${TerminalSettingPrefix.ShellArgs}${platformKey}`, e);
resolvedArgs.push(arg);
@@ -357,14 +357,14 @@ function getShellSetting(
return fetchSetting(<TerminalShellSetting>`terminal.integrated.${type}.${platformKey}`);
}
-export function createTerminalEnvironment(
+export async function createTerminalEnvironment(
shellLaunchConfig: IShellLaunchConfig,
envFromConfig: ITerminalEnvironment | undefined,
variableResolver: VariableResolver | undefined,
version: string | undefined,
detectLocale: 'auto' | 'off' | 'on',
baseEnv: IProcessEnvironment
-): IProcessEnvironment {
+): Promise<IProcessEnvironment> {
// Create a terminal environment based on settings, launch config and permissions
const env: IProcessEnvironment = {};
if (shellLaunchConfig.strictEnv) {
@@ -379,10 +379,10 @@ export function createTerminalEnvironment(
// Resolve env vars from config and shell
if (variableResolver) {
if (allowedEnvFromConfig) {
- resolveConfigurationVariables(variableResolver, allowedEnvFromConfig);
+ await resolveConfigurationVariables(variableResolver, allowedEnvFromConfig);
}
if (shellLaunchConfig.env) {
- resolveConfigurationVariables(variableResolver, shellLaunchConfig.env);
+ await resolveConfigurationVariables(variableResolver, shellLaunchConfig.env);
}
}
diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts
index c38b20757e8..b2f01c3e853 100644
--- a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts
+++ b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalBackend.ts
@@ -250,9 +250,9 @@ class LocalTerminalBackend extends BaseTerminalBackend implements ITerminalBacke
const platformKey = isWindows ? 'windows' : (isMacintosh ? 'osx' : 'linux');
const envFromConfigValue = this._configurationService.getValue<ITerminalEnvironment | undefined>(`terminal.integrated.env.${platformKey}`);
const baseEnv = await (shellLaunchConfig.useShellEnvironment ? this.getShellEnvironment() : this.getEnvironment());
- const env = terminalEnvironment.createTerminalEnvironment(shellLaunchConfig, envFromConfigValue, variableResolver, this._productService.version, this._configurationService.getValue(TerminalSettingId.DetectLocale), baseEnv);
+ const env = await terminalEnvironment.createTerminalEnvironment(shellLaunchConfig, envFromConfigValue, variableResolver, this._productService.version, this._configurationService.getValue(TerminalSettingId.DetectLocale), baseEnv);
if (!shellLaunchConfig.strictEnv && !shellLaunchConfig.hideFromUser) {
- this._environmentVariableService.mergedCollection.applyToProcessEnvironment(env, variableResolver);
+ await this._environmentVariableService.mergedCollection.applyToProcessEnvironment(env, variableResolver);
}
return env;
}
diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalService.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalService.test.ts
index 9ab2dc7f95f..10ffee5b9d0 100644
--- a/src/vs/workbench/contrib/terminal/test/browser/terminalService.test.ts
+++ b/src/vs/workbench/contrib/terminal/test/browser/terminalService.test.ts
@@ -12,7 +12,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
-import { TestEditorService, TestLifecycleService, TestTerminalEditorService, TestTerminalGroupService, TestTerminalInstanceService, TestTerminalProfileService } from 'vs/workbench/test/browser/workbenchTestServices';
+import { TestEditorService, TestLifecycleService, TestRemoteAgentService, TestTerminalEditorService, TestTerminalGroupService, TestTerminalInstanceService, TestTerminalProfileService } from 'vs/workbench/test/browser/workbenchTestServices';
import { ITerminalEditorService, ITerminalGroupService, ITerminalInstance, ITerminalInstanceService, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { IThemeService } from 'vs/platform/theme/common/themeService';
@@ -22,7 +22,6 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { TestDialogService } from 'vs/platform/dialogs/test/common/testDialogService';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
-import { TestRemoteAgentService } from 'vs/workbench/services/remote/test/common/testServices';
suite('Workbench - TerminalService', () => {
let instantiationService: TestInstantiationService;
diff --git a/src/vs/workbench/contrib/terminal/test/common/environmentVariableCollection.test.ts b/src/vs/workbench/contrib/terminal/test/common/environmentVariableCollection.test.ts
index 7b0b6594d4a..40c086a7dcc 100644
--- a/src/vs/workbench/contrib/terminal/test/common/environmentVariableCollection.test.ts
+++ b/src/vs/workbench/contrib/terminal/test/common/environmentVariableCollection.test.ts
@@ -78,7 +78,7 @@ suite('EnvironmentVariable - MergedEnvironmentVariableCollection', () => {
});
suite('applyToProcessEnvironment', () => {
- test('should apply the collection to an environment', () => {
+ test('should apply the collection to an environment', async () => {
const merged = new MergedEnvironmentVariableCollection(new Map([
['ext', {
map: deserializeEnvironmentVariableCollection([
@@ -93,7 +93,7 @@ suite('EnvironmentVariable - MergedEnvironmentVariableCollection', () => {
B: 'bar',
C: 'baz'
};
- merged.applyToProcessEnvironment(env);
+ await merged.applyToProcessEnvironment(env);
deepStrictEqual(env, {
A: 'a',
B: 'barb',
@@ -101,7 +101,7 @@ suite('EnvironmentVariable - MergedEnvironmentVariableCollection', () => {
});
});
- test('should apply the collection to environment entries with no values', () => {
+ test('should apply the collection to environment entries with no values', async () => {
const merged = new MergedEnvironmentVariableCollection(new Map([
['ext', {
map: deserializeEnvironmentVariableCollection([
@@ -112,7 +112,7 @@ suite('EnvironmentVariable - MergedEnvironmentVariableCollection', () => {
}]
]));
const env: IProcessEnvironment = {};
- merged.applyToProcessEnvironment(env);
+ await merged.applyToProcessEnvironment(env);
deepStrictEqual(env, {
A: 'a',
B: 'b',
@@ -120,7 +120,7 @@ suite('EnvironmentVariable - MergedEnvironmentVariableCollection', () => {
});
});
- test('should apply to variable case insensitively on Windows only', () => {
+ test('should apply to variable case insensitively on Windows only', async () => {
const merged = new MergedEnvironmentVariableCollection(new Map([
['ext', {
map: deserializeEnvironmentVariableCollection([
@@ -135,7 +135,7 @@ suite('EnvironmentVariable - MergedEnvironmentVariableCollection', () => {
B: 'B',
C: 'C'
};
- merged.applyToProcessEnvironment(env);
+ await merged.applyToProcessEnvironment(env);
if (isWindows) {
deepStrictEqual(env, {
A: 'a',
diff --git a/src/vs/workbench/contrib/terminal/test/common/environmentVariableService.test.ts b/src/vs/workbench/contrib/terminal/test/common/environmentVariableService.test.ts
index ed3269a6b83..dd391ea487d 100644
--- a/src/vs/workbench/contrib/terminal/test/common/environmentVariableService.test.ts
+++ b/src/vs/workbench/contrib/terminal/test/common/environmentVariableService.test.ts
@@ -87,7 +87,7 @@ suite('EnvironmentVariable - EnvironmentVariableService', () => {
]);
});
- test('should correctly apply the environment values from multiple extension contributions in the correct order', () => {
+ test('should correctly apply the environment values from multiple extension contributions in the correct order', async () => {
const collection1 = new Map<string, IEnvironmentVariableMutator>();
const collection2 = new Map<string, IEnvironmentVariableMutator>();
const collection3 = new Map<string, IEnvironmentVariableMutator>();
@@ -109,7 +109,7 @@ suite('EnvironmentVariable - EnvironmentVariableService', () => {
// Verify the entries get applied to the environment as expected
const env: IProcessEnvironment = { A: 'foo' };
- environmentVariableService.mergedCollection.applyToProcessEnvironment(env);
+ await environmentVariableService.mergedCollection.applyToProcessEnvironment(env);
deepStrictEqual(env, { A: 'a2:a3:a1' });
});
});
diff --git a/src/vs/workbench/contrib/terminal/test/common/terminalEnvironment.test.ts b/src/vs/workbench/contrib/terminal/test/common/terminalEnvironment.test.ts
index ca148531c12..b3918029cd0 100644
--- a/src/vs/workbench/contrib/terminal/test/common/terminalEnvironment.test.ts
+++ b/src/vs/workbench/contrib/terminal/test/common/terminalEnvironment.test.ts
@@ -180,66 +180,66 @@ suite('Workbench - TerminalEnvironment', () => {
strictEqual(Uri.file(a).fsPath, Uri.file(b).fsPath);
}
- test('should default to userHome for an empty workspace', () => {
- assertPathsMatch(getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, undefined, undefined), '/userHome/');
+ test('should default to userHome for an empty workspace', async () => {
+ assertPathsMatch(await getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, undefined, undefined), '/userHome/');
});
- test('should use to the workspace if it exists', () => {
- assertPathsMatch(getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, Uri.file('/foo'), undefined), '/foo');
+ test('should use to the workspace if it exists', async () => {
+ assertPathsMatch(await getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, Uri.file('/foo'), undefined), '/foo');
});
- test('should use an absolute custom cwd as is', () => {
- assertPathsMatch(getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, undefined, '/foo'), '/foo');
+ test('should use an absolute custom cwd as is', async () => {
+ assertPathsMatch(await getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, undefined, '/foo'), '/foo');
});
- test('should normalize a relative custom cwd against the workspace path', () => {
- assertPathsMatch(getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, Uri.file('/bar'), 'foo'), '/bar/foo');
- assertPathsMatch(getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, Uri.file('/bar'), './foo'), '/bar/foo');
- assertPathsMatch(getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, Uri.file('/bar'), '../foo'), '/foo');
+ test('should normalize a relative custom cwd against the workspace path', async () => {
+ assertPathsMatch(await getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, Uri.file('/bar'), 'foo'), '/bar/foo');
+ assertPathsMatch(await getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, Uri.file('/bar'), './foo'), '/bar/foo');
+ assertPathsMatch(await getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, Uri.file('/bar'), '../foo'), '/foo');
});
- test('should fall back for relative a custom cwd that doesn\'t have a workspace', () => {
- assertPathsMatch(getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, undefined, 'foo'), '/userHome/');
- assertPathsMatch(getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, undefined, './foo'), '/userHome/');
- assertPathsMatch(getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, undefined, '../foo'), '/userHome/');
+ test('should fall back for relative a custom cwd that doesn\'t have a workspace', async () => {
+ assertPathsMatch(await getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, undefined, 'foo'), '/userHome/');
+ assertPathsMatch(await getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, undefined, './foo'), '/userHome/');
+ assertPathsMatch(await getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, undefined, '../foo'), '/userHome/');
});
- test('should ignore custom cwd when told to ignore', () => {
- assertPathsMatch(getCwd({ executable: undefined, args: [], ignoreConfigurationCwd: true }, '/userHome/', undefined, Uri.file('/bar'), '/foo'), '/bar');
+ test('should ignore custom cwd when told to ignore', async () => {
+ assertPathsMatch(await getCwd({ executable: undefined, args: [], ignoreConfigurationCwd: true }, '/userHome/', undefined, Uri.file('/bar'), '/foo'), '/bar');
});
});
suite('getDefaultShell', () => {
- test('should change Sysnative to System32 in non-WoW64 systems', () => {
- const shell = getDefaultShell(key => {
+ test('should change Sysnative to System32 in non-WoW64 systems', async () => {
+ const shell = await getDefaultShell(key => {
return ({ 'terminal.integrated.shell.windows': 'C:\\Windows\\Sysnative\\cmd.exe' } as any)[key];
}, 'DEFAULT', false, 'C:\\Windows', undefined, {} as any, false, Platform.Windows);
strictEqual(shell, 'C:\\Windows\\System32\\cmd.exe');
});
- test('should not change Sysnative to System32 in WoW64 systems', () => {
- const shell = getDefaultShell(key => {
+ test('should not change Sysnative to System32 in WoW64 systems', async () => {
+ const shell = await getDefaultShell(key => {
return ({ 'terminal.integrated.shell.windows': 'C:\\Windows\\Sysnative\\cmd.exe' } as any)[key];
}, 'DEFAULT', true, 'C:\\Windows', undefined, {} as any, false, Platform.Windows);
strictEqual(shell, 'C:\\Windows\\Sysnative\\cmd.exe');
});
- test('should use automationShell when specified', () => {
- const shell1 = getDefaultShell(key => {
+ test('should use automationShell when specified', async () => {
+ const shell1 = await getDefaultShell(key => {
return ({
'terminal.integrated.shell.windows': 'shell',
'terminal.integrated.automationShell.windows': undefined
} as any)[key];
}, 'DEFAULT', false, 'C:\\Windows', undefined, {} as any, false, Platform.Windows);
strictEqual(shell1, 'shell', 'automationShell was false');
- const shell2 = getDefaultShell(key => {
+ const shell2 = await getDefaultShell(key => {
return ({
'terminal.integrated.shell.windows': 'shell',
'terminal.integrated.automationShell.windows': undefined
} as any)[key];
}, 'DEFAULT', false, 'C:\\Windows', undefined, {} as any, true, Platform.Windows);
strictEqual(shell2, 'shell', 'automationShell was true');
- const shell3 = getDefaultShell(key => {
+ const shell3 = await getDefaultShell(key => {
return ({
'terminal.integrated.shell.windows': 'shell',
'terminal.integrated.automationShell.windows': 'automationShell'
diff --git a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts
index 9382f47bc67..db687ec08b6 100644
--- a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts
+++ b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts
@@ -111,6 +111,21 @@ export class UnhideTestAction extends Action2 {
}
}
+export class UnhideAllTestsAction extends Action2 {
+ constructor() {
+ super({
+ id: TestCommandId.UnhideAllTestsAction,
+ title: localize('unhideAllTests', 'Unhide All Tests'),
+ });
+ }
+
+ public override run(accessor: ServicesAccessor) {
+ const service = accessor.get(ITestService);
+ service.excluded.clear();
+ return Promise.resolve();
+ }
+}
+
const testItemInlineAndInContext = (order: ActionOrder, when?: ContextKeyExpression) => [
{
id: MenuId.TestItem,
@@ -1190,4 +1205,5 @@ export const allTestActions = [
TestingViewAsTreeAction,
ToggleInlineTestOutput,
UnhideTestAction,
+ UnhideAllTestsAction,
];
diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts
index c32661787e3..4d1af363aa7 100644
--- a/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts
+++ b/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts
@@ -65,6 +65,11 @@ 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]);
+ }
+
const input = this.input = this._register(this.instantiationService.createInstance(ContextScopedSuggestEnabledInputWithHistory, {
id: 'testing.explorer.filter',
ariaLabel: localize('testExplorerFilterLabel', "Filter text for tests in the explorer"),
@@ -89,7 +94,7 @@ export class TestingExplorerFilter extends BaseActionViewItem {
value: this.state.text.value,
placeholderText: localize('testExplorerFilter', "Filter (e.g. text, !exclude, @tag)"),
},
- history: this.history.get([])
+ history
}));
this._register(attachSuggestEnabledInputBoxStyler(input, this.themeService));
diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts
index 71b7930768c..979dc41d729 100644
--- a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts
+++ b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts
@@ -25,7 +25,7 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { Lazy } from 'vs/base/common/lazy';
import { Disposable, DisposableStore, IDisposable, IReference, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { clamp } from 'vs/base/common/numbers';
-import { count } from 'vs/base/common/strings';
+import { count, removeAnsiEscapeCodes } from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
import { ICodeEditor, IDiffEditorConstructionOptions, isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { EditorAction2 } from 'vs/editor/browser/editorExtensions';
@@ -1162,7 +1162,7 @@ class TestMessageElement implements ITreeElement {
public readonly taskIndex: number,
public readonly messageIndex: number,
) {
- const { message, location } = test.tasks[taskIndex].messages[messageIndex];
+ const { type, message, location } = test.tasks[taskIndex].messages[messageIndex];
this.location = location;
this.uri = this.context = buildTestUri({
@@ -1175,7 +1175,9 @@ class TestMessageElement implements ITreeElement {
this.id = this.uri.toString();
- const asPlaintext = renderStringAsPlaintext(message);
+ const asPlaintext = type === TestMessageType.Output
+ ? removeAnsiEscapeCodes(message)
+ : renderStringAsPlaintext(message);
const lines = count(asPlaintext.trimRight(), '\n');
this.label = firstLine(asPlaintext);
if (lines > 0) {
diff --git a/src/vs/workbench/contrib/testing/browser/theme.ts b/src/vs/workbench/contrib/testing/browser/theme.ts
index dcd6e2bb567..2a8d95b8665 100644
--- a/src/vs/workbench/contrib/testing/browser/theme.ts
+++ b/src/vs/workbench/contrib/testing/browser/theme.ts
@@ -91,7 +91,7 @@ export const testMessageSeverityColors: {
localize('testing.message.error.marginBackground', 'Margin color beside error messages shown inline in the editor.')
),
},
- [TestMessageType.Info]: {
+ [TestMessageType.Output]: {
decorationForeground: registerColor(
'testing.message.info.decorationForeground',
{ dark: transparent(editorForeground, 0.5), light: transparent(editorForeground, 0.5), hcDark: transparent(editorForeground, 0.5), hcLight: transparent(editorForeground, 0.5) },
diff --git a/src/vs/workbench/contrib/testing/common/constants.ts b/src/vs/workbench/contrib/testing/common/constants.ts
index c8fc457be2d..224cbd48e90 100644
--- a/src/vs/workbench/contrib/testing/common/constants.ts
+++ b/src/vs/workbench/contrib/testing/common/constants.ts
@@ -89,4 +89,5 @@ export const enum TestCommandId {
ToggleAutoRun = 'testing.toggleautoRun',
ToggleInlineTestOutput = 'testing.toggleInlineTestOutput',
UnhideTestAction = 'testing.unhideTest',
+ UnhideAllTestsAction = 'testing.unhideAllTests',
}
diff --git a/src/vs/workbench/contrib/testing/common/testResult.ts b/src/vs/workbench/contrib/testing/common/testResult.ts
index dce023d7d60..368d43988d7 100644
--- a/src/vs/workbench/contrib/testing/common/testResult.ts
+++ b/src/vs/workbench/contrib/testing/common/testResult.ts
@@ -321,7 +321,7 @@ export class LiveTestResult implements ITestResult {
location,
message: output.toString(),
offset: this.output.offset,
- type: TestMessageType.Info,
+ type: TestMessageType.Output,
};
const index = this.mustGetTaskIndex(taskId);
diff --git a/src/vs/workbench/contrib/testing/common/testTypes.ts b/src/vs/workbench/contrib/testing/common/testTypes.ts
index 6f3e639ef90..29ae1628071 100644
--- a/src/vs/workbench/contrib/testing/common/testTypes.ts
+++ b/src/vs/workbench/contrib/testing/common/testTypes.ts
@@ -117,7 +117,7 @@ export namespace IRichLocation {
export const enum TestMessageType {
Error,
- Info
+ Output
}
export interface ITestErrorMessage {
@@ -156,7 +156,7 @@ export namespace ITestErrorMessage {
export interface ITestOutputMessage {
message: string;
- type: TestMessageType.Info;
+ type: TestMessageType.Output;
offset: number;
location: IRichLocation | undefined;
}
@@ -165,20 +165,20 @@ export namespace ITestOutputMessage {
export interface Serialized {
message: string;
offset: number;
- type: TestMessageType.Info;
+ type: TestMessageType.Output;
location: IRichLocation.Serialize | undefined;
}
export const serialize = (message: ITestOutputMessage): Serialized => ({
message: message.message,
- type: TestMessageType.Info,
+ type: TestMessageType.Output,
offset: message.offset,
location: message.location && IRichLocation.serialize(message.location),
});
export const deserialize = (message: Serialized): ITestOutputMessage => ({
message: message.message,
- type: TestMessageType.Info,
+ type: TestMessageType.Output,
offset: message.offset,
location: message.location && IRichLocation.deserialize(message.location),
});
diff --git a/src/vs/workbench/contrib/testing/common/testingContentProvider.ts b/src/vs/workbench/contrib/testing/common/testingContentProvider.ts
index c93c0968a07..4ce00913d35 100644
--- a/src/vs/workbench/contrib/testing/common/testingContentProvider.ts
+++ b/src/vs/workbench/contrib/testing/common/testingContentProvider.ts
@@ -12,6 +12,7 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { TestMessageType } from 'vs/workbench/contrib/testing/common/testTypes';
import { parseTestUri, TestUriType, TEST_DATA_SCHEME } from 'vs/workbench/contrib/testing/common/testingUri';
import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
+import { removeAnsiEscapeCodes } from 'vs/base/common/strings';
/**
* A content provider that returns various outputs for tests. This is used
@@ -61,12 +62,14 @@ export class TestingContentProvider implements IWorkbenchContribution, ITextMode
break;
}
case TestUriType.ResultMessage: {
- const message = test.tasks[parsed.taskIndex].messages[parsed.messageIndex]?.message;
- if (typeof message === 'string') {
- text = message;
- } else if (message) {
- text = message.value;
- language = this.languageService.createById('markdown');
+ const message = test.tasks[parsed.taskIndex].messages[parsed.messageIndex];
+ if (message) {
+ if (typeof message.message === 'string') {
+ text = message.type === TestMessageType.Output ? removeAnsiEscapeCodes(message.message) : message.message;
+ } else {
+ text = message.message.value;
+ language = this.languageService.createById('markdown');
+ }
}
break;
}
diff --git a/src/vs/workbench/contrib/themes/test/electron-browser/colorRegistry.releaseTest.ts b/src/vs/workbench/contrib/themes/test/electron-browser/colorRegistry.releaseTest.ts
index 397ecffdb59..76104ae81f2 100644
--- a/src/vs/workbench/contrib/themes/test/electron-browser/colorRegistry.releaseTest.ts
+++ b/src/vs/workbench/contrib/themes/test/electron-browser/colorRegistry.releaseTest.ts
@@ -5,7 +5,7 @@
import { Registry } from 'vs/platform/registry/common/platform';
import { IColorRegistry, Extensions, ColorContribution } from 'vs/platform/theme/common/colorRegistry';
-import { asText } from 'vs/platform/request/common/request';
+import { asTextOrError } from 'vs/platform/request/common/request';
import * as pfs from 'vs/base/node/pfs';
import * as path from 'vs/base/common/path';
import * as assert from 'assert';
@@ -39,7 +39,7 @@ suite('Color Registry', function () {
const environmentService = new class extends mock<INativeEnvironmentService>() { override args = { _: [] }; };
const reqContext = await new RequestService(new TestConfigurationService(), environmentService, new NullLogService()).request({ url: 'https://raw.githubusercontent.com/microsoft/vscode-docs/vnext/api/references/theme-color.md' }, CancellationToken.None);
- const content = (await asText(reqContext))!;
+ const content = (await asTextOrError(reqContext))!;
const expression = /-\s*\`([\w\.]+)\`: (.*)/g;
diff --git a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts
index 98ed6664ed8..65b9d91beca 100644
--- a/src/vs/workbench/contrib/timeline/browser/timelinePane.ts
+++ b/src/vs/workbench/contrib/timeline/browser/timelinePane.ts
@@ -66,6 +66,7 @@ function isTimelineItem(item: TreeElement | undefined): item is TimelineItem {
function updateRelativeTime(item: TimelineItem, lastRelativeTime: string | undefined): string | undefined {
item.relativeTime = isTimelineItem(item) ? fromNow(item.timestamp) : undefined;
+ item.relativeTimeFullWord = isTimelineItem(item) ? fromNow(item.timestamp, false, true) : undefined;
if (lastRelativeTime === undefined || item.relativeTime !== lastRelativeTime) {
lastRelativeTime = item.relativeTime;
item.hideRelativeTime = false;
@@ -196,6 +197,7 @@ class LoadMoreCommand {
readonly iconDark = undefined;
readonly source = undefined;
readonly relativeTime = undefined;
+ readonly relativeTimeFullWord = undefined;
readonly hideRelativeTime = undefined;
constructor(loading: boolean) {
@@ -350,7 +352,7 @@ export class TimelinePane extends ViewPane {
}
private onActiveEditorChanged() {
- if (!this.followActiveEditor) {
+ if (!this.followActiveEditor || !this.isExpanded()) {
return;
}
@@ -569,9 +571,10 @@ export class TimelinePane extends ViewPane {
}
}
request?.tokenSource.dispose(true);
-
+ options.cacheResults = true;
+ options.resetCache = reset;
request = this.timelineService.getTimeline(
- source, uri, options, new CancellationTokenSource(), { cacheResults: true, resetCache: reset }
+ source, uri, options, new CancellationTokenSource()
);
if (request === undefined) {
@@ -885,7 +888,7 @@ export class TimelinePane extends ViewPane {
if (isLoadMoreCommand(element)) {
return element.ariaLabel;
}
- return element.accessibilityInformation ? element.accessibilityInformation.label : localize('timeline.aria.item', "{0}: {1}", element.relativeTime ?? '', element.label);
+ return element.accessibilityInformation ? element.accessibilityInformation.label : localize('timeline.aria.item', "{0}: {1}", element.relativeTimeFullWord ?? '', element.label);
},
getRole(element: TreeElement): string {
if (isLoadMoreCommand(element)) {
@@ -1172,6 +1175,7 @@ class TimelineTreeRenderer implements ITreeRenderer<TreeElement, FuzzyScore, Tim
});
template.timestamp.textContent = item.relativeTime ?? '';
+ template.timestamp.ariaLabel = item.relativeTimeFullWord ?? '';
template.timestamp.parentElement!.classList.toggle('timeline-timestamp--duplicate', isTimelineItem(item) && item.hideRelativeTime);
template.actionBar.context = { uri: this.uri, item: item } as TimelineActionContext;
diff --git a/src/vs/workbench/contrib/timeline/common/timeline.ts b/src/vs/workbench/contrib/timeline/common/timeline.ts
index edf2a850a02..5185f65703e 100644
--- a/src/vs/workbench/contrib/timeline/common/timeline.ts
+++ b/src/vs/workbench/contrib/timeline/common/timeline.ts
@@ -51,6 +51,7 @@ export interface TimelineItem {
contextValue?: string;
relativeTime?: string;
+ relativeTimeFullWord?: string;
hideRelativeTime?: boolean;
}
@@ -76,11 +77,8 @@ export interface TimelineChangeEvent {
export interface TimelineOptions {
cursor?: string;
limit?: number | { timestamp: number; id?: string };
-}
-
-export interface InternalTimelineOptions {
- cacheResults: boolean;
- resetCache: boolean;
+ resetCache?: boolean;
+ cacheResults?: boolean;
}
export interface Timeline {
@@ -100,7 +98,7 @@ export interface Timeline {
export interface TimelineProvider extends TimelineProviderDescriptor, IDisposable {
onDidChange?: Event<TimelineChangeEvent>;
- provideTimeline(uri: URI, options: TimelineOptions, token: CancellationToken, internalOptions?: InternalTimelineOptions): Promise<Timeline | undefined>;
+ provideTimeline(uri: URI, options: TimelineOptions, token: CancellationToken): Promise<Timeline | undefined>;
}
export interface TimelineSource {
@@ -151,7 +149,7 @@ export interface ITimelineService {
getSources(): TimelineSource[];
- getTimeline(id: string, uri: URI, options: TimelineOptions, tokenSource: CancellationTokenSource, internalOptions?: InternalTimelineOptions): TimelineRequest | undefined;
+ getTimeline(id: string, uri: URI, options: TimelineOptions, tokenSource: CancellationTokenSource): TimelineRequest | undefined;
setUri(uri: URI): void;
}
diff --git a/src/vs/workbench/contrib/timeline/common/timelineService.ts b/src/vs/workbench/contrib/timeline/common/timelineService.ts
index 52bc586f90d..190dfa2501c 100644
--- a/src/vs/workbench/contrib/timeline/common/timelineService.ts
+++ b/src/vs/workbench/contrib/timeline/common/timelineService.ts
@@ -8,7 +8,7 @@ import { Event, Emitter } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { ILogService } from 'vs/platform/log/common/log';
-import { ITimelineService, TimelineChangeEvent, TimelineOptions, TimelineProvidersChangeEvent, TimelineProvider, InternalTimelineOptions, TimelinePaneId } from './timeline';
+import { ITimelineService, TimelineChangeEvent, TimelineOptions, TimelineProvidersChangeEvent, TimelineProvider, TimelinePaneId } from './timeline';
import { IViewsService } from 'vs/workbench/common/views';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
@@ -44,7 +44,7 @@ export class TimelineService implements ITimelineService {
return [...this.providers.values()].map(p => ({ id: p.id, label: p.label }));
}
- getTimeline(id: string, uri: URI, options: TimelineOptions, tokenSource: CancellationTokenSource, internalOptions?: InternalTimelineOptions) {
+ getTimeline(id: string, uri: URI, options: TimelineOptions, tokenSource: CancellationTokenSource) {
this.logService.trace(`TimelineService#getTimeline(${id}): uri=${uri.toString()}`);
const provider = this.providers.get(id);
@@ -61,7 +61,7 @@ export class TimelineService implements ITimelineService {
}
return {
- result: provider.provideTimeline(uri, options, tokenSource.token, internalOptions)
+ result: provider.provideTimeline(uri, options, tokenSource.token)
.then(result => {
if (result === undefined) {
return undefined;
diff --git a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts
index f7379a4d721..df20df258da 100644
--- a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts
+++ b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts
@@ -21,7 +21,7 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IProductService } from 'vs/platform/product/common/productService';
-import { asText, IRequestService } from 'vs/platform/request/common/request';
+import { asTextOrError, IRequestService } from 'vs/platform/request/common/request';
import { DEFAULT_MARKDOWN_STYLES, renderMarkdownDocument } from 'vs/workbench/contrib/markdown/browser/markdownDocumentRenderer';
import { WebviewInput } from 'vs/workbench/contrib/webviewPanel/browser/webviewEditorInput';
import { IWebviewWorkbenchService } from 'vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService';
@@ -163,7 +163,7 @@ export class ReleaseNotesManager {
const fetchReleaseNotes = async () => {
let text;
try {
- text = await asText(await this._requestService.request({ url }, CancellationToken.None));
+ text = await asTextOrError(await this._requestService.request({ url }, CancellationToken.None));
} catch {
throw new Error('Failed to fetch release notes');
}
diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts
index 07d50613857..4a7fe8b6202 100644
--- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts
+++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts
@@ -37,7 +37,7 @@ import { EditorResourceAccessor, SideBySideEditor } from 'vs/workbench/common/ed
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
import * as Constants from 'vs/workbench/contrib/logs/common/logConstants';
-import { IOutputService } from 'vs/workbench/contrib/output/common/output';
+import { IOutputService } from 'vs/workbench/services/output/common/output';
import { IActivityService, IBadge, NumberBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
diff --git a/src/vs/workbench/contrib/webview/browser/overlayWebview.ts b/src/vs/workbench/contrib/webview/browser/overlayWebview.ts
index bb25dfdc171..112efec0ac3 100644
--- a/src/vs/workbench/contrib/webview/browser/overlayWebview.ts
+++ b/src/vs/workbench/contrib/webview/browser/overlayWebview.ts
@@ -21,7 +21,8 @@ export class OverlayWebview extends Disposable implements IOverlayWebview {
private readonly _onDidWheel = this._register(new Emitter<IMouseWheelEvent>());
public readonly onDidWheel = this._onDidWheel.event;
- private readonly _pendingMessages = new Set<{ readonly message: any; readonly transfer?: readonly ArrayBuffer[] }>();
+ private _isFirstLoad = true;
+ private readonly _firstLoadPendingMessages = new Set<{ readonly message: any; readonly transfer?: readonly ArrayBuffer[]; readonly resolve: (value: boolean) => void }>();
private readonly _webview = this._register(new MutableDisposable<IWebviewElement>());
private readonly _webviewEvents = this._register(new DisposableStore());
@@ -70,6 +71,11 @@ export class OverlayWebview extends Disposable implements IOverlayWebview {
this._container?.remove();
this._container = undefined;
+ for (const msg of this._firstLoadPendingMessages) {
+ msg.resolve(false);
+ }
+ this._firstLoadPendingMessages.clear();
+
this._onDidDispose.fire();
super.dispose();
@@ -200,8 +206,13 @@ export class OverlayWebview extends Disposable implements IOverlayWebview {
this._onDidUpdateState.fire(state);
}));
- this._pendingMessages.forEach(msg => webview.postMessage(msg.message, msg.transfer));
- this._pendingMessages.clear();
+ if (this._isFirstLoad) {
+ this._firstLoadPendingMessages.forEach(async msg => {
+ msg.resolve(await webview.postMessage(msg.message, msg.transfer));
+ });
+ }
+ this._isFirstLoad = false;
+ this._firstLoadPendingMessages.clear();
}
this.container.style.visibility = 'visible';
@@ -268,12 +279,19 @@ export class OverlayWebview extends Disposable implements IOverlayWebview {
private readonly _onMissingCsp = this._register(new Emitter<ExtensionIdentifier>());
public readonly onMissingCsp: Event<any> = this._onMissingCsp.event;
- public postMessage(message: any, transfer?: readonly ArrayBuffer[]): void {
+ public async postMessage(message: any, transfer?: readonly ArrayBuffer[]): Promise<boolean> {
if (this._webview.value) {
- this._webview.value.postMessage(message, transfer);
- } else {
- this._pendingMessages.add({ message, transfer });
+ return this._webview.value.postMessage(message, transfer);
}
+
+ if (this._isFirstLoad) {
+ let resolve: (x: boolean) => void;
+ const p = new Promise<boolean>(r => resolve = r);
+ this._firstLoadPendingMessages.add({ message, transfer, resolve: resolve! });
+ return p;
+ }
+
+ return false;
}
focus(): void { this._webview.value?.focus(); }
diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js
index 48beccb020a..d2c57f4aa10 100644
--- a/src/vs/workbench/contrib/webview/browser/pre/main.js
+++ b/src/vs/workbench/contrib/webview/browser/pre/main.js
@@ -368,16 +368,14 @@ const unloadMonitor = new class {
}
switch (this.confirmBeforeClose) {
- case 'always':
- {
- event.preventDefault();
- event.returnValue = '';
- return '';
- }
- case 'never':
- {
- break;
- }
+ case 'always': {
+ event.preventDefault();
+ event.returnValue = '';
+ return '';
+ }
+ case 'never': {
+ break;
+ }
case 'keyboardOnly':
default: {
if (this.isModifierKeyDown) {
@@ -680,6 +678,22 @@ const handleInnerScroll = (event) => {
});
};
+function handleInnerDragStartEvent(/** @type {DragEvent} */ e) {
+ if (e.defaultPrevented) {
+ // Extension code has already handled this event
+ return;
+ }
+
+ if (!e.dataTransfer || e.shiftKey) {
+ return;
+ }
+
+ // Only handle drags from outside editor for now
+ if (e.dataTransfer.items.length && Array.prototype.every.call(e.dataTransfer.items, item => item.kind === 'file')) {
+ hostMessaging.postMessage('drag-start');
+ }
+}
+
/**
* @param {() => void} callback
*/
@@ -1021,6 +1035,9 @@ onDomReady(() => {
});
});
+ contentWindow.addEventListener('dragenter', handleInnerDragStartEvent);
+ contentWindow.addEventListener('dragover', handleInnerDragStartEvent);
+
unloadMonitor.onIframeLoaded(newFrame);
}
});
@@ -1107,5 +1124,10 @@ onDomReady(() => {
}
};
+ // Also forward events before the contents of the webview have loaded
+ window.addEventListener('keydown', handleInnerKeydown);
+ window.addEventListener('dragenter', handleInnerDragStartEvent);
+ window.addEventListener('dragover', handleInnerDragStartEvent);
+
hostMessaging.signalReady();
});
diff --git a/src/vs/workbench/contrib/webview/browser/pre/service-worker.js b/src/vs/workbench/contrib/webview/browser/pre/service-worker.js
index 70534ee470d..81a45ab6ca7 100644
--- a/src/vs/workbench/contrib/webview/browser/pre/service-worker.js
+++ b/src/vs/workbench/contrib/webview/browser/pre/service-worker.js
@@ -189,8 +189,10 @@ sw.addEventListener('fetch', (event) => {
}
// If we're making a request against the remote authority, we want to go
- // back through VS Code itself so that we are authenticated properly
- if (requestUrl.host === remoteAuthority) {
+ // through VS Code itself so that we are authenticated properly. If the
+ // service worker is hosted on the same origin we will have cookies and
+ // authentication will not be an issue.
+ if (requestUrl.origin !== sw.origin && requestUrl.host === remoteAuthority) {
switch (event.request.method) {
case 'GET':
case 'HEAD':
diff --git a/src/vs/workbench/contrib/webview/browser/webview.ts b/src/vs/workbench/contrib/webview/browser/webview.ts
index 0db26467cba..284a3d7f897 100644
--- a/src/vs/workbench/contrib/webview/browser/webview.ts
+++ b/src/vs/workbench/contrib/webview/browser/webview.ts
@@ -183,7 +183,7 @@ export interface IWebview extends IDisposable {
readonly onMessage: Event<WebviewMessageReceivedEvent>;
readonly onMissingCsp: Event<ExtensionIdentifier>;
- postMessage(message: any, transfer?: readonly ArrayBuffer[]): void;
+ postMessage(message: any, transfer?: readonly ArrayBuffer[]): Promise<boolean>;
focus(): void;
reload(): void;
diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts
index 1c312d73bdd..6cd87f63f28 100644
--- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts
+++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts
@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { isFirefox } from 'vs/base/browser/browser';
-import { addDisposableListener } from 'vs/base/browser/dom';
+import { addDisposableListener, EventType } from 'vs/base/browser/dom';
import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent';
import { IAction } from 'vs/base/common/actions';
import { ThrottledDelayer } from 'vs/base/common/async';
@@ -59,6 +59,7 @@ export const enum WebviewMessageChannels {
didKeydown = 'did-keydown',
didKeyup = 'did-keyup',
didContextMenu = 'did-context-menu',
+ dragStart = 'drag-start',
}
interface IKeydownEvent {
@@ -85,10 +86,11 @@ namespace WebviewState {
readonly type = Type.Initializing;
constructor(
- public readonly pendingMessages: Array<{
+ public pendingMessages: Array<{
readonly channel: string;
readonly data?: any;
readonly transferable: Transferable[];
+ readonly resolve: (posted: boolean) => void;
}>
) { }
}
@@ -349,6 +351,10 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD
}
}));
+ this._register(this.on(WebviewMessageChannels.dragStart, () => {
+ this.startBlockingIframeDragEvents();
+ }));
+
if (options.enableFindWidget) {
this._webviewFindWidget = this._register(instantiationService.createInstance(WebviewFindWidget, this));
this.styledFindWidget();
@@ -369,6 +375,13 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD
this.messagePort = undefined;
+ if (this._state.type === WebviewState.Type.Initializing) {
+ for (const message of this._state.pendingMessages) {
+ message.resolve(false);
+ }
+ this._state.pendingMessages = [];
+ }
+
this._onDidDispose.fire();
this._resourceLoadingCts.dispose(true);
@@ -410,15 +423,18 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD
private readonly _onDidDispose = this._register(new Emitter<void>());
public readonly onDidDispose = this._onDidDispose.event;
- public postMessage(message: any, transfer?: ArrayBuffer[]): void {
- this._send('message', { message, transfer });
+ public postMessage(message: any, transfer?: ArrayBuffer[]): Promise<boolean> {
+ return this._send('message', { message, transfer });
}
- protected _send(channel: string, data?: any, transferable: Transferable[] = []): void {
+ protected async _send(channel: string, data?: any, transferable: Transferable[] = []): Promise<boolean> {
if (this._state.type === WebviewState.Type.Initializing) {
- this._state.pendingMessages.push({ channel, data, transferable });
+ let resolve: (x: boolean) => void;
+ const promise = new Promise<boolean>(r => resolve = r);
+ this._state.pendingMessages.push({ channel, data, transferable, resolve: resolve! });
+ return promise;
} else {
- this.doPostMessage(channel, data, transferable);
+ return this.doPostMessage(channel, data, transferable);
}
}
@@ -478,9 +494,32 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD
if (this._webviewFindWidget) {
parent.appendChild(this._webviewFindWidget.getDomNode());
}
+
+ [EventType.MOUSE_DOWN, EventType.MOUSE_MOVE, EventType.DROP].forEach(eventName => {
+ this._register(addDisposableListener(parent, eventName, () => {
+ this.stopBlockingIframeDragEvents();
+ }));
+ });
+
+ [parent, window].forEach(node => this._register(addDisposableListener(node as HTMLElement, EventType.DRAG_END, () => {
+ this.stopBlockingIframeDragEvents();
+ })));
+
parent.appendChild(this.element);
}
+ private startBlockingIframeDragEvents() {
+ if (this.element) {
+ this.element.style.pointerEvents = 'none';
+ }
+ }
+
+ private stopBlockingIframeDragEvents() {
+ if (this.element) {
+ this.element.style.pointerEvents = 'auto';
+ }
+ }
+
protected webviewContentEndpoint(encodedWebviewOrigin: string): string {
const endpoint = this._environmentService.webviewExternalEndpoint!.replace('{{uuid}}', encodedWebviewOrigin);
if (endpoint[endpoint.length - 1] === '/') {
@@ -494,10 +533,12 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD
return uri.scheme + '://' + uri.authority.toLowerCase();
}
- private doPostMessage(channel: string, data?: any, transferable: Transferable[] = []): void {
+ private doPostMessage(channel: string, data?: any, transferable: Transferable[] = []): boolean {
if (this.element && this.messagePort) {
this.messagePort.postMessage({ channel, args: data }, transferable);
+ return true;
}
+ return false;
}
protected on<T = unknown>(channel: WebviewMessageChannels, handler: (data: T, e: MessageEvent) => void): IDisposable {
@@ -530,7 +571,9 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD
} as const;
type Classification = {
- extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; owner: 'mjbvz'; comment: 'The id of the extension that created the webview.' };
+ extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The id of the extension that created the webview.' };
+ owner: 'mjbz';
+ comment: 'Helps find which extensions are contributing webviews with invalid CSPs';
};
this._telemetryService.publicLog2<typeof payload, Classification>('webviewMissingCsp', payload);
@@ -670,18 +713,14 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD
}
windowDidDragStart(): void {
- // Webview break drag and droping around the main window (no events are generated when you are over them)
+ // Webview break drag and dropping around the main window (no events are generated when you are over them)
// Work around this by disabling pointer events during the drag.
// https://github.com/electron/electron/issues/18226
- if (this.element) {
- this.element.style.pointerEvents = 'none';
- }
+ this.startBlockingIframeDragEvents();
}
windowDidDragEnd(): void {
- if (this.element) {
- this.element.style.pointerEvents = '';
- }
+ this.stopBlockingIframeDragEvents();
}
public selectAll() {
diff --git a/src/vs/workbench/contrib/webview/browser/webviewFindWidget.ts b/src/vs/workbench/contrib/webview/browser/webviewFindWidget.ts
index e7bad42e252..58841297621 100644
--- a/src/vs/workbench/contrib/webview/browser/webviewFindWidget.ts
+++ b/src/vs/workbench/contrib/webview/browser/webviewFindWidget.ts
@@ -6,6 +6,7 @@
import { Event } from 'vs/base/common/event';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
+import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { SimpleFindWidget } from 'vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget';
import { KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_FOCUSED } from 'vs/workbench/contrib/webview/browser/webview';
@@ -29,9 +30,10 @@ export class WebviewFindWidget extends SimpleFindWidget {
constructor(
private readonly _delegate: WebviewFindDelegate,
@IContextViewService contextViewService: IContextViewService,
- @IContextKeyService contextKeyService: IContextKeyService
+ @IContextKeyService contextKeyService: IContextKeyService,
+ @IKeybindingService keybindingService: IKeybindingService
) {
- super(contextViewService, contextKeyService, undefined, { showOptionButtons: false, checkImeCompletionState: _delegate.checkImeCompletionState });
+ super(undefined, { showOptionButtons: false, checkImeCompletionState: _delegate.checkImeCompletionState }, contextViewService, contextKeyService, keybindingService);
this._findWidgetFocused = KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_FOCUSED.bindTo(contextKeyService);
this._register(_delegate.hasFindResult(hasResult => {
diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInput.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInput.ts
index 1caa0e68f25..63e3f1cc124 100644
--- a/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInput.ts
+++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInput.ts
@@ -23,7 +23,7 @@ export class WebviewInput extends EditorInput {
}
public override get capabilities(): EditorInputCapabilities {
- return EditorInputCapabilities.Readonly | EditorInputCapabilities.Singleton;
+ return EditorInputCapabilities.Readonly | EditorInputCapabilities.Singleton | EditorInputCapabilities.CanDropIntoEditor;
}
private _name: string;
diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts
index 9784e9f9fd2..7015fa5e7b4 100644
--- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts
+++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts
@@ -99,12 +99,16 @@ const parsedStartEntries: IWelcomePageStartEntry[] = startEntries.map((e, i) =>
}));
type GettingStartedActionClassification = {
- command: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; owner: 'JacksonKearl'; comment: 'Help understand what actions are most commonly taken on the getting started page' };
- argument: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; owner: 'JacksonKearl'; comment: 'As above' };
+ command: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The command being executed on the getting started page.' };
+ walkthroughId: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The walkthrough which the command is in' };
+ argument: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'The arguments being passed to the command' };
+ owner: 'lramos15';
+ comment: 'Help understand what actions are most commonly taken on the getting started page';
};
type GettingStartedActionEvent = {
command: string;
+ walkthroughId: string | undefined;
argument: string | undefined;
};
@@ -343,7 +347,7 @@ export class GettingStartedPage extends EditorPane {
private async runDispatchCommand(command: string, argument: string) {
this.commandService.executeCommand('workbench.action.keepEditor');
- this.telemetryService.publicLog2<GettingStartedActionEvent, GettingStartedActionClassification>('gettingStarted.ActionExecuted', { command, argument });
+ this.telemetryService.publicLog2<GettingStartedActionEvent, GettingStartedActionClassification>('gettingStarted.ActionExecuted', { command, argument, walkthroughId: this.currentWalkthrough?.id });
switch (command) {
case 'scrollPrev': {
this.scrollPrev();
@@ -511,7 +515,7 @@ export class GettingStartedPage extends EditorPane {
if (hrefs.length === 1) {
const href = hrefs[0];
if (href.startsWith('http')) {
- this.telemetryService.publicLog2<GettingStartedActionEvent, GettingStartedActionClassification>('gettingStarted.ActionExecuted', { command: 'runStepAction', argument: href });
+ this.telemetryService.publicLog2<GettingStartedActionEvent, GettingStartedActionClassification>('gettingStarted.ActionExecuted', { command: 'runStepAction', argument: href, walkthroughId: this.currentWalkthrough?.id });
this.openerService.open(href);
}
}
@@ -546,7 +550,7 @@ export class GettingStartedPage extends EditorPane {
if (hrefs.length === 1) {
const href = hrefs[0];
if (href.startsWith('http')) {
- this.telemetryService.publicLog2<GettingStartedActionEvent, GettingStartedActionClassification>('gettingStarted.ActionExecuted', { command: 'runStepAction', argument: href });
+ this.telemetryService.publicLog2<GettingStartedActionEvent, GettingStartedActionClassification>('gettingStarted.ActionExecuted', { command: 'runStepAction', argument: href, walkthroughId: this.currentWalkthrough?.id });
this.openerService.open(href);
}
}
@@ -723,10 +727,10 @@ export class GettingStartedPage extends EditorPane {
const showOnStartupLabel = $('label.caption', { for: 'showOnStartup' }, localize('welcomePage.showOnStartup', "Show welcome page on startup"));
const onShowOnStartupChanged = () => {
if (showOnStartupCheckbox.checked) {
- this.telemetryService.publicLog2<GettingStartedActionEvent, GettingStartedActionClassification>('gettingStarted.ActionExecuted', { command: 'showOnStartupChecked', argument: undefined });
+ this.telemetryService.publicLog2<GettingStartedActionEvent, GettingStartedActionClassification>('gettingStarted.ActionExecuted', { command: 'showOnStartupChecked', argument: undefined, walkthroughId: this.currentWalkthrough?.id });
this.configurationService.updateValue(configurationKey, 'welcomePage');
} else {
- this.telemetryService.publicLog2<GettingStartedActionEvent, GettingStartedActionClassification>('gettingStarted.ActionExecuted', { command: 'showOnStartupUnchecked', argument: undefined });
+ this.telemetryService.publicLog2<GettingStartedActionEvent, GettingStartedActionClassification>('gettingStarted.ActionExecuted', { command: 'showOnStartupUnchecked', argument: undefined, walkthroughId: this.currentWalkthrough?.id });
this.configurationService.updateValue(configurationKey, 'none');
}
};
@@ -852,7 +856,7 @@ export class GettingStartedPage extends EditorPane {
link.title = fullPath;
link.setAttribute('aria-label', localize('welcomePage.openFolderWithPath', "Open folder {0} with path {1}", name, parentPath));
link.addEventListener('click', e => {
- this.telemetryService.publicLog2<GettingStartedActionEvent, GettingStartedActionClassification>('gettingStarted.ActionExecuted', { command: 'openRecent', argument: undefined });
+ this.telemetryService.publicLog2<GettingStartedActionEvent, GettingStartedActionClassification>('gettingStarted.ActionExecuted', { command: 'openRecent', argument: undefined, walkthroughId: this.currentWalkthrough?.id });
this.hostService.openWindow([windowOpenable], {
forceNewWindow: e.ctrlKey || e.metaKey,
remoteAuthority: recent.remoteAuthority || null // local window if remoteAuthority is not set or can not be deducted from the openable
@@ -1092,7 +1096,7 @@ export class GettingStartedPage extends EditorPane {
const toSide = href.startsWith('command:toSide:');
const command = href.replace(/command:(toSide:)?/, 'command:');
- this.telemetryService.publicLog2<GettingStartedActionEvent, GettingStartedActionClassification>('gettingStarted.ActionExecuted', { command: 'runStepAction', argument: href });
+ this.telemetryService.publicLog2<GettingStartedActionEvent, GettingStartedActionClassification>('gettingStarted.ActionExecuted', { command: 'runStepAction', argument: href, walkthroughId: this.currentWalkthrough?.id });
const fullSize = this.groupsService.contentDimension;
diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts
index 75b3891db8d..4be26d8aedc 100644
--- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts
+++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts
@@ -340,7 +340,7 @@ export class WalkthroughsService extends Disposable implements IWalkthroughsServ
}
if (step.media.image) {
- const altText = (step.media as any).altText;
+ const altText = step.media.altText;
if (altText === undefined) {
console.error('Walkthrough item:', fullyQualifiedID, 'is missing altText for its media element.');
}
@@ -362,7 +362,7 @@ export class WalkthroughsService extends Disposable implements IWalkthroughsServ
};
}
- // Legacy media config
+ // Legacy media config (only in use by remote-wsl at the moment)
else {
const legacyMedia = step.media as unknown as { path: string; altText: string };
if (typeof legacyMedia.path === 'string' && legacyMedia.path.endsWith('.md')) {
diff --git a/src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts b/src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts
index b78599440db..8b87c52e229 100644
--- a/src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts
+++ b/src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts
@@ -9,7 +9,7 @@ import { assertIsDefined } from 'vs/base/common/types';
import { localize } from 'vs/nls';
import { Action2, IMenuService, MenuId, registerAction2, IMenu, MenuRegistry, MenuItemAction } from 'vs/platform/actions/common/actions';
import { ICommandService } from 'vs/platform/commands/common/commands';
-import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
+import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
@@ -18,11 +18,9 @@ import { Registry } from 'vs/platform/registry/common/platform';
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
-
+const builtInSource = localize('Built-In', "Built-In");
const category = localize('Create', "Create");
-export const HasMultipleNewFileEntries = new RawContextKey<boolean>('hasMultipleNewFileEntries', false);
-
registerAction2(class extends Action2 {
constructor() {
super({
@@ -36,15 +34,14 @@ registerAction2(class extends Action2 {
},
menu: {
id: MenuId.MenubarFileMenu,
- when: HasMultipleNewFileEntries,
group: '1_new',
order: 2
}
});
}
- run(accessor: ServicesAccessor) {
- assertIsDefined(NewFileTemplatesManager.Instance).run();
+ async run(accessor: ServicesAccessor): Promise<boolean> {
+ return assertIsDefined(NewFileTemplatesManager.Instance).run();
}
});
@@ -68,8 +65,6 @@ class NewFileTemplatesManager extends Disposable {
this._register({ dispose() { if (NewFileTemplatesManager.Instance === this) { NewFileTemplatesManager.Instance = undefined; } } });
this.menu = menuService.createMenu(MenuId.NewFile, contextKeyService);
- this.updateContextKeys();
- this._register(this.menu.onDidChange(() => { this.updateContextKeys(); }));
}
private allEntries(): NewFileItem[] {
@@ -77,43 +72,53 @@ class NewFileTemplatesManager extends Disposable {
for (const [groupName, group] of this.menu.getActions({ renderShortTitle: true })) {
for (const action of group) {
if (action instanceof MenuItemAction) {
- items.push({ commandID: action.item.id, from: action.item.source ?? localize('Built-In', "Built-In"), title: action.label, group: groupName });
+ items.push({ commandID: action.item.id, from: action.item.source ?? builtInSource, title: action.label, group: groupName });
}
}
}
return items;
}
- private updateContextKeys() {
- HasMultipleNewFileEntries.bindTo(this.contextKeyService).set(this.allEntries().length > 1);
- }
-
- run() {
+ async run(): Promise<boolean> {
const entries = this.allEntries();
if (entries.length === 0) {
throw Error('Unexpected empty new items list');
}
else if (entries.length === 1) {
this.commandService.executeCommand(entries[0].commandID);
+ return true;
}
else {
- this.selectNewEntry(entries);
+ return this.selectNewEntry(entries);
}
}
- private async selectNewEntry(entries: NewFileItem[]) {
+ private async selectNewEntry(entries: NewFileItem[]): Promise<boolean> {
+ let resolveResult: (res: boolean) => void;
+ const resultPromise = new Promise<boolean>(resolve => {
+ resolveResult = resolve;
+ });
+
const disposables = new DisposableStore();
const qp = this.quickInputService.createQuickPick();
qp.title = localize('createNew', "Create New...");
qp.matchOnDetail = true;
qp.matchOnDescription = true;
- const sortCategories = (a: string, b: string): number => {
+ const sortCategories = (a: NewFileItem, b: NewFileItem): number => {
const categoryPriority: Record<string, number> = { 'file': 1, 'notebook': 2 };
- if (categoryPriority[a] && categoryPriority[b]) { return categoryPriority[b] - categoryPriority[a]; }
- if (categoryPriority[a]) { return 1; }
- if (categoryPriority[b]) { return -1; }
- return a.localeCompare(b);
+ if (categoryPriority[a.group] && categoryPriority[b.group]) {
+ if (categoryPriority[a.group] !== categoryPriority[b.group]) {
+ return categoryPriority[b.group] - categoryPriority[a.group];
+ }
+ }
+ else if (categoryPriority[a.group]) { return 1; }
+ else if (categoryPriority[b.group]) { return -1; }
+
+ if (a.from === builtInSource) { return 1; }
+ if (b.from === builtInSource) { return -1; }
+
+ return a.from.localeCompare(b.from);
};
const displayCategory: Record<string, string> = {
@@ -125,7 +130,7 @@ class NewFileTemplatesManager extends Disposable {
const items: (((IQuickPickItem & NewFileItem) | IQuickPickSeparator))[] = [];
let lastSeparator: string | undefined;
entries
- .sort((a, b) => -sortCategories(a.group, b.group))
+ .sort((a, b) => -sortCategories(a, b))
.forEach((entry) => {
const command = entry.commandID;
const keybinding = this.keybindingService.lookupKeybinding(command || '', this.contextKeyService);
@@ -159,6 +164,8 @@ class NewFileTemplatesManager extends Disposable {
disposables.add(qp.onDidAccept(async e => {
const selected = qp.selectedItems[0] as (IQuickPickItem & NewFileItem);
+ resolveResult(!!selected);
+
qp.hide();
if (selected) { await this.commandService.executeCommand(selected.commandID); }
}));
@@ -166,23 +173,26 @@ class NewFileTemplatesManager extends Disposable {
disposables.add(qp.onDidHide(() => {
qp.dispose();
disposables.dispose();
+ resolveResult(false);
}));
disposables.add(qp.onDidTriggerItemButton(e => {
qp.hide();
this.commandService.executeCommand('workbench.action.openGlobalKeybindings', (e.item as (IQuickPickItem & NewFileItem)).commandID);
+ resolveResult(false);
}));
qp.show();
- }
+ return resultPromise;
+ }
}
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
.registerWorkbenchContribution(NewFileTemplatesManager, LifecyclePhase.Restored);
MenuRegistry.appendMenuItem(MenuId.NewFile, {
- group: 'File',
+ group: 'file',
command: {
id: 'workbench.action.files.newUntitledFile',
title: localize('miNewFile2', "Text File")
diff --git a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts
index 714d83dc166..1575fbf2118 100644
--- a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts
+++ b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts
@@ -761,7 +761,9 @@ class WorkspaceTrustTelemetryContribution extends Disposable implements IWorkben
const disabledByCliFlag = this.environmentService.disableWorkspaceTrust;
type WorkspaceTrustDisabledEventClassification = {
- reason: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
+ owner: 'sbatten';
+ comment: 'Logged when workspace trust is disabled';
+ reason: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The reason workspace trust is disabled. e.g. cli or setting' };
};
type WorkspaceTrustDisabledEvent = {
@@ -775,7 +777,9 @@ class WorkspaceTrustTelemetryContribution extends Disposable implements IWorkben
}
type WorkspaceTrustInfoEventClassification = {
- trustedFoldersCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true };
+ owner: 'sbatten';
+ comment: 'Information about the workspaces trusted on the machine';
+ trustedFoldersCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of trusted folders on the machine' };
};
type WorkspaceTrustInfoEvent = {
@@ -798,8 +802,10 @@ class WorkspaceTrustTelemetryContribution extends Disposable implements IWorkben
};
type WorkspaceTrustStateChangedEventClassification = {
- workspaceId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' };
- isTrusted: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true };
+ owner: 'sbatten';
+ comment: 'Logged when the workspace transitions between trusted and restricted modes';
+ workspaceId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'An id of the workspace' };
+ isTrusted: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'true if the workspace is trusted' };
};
this.telemetryService.publicLog2<WorkspaceTrustStateChangedEvent, WorkspaceTrustStateChangedEventClassification>('workspaceTrustStateChanged', {
@@ -809,9 +815,11 @@ class WorkspaceTrustTelemetryContribution extends Disposable implements IWorkben
if (isTrusted) {
type WorkspaceTrustFolderInfoEventClassification = {
- trustedFolderDepth: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true };
- workspaceFolderDepth: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true };
- delta: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true };
+ owner: 'sbatten';
+ comment: 'Some metrics on the trusted workspaces folder structure';
+ trustedFolderDepth: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of directories deep of the trusted path' };
+ workspaceFolderDepth: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of directories deep of the workspace path' };
+ delta: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The difference between the trusted path and the workspace path directories depth' };
};
type WorkspaceTrustFolderInfoEvent = {
diff --git a/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts b/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts
index 5aae1567d83..679be9104d5 100644
--- a/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts
+++ b/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts
@@ -50,7 +50,8 @@ import { WorkspaceTrustEditorInput } from 'vs/workbench/services/workspaces/brow
import { IEditorOptions } from 'vs/platform/editor/common/editor';
import { getExtensionDependencies } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { EnablementState, IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
-import { posix } from 'vs/base/common/path';
+import { posix, win32 } from 'vs/base/common/path';
+import { hasDriveLetter, toSlashes } from 'vs/base/common/extpath';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { IProductService } from 'vs/platform/product/common/productService';
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
@@ -352,7 +353,6 @@ class WorkspaceTrustedUrisTable extends Disposable {
canSelectMany: false,
defaultUri: item.uri,
openLabel: localize('trustUri', "Trust Folder"),
-
title: localize('selectTrustedUri', "Select Folder To Trust")
});
@@ -530,8 +530,10 @@ class TrustedUriPathColumnRenderer implements ITableRenderer<ITrustedUriItem, IT
const accept = () => {
hideInputBox();
- const uri = item.uri.with({ path: templateData.pathInput.value });
- templateData.pathLabel.innerText = templateData.pathInput.value;
+
+ const pathToUse = templateData.pathInput.value;
+ const uri = hasDriveLetter(pathToUse) ? item.uri.with({ path: posix.sep + toSlashes(pathToUse) }) : item.uri.with({ path: pathToUse });
+ templateData.pathLabel.innerText = this.formatPath(uri);
if (uri) {
this.table.acceptEdit(item, uri);
@@ -563,12 +565,10 @@ class TrustedUriPathColumnRenderer implements ITableRenderer<ITrustedUriItem, IT
reject();
})));
- const stringValue = item.uri.scheme === Schemas.file ? URI.revive(item.uri).fsPath : item.uri.path;
+ const stringValue = this.formatPath(item.uri);
templateData.pathInput.value = stringValue;
templateData.pathLabel.innerText = stringValue;
templateData.element.classList.toggle('current-workspace-parent', item.parentOfWorkspaceItem);
-
- // templateData.pathLabel.style.display = '';
}
disposeTemplate(templateData: ITrustedUriPathColumnTemplateData): void {
@@ -576,6 +576,24 @@ class TrustedUriPathColumnRenderer implements ITableRenderer<ITrustedUriItem, IT
templateData.renderDisposables.dispose();
}
+ private formatPath(uri: URI): string {
+ if (uri.scheme === Schemas.file) {
+ return uri.fsPath;
+ }
+
+ // If the path is not a file uri, but points to a windows remote, we should create windows fs path
+ // e.g. /c:/user/directory => C:\user\directory
+ if (uri.path.startsWith(posix.sep)) {
+ const pathWithoutLeadingSeparator = uri.path.substring(1);
+ const isWindowsPath = hasDriveLetter(pathWithoutLeadingSeparator, true);
+ if (isWindowsPath) {
+ return win32.normalize(pathWithoutLeadingSeparator);
+ }
+ }
+
+ return uri.path;
+ }
+
}