From 97a0f6c872c292cf03ebcddd0cc436ad0ea937fc Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Tue, 21 Jun 2022 08:27:32 +0000 Subject: =?UTF-8?q?=F0=9F=94=A8=20Take=20out=20core=20functionality=20clas?= =?UTF-8?q?s=20from=20SurroundWithSnippet=20action?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Babak K. Shandiz --- .../snippets/browser/surroundWithSnippet.ts | 87 +++++++++++++++------- 1 file changed, 60 insertions(+), 27 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts b/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts index 80d25b05968..7c6ea9d818a 100644 --- a/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts +++ b/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts @@ -10,51 +10,84 @@ import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetCon import { localize } from 'vs/nls'; import { registerAction2 } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { pickSnippet } from 'vs/workbench/contrib/snippets/browser/snippetPicker'; -import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets.contribution'; +import { ISnippetsService } from './snippets.contribution'; +const options = { + id: 'editor.action.surroundWithSnippet', + title: { + value: localize('label', 'Surround With Snippet...'), + original: 'Surround With Snippet...' + }, + precondition: ContextKeyExpr.and( + EditorContextKeys.writable, + EditorContextKeys.hasNonEmptySelection + ), + f1: true, +}; -registerAction2(class SurroundWithAction extends EditorAction2 { +class SurroundWithSnippet { + constructor( + private readonly _editor: ICodeEditor, + @ISnippetsService private readonly _snippetService: ISnippetsService, + @IClipboardService private readonly _clipboardService: IClipboardService, + @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @IInstantiationService private readonly _instaService: IInstantiationService, + ) { } - constructor() { - super({ - id: 'editor.action.surroundWithSnippet', - title: { value: localize('label', 'Surround With Snippet...'), original: 'Surround With Snippet...' }, - precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasNonEmptySelection), - f1: true - }); - } + async getSurroundableSnippets(): Promise { + if (!this._editor.hasModel()) { + return []; + } - async runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]) { + const model = this._editor.getModel(); + const { lineNumber, column } = this._editor.getPosition(); + model.tokenization.tokenizeIfCheap(lineNumber); + const languageId = model.getLanguageIdAtPosition(lineNumber, column); - const snippetService = accessor.get(ISnippetsService); - const clipboardService = accessor.get(IClipboardService); - const instaService = accessor.get(IInstantiationService); + const allSnippets = await this._snippetService.getSnippets(languageId, { includeNoPrefixSnippets: true, includeDisabledSnippets: true }); + return allSnippets.filter(snippet => snippet.usesSelection); + } + + canExecute(): boolean { + return this._contextKeyService.contextMatchesRules(options.precondition); + } - if (!editor.hasModel()) { + async run() { + if (!this.canExecute()) { return; } - const { lineNumber, column } = editor.getPosition(); - editor.getModel().tokenization.tokenizeIfCheap(lineNumber); - const languageId = editor.getModel().getLanguageIdAtPosition(lineNumber, column); - - const allSnippets = await snippetService.getSnippets(languageId, { includeNoPrefixSnippets: true, includeDisabledSnippets: true }); - const surroundSnippets = allSnippets.filter(snippet => snippet.usesSelection); - const snippet = await instaService.invokeFunction(pickSnippet, surroundSnippets); + const snippets = await this.getSurroundableSnippets(); + if (!snippets.length) { + return; + } + const snippet = await this._instaService.invokeFunction(pickSnippet, snippets); if (!snippet) { return; } - let clipboardText: string | undefined; if (snippet.needsClipboard) { - clipboardText = await clipboardService.readText(); + clipboardText = await this._clipboardService.readText(); } - SnippetController2.get(editor)?.insert(snippet.codeSnippet, { clipboardText }); + SnippetController2.get(this._editor)?.insert(snippet.codeSnippet, { clipboardText }); + } +} + +class SurroundWithSnippetEditorAction extends EditorAction2 { + constructor() { + super(options); } -}); + async runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]) { + const instaService = accessor.get(IInstantiationService); + const core = instaService.createInstance(SurroundWithSnippet, editor); + await core.run(); + } +} + +registerAction2(SurroundWithSnippetEditorAction); -- cgit v1.2.3 From 0dcd685fb03856b2f7e6f8f0c79301be987a49c7 Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Tue, 21 Jun 2022 08:35:24 +0000 Subject: =?UTF-8?q?=F0=9F=8E=81=20Add=20SurroundWithSnippet=20as=20a=20`Qu?= =?UTF-8?q?ickFix`=20code=20action?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Babak K. Shandiz --- .../snippets/browser/surroundWithSnippet.ts | 48 +++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts b/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts index 7c6ea9d818a..bff88a59966 100644 --- a/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts +++ b/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorAction2 } from 'vs/editor/browser/editorExtensions'; +import { EditorAction2, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; import { localize } from 'vs/nls'; @@ -14,6 +14,15 @@ import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/commo import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { pickSnippet } from 'vs/workbench/contrib/snippets/browser/snippetPicker'; import { ISnippetsService } from './snippets.contribution'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { ITextModel } from 'vs/editor/common/model'; +import { CodeAction, CodeActionProvider, CodeActionContext, CodeActionList } from 'vs/editor/common/languages'; +import { CodeActionKind } from 'vs/editor/contrib/codeAction/browser/types'; +import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { Range } from 'vs/editor/common/core/range'; +import { Selection } from 'vs/editor/common/core/selection'; +import { Snippet } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; const options = { id: 'editor.action.surroundWithSnippet', @@ -91,3 +100,40 @@ class SurroundWithSnippetEditorAction extends EditorAction2 { } registerAction2(SurroundWithSnippetEditorAction); + + +export class SurroundWithSnippetCodeActionProvider extends Disposable implements CodeActionProvider { + private static readonly codeAction: CodeAction = { + kind: CodeActionKind.QuickFix.value, + title: options.title.value, + command: { + id: options.id, + title: options.title.value, + }, + }; + + private core: SurroundWithSnippet; + + constructor( + editor: ICodeEditor, + @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, + @IInstantiationService instaService: IInstantiationService, + ) { + super(); + this.core = instaService.createInstance(SurroundWithSnippet, editor); + this._register(languageFeaturesService.codeActionProvider.register('*', this)); + } + + async provideCodeActions(model: ITextModel, range: Range | Selection, context: CodeActionContext, token: CancellationToken): Promise { + if (!this.core.canExecute()) { + return { actions: [], dispose: () => { } }; + } + const snippets = await this.core.getSurroundableSnippets(); + return { + actions: snippets.length ? [SurroundWithSnippetCodeActionProvider.codeAction] : [], + dispose: () => { } + }; + } +} + +registerEditorContribution(options.id, SurroundWithSnippetCodeActionProvider); -- cgit v1.2.3 From 746bc9f571bb1121ee719751258bc7680c90c5c4 Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Wed, 6 Jul 2022 14:42:12 +0000 Subject: =?UTF-8?q?=F0=9F=94=A8=20Move=20'Surround=20With=20Snippet'=20fro?= =?UTF-8?q?m=20quick=20fixes=20to=20refactorings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Babak K. Shandiz --- src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts b/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts index bff88a59966..40974a31647 100644 --- a/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts +++ b/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts @@ -104,7 +104,7 @@ registerAction2(SurroundWithSnippetEditorAction); export class SurroundWithSnippetCodeActionProvider extends Disposable implements CodeActionProvider { private static readonly codeAction: CodeAction = { - kind: CodeActionKind.QuickFix.value, + kind: CodeActionKind.Refactor.value, title: options.title.value, command: { id: options.id, -- cgit v1.2.3 From d0f9637fb4c28c32ebd8f766c5a66fa18483152c Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Thu, 7 Jul 2022 11:27:54 +0000 Subject: =?UTF-8?q?=F0=9F=94=A8=20Refactor=20"Surround=20With=20Snippet"?= =?UTF-8?q?=20editor=20contribution=20into=20workbench=20contribution?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Babak K. Shandiz --- .../snippets/browser/surroundWithSnippet.ts | 140 ++++++++++++--------- 1 file changed, 82 insertions(+), 58 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts b/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts index 40974a31647..44b03099e4c 100644 --- a/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts +++ b/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorAction2, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { EditorAction2 } from 'vs/editor/browser/editorExtensions'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; import { localize } from 'vs/nls'; import { registerAction2 } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { pickSnippet } from 'vs/workbench/contrib/snippets/browser/snippetPicker'; import { ISnippetsService } from './snippets.contribution'; @@ -23,6 +23,12 @@ import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeat import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { Snippet } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { Position } from 'vs/editor/common/core/position'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { EditorInputCapabilities } from 'vs/workbench/common/editor'; const options = { id: 'editor.action.surroundWithSnippet', @@ -37,72 +43,88 @@ const options = { f1: true, }; -class SurroundWithSnippet { - constructor( - private readonly _editor: ICodeEditor, - @ISnippetsService private readonly _snippetService: ISnippetsService, - @IClipboardService private readonly _clipboardService: IClipboardService, - @IContextKeyService private readonly _contextKeyService: IContextKeyService, - @IInstantiationService private readonly _instaService: IInstantiationService, - ) { } - - async getSurroundableSnippets(): Promise { - if (!this._editor.hasModel()) { - return []; - } +const MAX_SNIPPETS_ON_CODE_ACTIONS_MENU = 6; - const model = this._editor.getModel(); - const { lineNumber, column } = this._editor.getPosition(); - model.tokenization.tokenizeIfCheap(lineNumber); - const languageId = model.getLanguageIdAtPosition(lineNumber, column); +function makeCodeActionForSnippet(snippet: Snippet): CodeAction { + const title = localize('codeAction', "Surround With Snippet: {0}", snippet.name); + return { + title, + command: { + id: 'editor.action.insertSnippet', + title, + arguments: [{ name: snippet.name }] + }, + }; +} - const allSnippets = await this._snippetService.getSnippets(languageId, { includeNoPrefixSnippets: true, includeDisabledSnippets: true }); - return allSnippets.filter(snippet => snippet.usesSelection); +async function getSurroundableSnippets(accessor: ServicesAccessor, model: ITextModel | null, position: Position | null): Promise { + if (!model) { + return []; } - canExecute(): boolean { - return this._contextKeyService.contextMatchesRules(options.precondition); + const snippetsService = accessor.get(ISnippetsService); + + let languageId: string; + if (position) { + const { lineNumber, column } = position; + model.tokenization.tokenizeIfCheap(lineNumber); + languageId = model.getLanguageIdAtPosition(lineNumber, column); + } else { + languageId = model.getLanguageId(); } - async run() { - if (!this.canExecute()) { - return; - } + const allSnippets = await snippetsService.getSnippets(languageId, { includeNoPrefixSnippets: true, includeDisabledSnippets: true }); + return allSnippets.filter(snippet => snippet.usesSelection); +} - const snippets = await this.getSurroundableSnippets(); - if (!snippets.length) { - return; - } +function canExecute(accessor: ServicesAccessor): boolean { + const editorService = accessor.get(IEditorService); - const snippet = await this._instaService.invokeFunction(pickSnippet, snippets); - if (!snippet) { - return; - } + const editor = editorService.activeEditor; + if (!editor || editor.hasCapability(EditorInputCapabilities.Readonly)) { + return false; + } + const selections = editorService.activeTextEditorControl?.getSelections(); + return !!selections && selections.length > 0; +} - let clipboardText: string | undefined; - if (snippet.needsClipboard) { - clipboardText = await this._clipboardService.readText(); - } +async function surroundWithSnippet(accessor: ServicesAccessor, editor: ICodeEditor) { + const instaService = accessor.get(IInstantiationService); + const clipboardService = accessor.get(IClipboardService); - SnippetController2.get(this._editor)?.insert(snippet.codeSnippet, { clipboardText }); + if (!canExecute(accessor)) { + return; } + + const snippets = await getSurroundableSnippets(accessor, editor.getModel(), editor.getPosition()); + if (!snippets.length) { + return; + } + + const snippet = await instaService.invokeFunction(pickSnippet, snippets); + if (!snippet) { + return; + } + + let clipboardText: string | undefined; + if (snippet.needsClipboard) { + clipboardText = await clipboardService.readText(); + } + + SnippetController2.get(editor)?.insert(snippet.codeSnippet, { clipboardText }); } -class SurroundWithSnippetEditorAction extends EditorAction2 { + +registerAction2(class SurroundWithSnippetEditorAction extends EditorAction2 { constructor() { super(options); } async runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]) { - const instaService = accessor.get(IInstantiationService); - const core = instaService.createInstance(SurroundWithSnippet, editor); - await core.run(); + await surroundWithSnippet(accessor, editor); } -} - -registerAction2(SurroundWithSnippetEditorAction); +}); - -export class SurroundWithSnippetCodeActionProvider extends Disposable implements CodeActionProvider { +export class SurroundWithSnippetCodeActionProvider extends Disposable implements CodeActionProvider, IWorkbenchContribution { private static readonly codeAction: CodeAction = { kind: CodeActionKind.Refactor.value, title: options.title.value, @@ -112,28 +134,30 @@ export class SurroundWithSnippetCodeActionProvider extends Disposable implements }, }; - private core: SurroundWithSnippet; - constructor( - editor: ICodeEditor, @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, - @IInstantiationService instaService: IInstantiationService, + @IInstantiationService private readonly instaService: IInstantiationService, ) { super(); - this.core = instaService.createInstance(SurroundWithSnippet, editor); this._register(languageFeaturesService.codeActionProvider.register('*', this)); } async provideCodeActions(model: ITextModel, range: Range | Selection, context: CodeActionContext, token: CancellationToken): Promise { - if (!this.core.canExecute()) { + if (!this.instaService.invokeFunction(canExecute)) { + return { actions: [], dispose: () => { } }; + } + + const snippets = await this.instaService.invokeFunction(accessor => getSurroundableSnippets(accessor, model, range.getEndPosition())); + if (!snippets.length) { return { actions: [], dispose: () => { } }; } - const snippets = await this.core.getSurroundableSnippets(); return { - actions: snippets.length ? [SurroundWithSnippetCodeActionProvider.codeAction] : [], + actions: snippets.length <= MAX_SNIPPETS_ON_CODE_ACTIONS_MENU + ? snippets.map(x => makeCodeActionForSnippet(x)) + : [SurroundWithSnippetCodeActionProvider.codeAction], dispose: () => { } }; } } -registerEditorContribution(options.id, SurroundWithSnippetCodeActionProvider); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(SurroundWithSnippetCodeActionProvider, LifecyclePhase.Restored); -- cgit v1.2.3 From f0f5152fce6668d6d742613dfae45c1895f685c9 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 13 Jul 2022 02:50:22 -0700 Subject: Remove accidental log --- src/vs/workbench/contrib/terminal/browser/terminalInstance.ts | 1 - 1 file changed, 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 1f8a4970e50..761739d44a9 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -1236,7 +1236,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } private _setShellIntegrationContextKey(): void { - console.log('set', this.xterm?.shellIntegration.status === ShellIntegrationStatus.VSCode); if (this.xterm) { this._terminalShellIntegrationEnabledContextKey.set(this.xterm.shellIntegration.status === ShellIntegrationStatus.VSCode); } -- cgit v1.2.3 From b7f71bd4f63809665ff55ddc35f55b77617270cd Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 13 Jul 2022 14:46:48 +0200 Subject: Closing dialog does not cancel (Manjaro Linux) (fix #154719) (#155049) * Closing dialog does not cancel (Manjaro Linux) (fix #154719) * set `cancelId` --- .../contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts | 9 ++++++--- src/vs/workbench/contrib/format/browser/formatActionsNone.ts | 5 +++-- 2 files changed, 9 insertions(+), 5 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts index bad36b7ccad..57a34f2c7d0 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEdit.contribution.ts @@ -122,11 +122,14 @@ class BulkEditPreviewContribution { const choice = await this._dialogService.show( Severity.Info, localize('overlap', "Another refactoring is being previewed."), - [localize('cancel', "Cancel"), localize('continue', "Continue")], - { detail: localize('detail', "Press 'Continue' to discard the previous refactoring and continue with the current refactoring.") } + [localize('continue', "Continue"), localize('cancel', "Cancel")], + { + detail: localize('detail', "Press 'Continue' to discard the previous refactoring and continue with the current refactoring."), + cancelId: 1 + } ); - if (choice.choice === 0) { + if (choice.choice === 1) { // this refactoring is being cancelled return []; } diff --git a/src/vs/workbench/contrib/format/browser/formatActionsNone.ts b/src/vs/workbench/contrib/format/browser/formatActionsNone.ts index b32e1dc76fb..0177b09bf8f 100644 --- a/src/vs/workbench/contrib/format/browser/formatActionsNone.ts +++ b/src/vs/workbench/contrib/format/browser/formatActionsNone.ts @@ -68,9 +68,10 @@ registerEditorAction(class FormatDocumentMultipleAction extends EditorAction { const res = await dialogService.show( Severity.Info, message, - [nls.localize('cancel', "Cancel"), nls.localize('install.formatter', "Install Formatter...")] + [nls.localize('install.formatter', "Install Formatter..."), nls.localize('cancel', "Cancel")], + { cancelId: 1 } ); - if (res.choice === 1) { + if (res.choice !== 1) { showExtensionQuery(paneCompositeService, `category:formatters ${langName}`); } } -- cgit v1.2.3 From 2c992c7b7da9338d3914d980337b04505fdc3ae8 Mon Sep 17 00:00:00 2001 From: Gavin McQuistin Date: Wed, 13 Jul 2022 17:01:25 +0100 Subject: Fix typo in files.contribution.ts (#155016) --- src/vs/workbench/contrib/files/browser/files.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index 1ebf70ce257..79e57d4312d 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -404,7 +404,7 @@ configurationRegistry.registerConfiguration({ }, 'explorer.expandSingleFolderWorkspaces': { 'type': 'boolean', - 'description': nls.localize('expandSingleFolderWorkspaces', "Controls whether the explorer should expand multi-root workspaces containing only one folder during initilization"), + 'description': nls.localize('expandSingleFolderWorkspaces', "Controls whether the explorer should expand multi-root workspaces containing only one folder during initialization"), 'default': true }, 'explorer.sortOrder': { -- cgit v1.2.3 From a03ebcaa0d915e1cc0c795cc3b4c58452f6587a3 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Wed, 13 Jul 2022 12:19:15 -0400 Subject: Fix #150836 (#155081) --- .../workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts index 497dfb1165c..8467fc4ed88 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts @@ -1193,7 +1193,7 @@ export class GettingStartedPage extends EditorPane { if (isCommand) { const keybindingLabel = this.getKeybindingLabel(command); if (keybindingLabel) { - container.appendChild($('span.shortcut-message', {}, 'Tip: Use keyboard shortcut ', $('span.keybinding', {}, keybindingLabel))); + container.appendChild($('span.shortcut-message', {}, localize('gettingStarted.keyboardTip', 'Tip: Use keyboard shortcut '), $('span.keybinding', {}, keybindingLabel))); } } -- cgit v1.2.3 From ff31a8c6fd9bf259de64ffda31420e9eaa02336b Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 13 Jul 2022 10:17:01 -0700 Subject: disable decorations (#154430) --- .../contrib/terminal/browser/media/terminal.css | 4 + .../contrib/terminal/browser/terminalView.ts | 33 +++++-- .../terminal/browser/xterm/decorationAddon.ts | 110 +++++++++++++++------ .../terminal/common/terminalConfiguration.ts | 19 ++-- .../test/browser/xterm/decorationAddon.test.ts | 9 +- 5 files changed, 128 insertions(+), 47 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index 17933a5776d..16275cb5114 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -14,6 +14,10 @@ position: relative; } +.terminal-command-decoration.hide { + visibility: hidden; +} + .monaco-workbench .pane-body.integrated-terminal .terminal-outer-container, .monaco-workbench .pane-body.integrated-terminal .terminal-groups-container, .monaco-workbench .pane-body.integrated-terminal .terminal-group, diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index 9dc9017d20f..bb3b8225a5f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -46,6 +46,7 @@ import { getTerminalActionBarArgs } from 'vs/workbench/contrib/terminal/browser/ import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { getShellIntegrationTooltip } from 'vs/workbench/contrib/terminal/browser/terminalTooltip'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; export class TerminalViewPane extends ViewPane { private _actions: IAction[] | undefined; @@ -65,7 +66,7 @@ export class TerminalViewPane extends ViewPane { @IKeybindingService keybindingService: IKeybindingService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IViewDescriptorService viewDescriptorService: IViewDescriptorService, - @IConfigurationService configurationService: IConfigurationService, + @IConfigurationService private readonly _configurationService: IConfigurationService, @IContextMenuService private readonly _contextMenuService: IContextMenuService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @ITerminalService private readonly _terminalService: ITerminalService, @@ -81,7 +82,7 @@ export class TerminalViewPane extends ViewPane { @ITerminalProfileResolverService private readonly _terminalProfileResolverService: ITerminalProfileResolverService, @IThemeService private readonly _themeService: IThemeService ) { - super(options, keybindingService, _contextMenuService, configurationService, _contextKeyService, viewDescriptorService, _instantiationService, openerService, themeService, telemetryService); + super(options, keybindingService, _contextMenuService, _configurationService, _contextKeyService, viewDescriptorService, _instantiationService, openerService, themeService, telemetryService); this._register(this._terminalService.onDidRegisterProcessSupport(() => { if (this._actions) { for (const action of this._actions) { @@ -111,20 +112,34 @@ export class TerminalViewPane extends ViewPane { this._terminalTabbedView?.rerenderTabs(); } })); - configurationService.onDidChangeConfiguration(e => { - if ((e.affectsConfiguration(TerminalSettingId.ShellIntegrationDecorationsEnabled) && !configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationsEnabled)) || - (e.affectsConfiguration(TerminalSettingId.ShellIntegrationEnabled) && !configurationService.getValue(TerminalSettingId.ShellIntegrationEnabled))) { - this._parentDomElement?.classList.remove('shell-integration'); - } else if (configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationsEnabled) && configurationService.getValue(TerminalSettingId.ShellIntegrationEnabled)) { - this._parentDomElement?.classList.add('shell-integration'); + _configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(TerminalSettingId.ShellIntegrationDecorationsEnabled) || e.affectsConfiguration(TerminalSettingId.ShellIntegrationEnabled)) { + this._updateForShellIntegration(); } }); + this._register(this._terminalService.onDidCreateInstance((i) => { + i.capabilities.onDidAddCapability(c => { + if (c === TerminalCapability.CommandDetection && !this._gutterDecorationsEnabled()) { + this._parentDomElement?.classList.add('shell-integration'); + } + }); + })); + this._updateForShellIntegration(); + } - if (configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationsEnabled) && configurationService.getValue(TerminalSettingId.ShellIntegrationEnabled)) { + private _updateForShellIntegration() { + if (this._gutterDecorationsEnabled()) { this._parentDomElement?.classList.add('shell-integration'); + } else { + this._parentDomElement?.classList.remove('shell-integration'); } } + private _gutterDecorationsEnabled(): boolean { + const decorationsEnabled = this._configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationsEnabled); + return (decorationsEnabled === 'both' || decorationsEnabled === 'gutter') && this._configurationService.getValue(TerminalSettingId.ShellIntegrationEnabled); + } + override renderBody(container: HTMLElement): void { super.renderBody(container); diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts index 45b820a935c..31d764e8de2 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts @@ -28,13 +28,14 @@ import { IGenericMarkProperties } from 'vs/platform/terminal/common/terminalProc const enum DecorationSelector { CommandDecoration = 'terminal-command-decoration', + Hide = 'hide', ErrorColor = 'error', DefaultColor = 'default-color', Default = 'default', Codicon = 'codicon', XtermDecoration = 'xterm-decoration', - OverviewRuler = 'xterm-decoration-overview-ruler', - GenericMarkerIcon = 'codicon-circle-small-filled' + GenericMarkerIcon = 'codicon-circle-small-filled', + OverviewRuler = '.xterm-decoration-overview-ruler' } const enum DecorationStyles { @@ -51,6 +52,8 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { private _contextMenuVisible: boolean = false; private _decorations: Map = new Map(); private _placeholderDecoration: IDecoration | undefined; + private _showGutterDecorations?: boolean; + private _showOverviewRulerDecorations?: boolean; private readonly _onDidRequestRunCommand = this._register(new Emitter<{ command: ITerminalCommand; copyAsHtml?: boolean }>()); readonly onDidRequestRunCommand = this._onDidRequestRunCommand.event; @@ -79,9 +82,67 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { this.refreshLayouts(); } else if (e.affectsConfiguration('workbench.colorCustomizations')) { this._refreshStyles(true); + } else if (e.affectsConfiguration(TerminalSettingId.ShellIntegrationDecorationsEnabled)) { + if (this._commandDetectionListeners) { + dispose(this._commandDetectionListeners); + this._commandDetectionListeners = undefined; + } + this._updateDecorationVisibility(); } }); this._themeService.onDidColorThemeChange(() => this._refreshStyles(true)); + this._updateDecorationVisibility(); + this._register(this._capabilities.onDidAddCapability(c => { + if (c === TerminalCapability.CommandDetection) { + this._addCommandDetectionListeners(); + } + })); + this._register(this._capabilities.onDidRemoveCapability(c => { + if (c === TerminalCapability.CommandDetection) { + if (this._commandDetectionListeners) { + dispose(this._commandDetectionListeners); + this._commandDetectionListeners = undefined; + } + } + })); + } + + private _updateDecorationVisibility(): void { + const showDecorations = this._configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationsEnabled); + this._showGutterDecorations = (showDecorations === 'both' || showDecorations === 'gutter'); + this._showOverviewRulerDecorations = (showDecorations === 'both' || showDecorations === 'overviewRuler'); + this._disposeAllDecorations(); + if (this._showGutterDecorations || this._showOverviewRulerDecorations) { + this._attachToCommandCapability(); + this._updateGutterDecorationVisibility(); + } + const currentCommand = this._capabilities.get(TerminalCapability.CommandDetection)?.executingCommandObject; + if (currentCommand) { + this.registerCommandDecoration(currentCommand, true); + } + } + + private _disposeAllDecorations(): void { + this._placeholderDecoration?.dispose(); + for (const value of this._decorations.values()) { + value.decoration.dispose(); + dispose(value.disposables); + } + } + + private _updateGutterDecorationVisibility(): void { + const commandDecorationElements = document.querySelectorAll(DecorationSelector.CommandDecoration); + for (const commandDecorationElement of commandDecorationElements) { + this._updateCommandDecorationVisibility(commandDecorationElement); + } + } + + private _updateCommandDecorationVisibility(commandDecorationElement: Element): void { + if (this._showGutterDecorations) { + commandDecorationElement.classList.remove(DecorationSelector.Hide); + } else { + commandDecorationElement.classList.add(DecorationSelector.Hide); + } } public refreshLayouts(): void { @@ -128,31 +189,14 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { public clearDecorations(): void { this._placeholderDecoration?.marker.dispose(); this._clearPlaceholder(); - for (const value of this._decorations.values()) { - value.decoration.dispose(); - dispose(value.disposables); - } + this._disposeAllDecorations(); this._decorations.clear(); } private _attachToCommandCapability(): void { if (this._capabilities.has(TerminalCapability.CommandDetection)) { this._addCommandDetectionListeners(); - } else { - this._register(this._capabilities.onDidAddCapability(c => { - if (c === TerminalCapability.CommandDetection) { - this._addCommandDetectionListeners(); - } - })); } - this._register(this._capabilities.onDidRemoveCapability(c => { - if (c === TerminalCapability.CommandDetection) { - if (this._commandDetectionListeners) { - dispose(this._commandDetectionListeners); - this._commandDetectionListeners = undefined; - } - } - })); } private _addCommandDetectionListeners(): void { @@ -204,13 +248,12 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { } registerCommandDecoration(command: ITerminalCommand, beforeCommandExecution?: boolean): IDecoration | undefined { - if (!this._terminal || (beforeCommandExecution && command.genericMarkProperties)) { + if (!this._terminal || (beforeCommandExecution && command.genericMarkProperties) || (!this._showGutterDecorations && !this._showOverviewRulerDecorations)) { return undefined; } if (!command.marker) { throw new Error(`cannot add a decoration for a command ${JSON.stringify(command)} with no marker`); } - this._clearPlaceholder(); let color = command.exitCode === undefined ? defaultColor : command.exitCode ? errorColor : successColor; if (color && typeof color !== 'string') { @@ -220,9 +263,9 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { } const decoration = this._terminal.registerDecoration({ marker: command.marker, - overviewRulerOptions: beforeCommandExecution + overviewRulerOptions: this._showOverviewRulerDecorations ? (beforeCommandExecution ? { color, position: 'left' } - : { color, position: command.exitCode ? 'right' : 'left' } + : { color, position: command.exitCode ? 'right' : 'left' }) : undefined }); if (!decoration) { return undefined; @@ -287,20 +330,25 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { element.classList.remove(classes); } element.classList.add(DecorationSelector.CommandDecoration, DecorationSelector.Codicon, DecorationSelector.XtermDecoration); + if (genericMarkProperties) { element.classList.add(DecorationSelector.DefaultColor, DecorationSelector.GenericMarkerIcon); if (!genericMarkProperties.hoverMessage) { //disable the mouse pointer element.classList.add(DecorationSelector.Default); } - } else if (exitCode === undefined) { - element.classList.add(DecorationSelector.DefaultColor, DecorationSelector.Default); - element.classList.add(`codicon-${this._configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationIcon)}`); - } else if (exitCode) { - element.classList.add(DecorationSelector.ErrorColor); - element.classList.add(`codicon-${this._configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationIconError)}`); } else { - element.classList.add(`codicon-${this._configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationIconSuccess)}`); + // command decoration + this._updateCommandDecorationVisibility(element); + if (exitCode === undefined) { + element.classList.add(DecorationSelector.DefaultColor, DecorationSelector.Default); + element.classList.add(`codicon-${this._configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationIcon)}`); + } else if (exitCode) { + element.classList.add(DecorationSelector.ErrorColor); + element.classList.add(`codicon-${this._configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationIconError)}`); + } else { + element.classList.add(`codicon-${this._configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationIconSuccess)}`); + } } } diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index 3c5513e7c90..c5b6caee4af 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -117,17 +117,17 @@ const terminalConfiguration: IConfigurationNode = { [TerminalSettingId.ShellIntegrationDecorationIconSuccess]: { type: 'string', default: 'primitive-dot', - markdownDescription: localize('terminal.integrated.shellIntegration.decorationIconSuccess', "Controls the icon that will be used for each command in terminals with shell integration enabled that do not have an associated exit code. Set to {0} to hide the icon or disable decorations with {1}.", '`\'\'`', '`#terminal.integrated.shellIntegration.decorationsEnabled#`') + markdownDescription: localize('terminal.integrated.shellIntegration.decorationIconSuccess', "Controls the icon that will be used for each command in terminals with shell integration enabled that do not have an associated exit code. Set to {0} to hide the icon or disable decorations with {1}.", '`\"\"`', '`#terminal.integrated.shellIntegration.decorationsEnabled#`') }, [TerminalSettingId.ShellIntegrationDecorationIconError]: { type: 'string', default: 'error-small', - markdownDescription: localize('terminal.integrated.shellIntegration.decorationIconError', "Controls the icon that will be used for each command in terminals with shell integration enabled that do have an associated exit code. Set to {0} to hide the icon or disable decorations with {1}.", '`\'\'`', '`#terminal.integrated.shellIntegration.decorationsEnabled#`') + markdownDescription: localize('terminal.integrated.shellIntegration.decorationIconError', "Controls the icon that will be used for each command in terminals with shell integration enabled that do have an associated exit code. Set to {0} to hide the icon or disable decorations with {1}.", '`\"\"`', '`#terminal.integrated.shellIntegration.decorationsEnabled#`') }, [TerminalSettingId.ShellIntegrationDecorationIcon]: { type: 'string', default: 'circle-outline', - markdownDescription: localize('terminal.integrated.shellIntegration.decorationIcon', "Controls the icon that will be used for skipped/empty commands. Set to {0} to hide the icon or disable decorations with {1}.", '`\'\'`', '`#terminal.integrated.shellIntegration.decorationsEnabled#`') + markdownDescription: localize('terminal.integrated.shellIntegration.decorationIcon', "Controls the icon that will be used for skipped/empty commands. Set to {0} to hide the icon or disable decorations with {1}.", '`\"\"`', '`#terminal.integrated.shellIntegration.decorationsEnabled#`') }, [TerminalSettingId.TabsFocusMode]: { type: 'string', @@ -546,15 +546,22 @@ const terminalConfiguration: IConfigurationNode = { }, [TerminalSettingId.ShellIntegrationEnabled]: { restricted: true, - markdownDescription: localize('terminal.integrated.shellIntegration.enabled', "Enable features like enhanced command tracking and current working directory detection. \n\nShell integration works by injecting the shell with a startup script. The script gives VS Code insight into what is happening within the terminal.\n\nSupported shells:\n\n- Linux/macOS: bash, pwsh, zsh\n - Windows: pwsh\n\nThis setting applies only when terminals are created, so you will need to restart your terminals for it to take effect.\n\n Note that the script injection may not work if you have custom arguments defined in the terminal profile, a [complex bash `PROMPT_COMMAND`](https://code.visualstudio.com/docs/editor/integrated-terminal#_complex-bash-promptcommand), or other unsupported setup."), + markdownDescription: localize('terminal.integrated.shellIntegration.enabled', "Determines whether or not shell integration is auto-injected to support features like enhanced command tracking and current working directory detection. \n\nShell integration works by injecting the shell with a startup script. The script gives VS Code insight into what is happening within the terminal.\n\nSupported shells:\n\n- Linux/macOS: bash, pwsh, zsh\n - Windows: pwsh\n\nThis setting applies only when terminals are created, so you will need to restart your terminals for it to take effect.\n\n Note that the script injection may not work if you have custom arguments defined in the terminal profile, a [complex bash `PROMPT_COMMAND`](https://code.visualstudio.com/docs/editor/integrated-terminal#_complex-bash-promptcommand), or other unsupported setup. To disable decorations, see {0}", '`#terminal.integrated.shellIntegrations.decorationsEnabled#`'), type: 'boolean', default: true }, [TerminalSettingId.ShellIntegrationDecorationsEnabled]: { restricted: true, markdownDescription: localize('terminal.integrated.shellIntegration.decorationsEnabled', "When shell integration is enabled, adds a decoration for each command."), - type: 'boolean', - default: true + type: 'string', + enum: ['both', 'gutter', 'overviewRuler', 'never'], + enumDescriptions: [ + localize('terminal.integrated.shellIntegration.decorationsEnabled.both', "Show decorations in the gutter (left) and overview ruler (right)"), + localize('terminal.integrated.shellIntegration.decorationsEnabled.gutter', "Show gutter decorations to the left of the terminal"), + localize('terminal.integrated.shellIntegration.decorationsEnabled.overviewRuler', "Show overview ruler decorations to the right of the terminal"), + localize('terminal.integrated.shellIntegration.decorationsEnabled.never', "Do not show decorations"), + ], + default: 'both' }, [TerminalSettingId.ShellIntegrationCommandHistory]: { restricted: true, diff --git a/src/vs/workbench/contrib/terminal/test/browser/xterm/decorationAddon.test.ts b/src/vs/workbench/contrib/terminal/test/browser/xterm/decorationAddon.test.ts index 3ea312e1e1e..ddcbd66e3b2 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/xterm/decorationAddon.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/xterm/decorationAddon.test.ts @@ -37,7 +37,14 @@ suite('DecorationAddon', () => { const instantiationService = new TestInstantiationService(); const configurationService = new TestConfigurationService({ workbench: { - hover: { delay: 5 } + hover: { delay: 5 }, + }, + terminal: { + integrated: { + shellIntegration: { + decorationsEnabled: 'both' + } + } } }); instantiationService.stub(IThemeService, new TestThemeService()); -- cgit v1.2.3 From 9bed8738a70050510a9fa1e5e55d3d1f4b6c451b Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Wed, 13 Jul 2022 13:19:33 -0400 Subject: Fix #153860 (#155086) --- src/vs/workbench/contrib/files/browser/fileCommands.ts | 9 ++++++--- .../welcomeViews/common/newFile.contribution.ts | 18 ++++++++++++++++-- 2 files changed, 22 insertions(+), 5 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index 67ab692b603..0cea70cc910 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -629,7 +629,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ { isOptional: true, name: 'New Untitled File args', - description: 'The editor view type and language ID if known', + description: 'The editor view type, language ID, or resource path if known', schema: { 'type': 'object', 'properties': { @@ -638,17 +638,20 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ }, 'languageId': { 'type': 'string' + }, + 'path': { + 'type': 'string' } } } } ] }, - handler: async (accessor, args?: { languageId?: string; viewType?: string }) => { + handler: async (accessor, args?: { languageId?: string; viewType?: string; path?: string }) => { const editorService = accessor.get(IEditorService); await editorService.openEditor({ - resource: undefined, + resource: args?.path ? URI.from({ scheme: Schemas.untitled, path: `Untitled-${args.path}` }) : undefined, options: { override: args?.viewType, pinned: true diff --git a/src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts b/src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts index 8b87c52e229..02106a3341c 100644 --- a/src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts +++ b/src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts @@ -45,7 +45,7 @@ registerAction2(class extends Action2 { } }); -type NewFileItem = { commandID: string; title: string; from: string; group: string }; +type NewFileItem = { commandID: string; title: string; from: string; group: string; commandArgs?: any }; class NewFileTemplatesManager extends Disposable { static Instance: NewFileTemplatesManager | undefined; @@ -162,12 +162,26 @@ class NewFileTemplatesManager extends Disposable { disposables.add(this.menu.onDidChange(() => refreshQp(this.allEntries()))); + disposables.add(qp.onDidChangeValue((val: string) => { + if (val === '') { + return; + } + const currentTextEntry: NewFileItem = { + commandID: 'workbench.action.files.newUntitledFile', + commandArgs: { languageId: undefined, viewType: undefined, path: val }, + title: localize('miNewFileWithName', "New File ({0})", val), + group: 'file', + from: builtInSource, + }; + refreshQp([currentTextEntry, ...entries]); + })); + disposables.add(qp.onDidAccept(async e => { const selected = qp.selectedItems[0] as (IQuickPickItem & NewFileItem); resolveResult(!!selected); qp.hide(); - if (selected) { await this.commandService.executeCommand(selected.commandID); } + if (selected) { await this.commandService.executeCommand(selected.commandID, selected.commandArgs); } })); disposables.add(qp.onDidHide(() => { -- cgit v1.2.3 From b5ffc79cc99314fae84842d5260b2ae01e555ee1 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Wed, 13 Jul 2022 10:20:38 -0700 Subject: Add telemetry tracking edit session feature usage (#155084) --- .../browser/editSessions.contribution.ts | 32 ++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts index 12f620df779..e292a79c084 100644 --- a/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts +++ b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts @@ -85,6 +85,12 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo super(); if (this.environmentService.editSessionId !== undefined) { + type ResumeEvent = {}; + type ResumeClassification = { + owner: 'joyceerhl'; comment: 'Reporting when an action is resumed from an edit session identifier.'; + }; + this.telemetryService.publicLog2('editSessions.continue.resume'); + void this.resumeEditSession(this.environmentService.editSessionId).finally(() => this.environmentService.editSessionId = undefined); } @@ -148,6 +154,12 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo } async run(accessor: ServicesAccessor, workspaceUri: URI | undefined): Promise { + type ContinueEditSessionEvent = {}; + type ContinueEditSessionClassification = { + owner: 'joyceerhl'; comment: 'Reporting when the continue edit session action is run.'; + }; + that.telemetryService.publicLog2('editSessions.continue.store'); + let uri = workspaceUri ?? await that.pickContinueEditSessionDestination(); if (uri === undefined) { return; } @@ -187,7 +199,15 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo await that.progressService.withProgress({ location: ProgressLocation.Notification, title: localize('resuming edit session', 'Resuming edit session...') - }, async () => await that.resumeEditSession()); + }, async () => { + type ResumeEvent = {}; + type ResumeClassification = { + owner: 'joyceerhl'; comment: 'Reporting when the resume edit session action is invoked.'; + }; + that.telemetryService.publicLog2('editSessions.resume'); + + await that.resumeEditSession(); + }); } })); } @@ -208,7 +228,15 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo await that.progressService.withProgress({ location: ProgressLocation.Notification, title: localize('storing edit session', 'Storing edit session...') - }, async () => await that.storeEditSession(true)); + }, async () => { + type StoreEvent = {}; + type StoreClassification = { + owner: 'joyceerhl'; comment: 'Reporting when the store edit session action is invoked.'; + }; + that.telemetryService.publicLog2('editSessions.store'); + + await that.storeEditSession(true); + }); } })); } -- cgit v1.2.3 From 6ce1ad6cfec964988e8e777523ef9c988ea7f188 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 13 Jul 2022 10:25:25 -0700 Subject: allow more args in run task (#154854) --- .../contrib/tasks/browser/abstractTaskService.ts | 107 ++++++++++++++++----- .../contrib/tasks/browser/taskQuickPick.ts | 15 +-- .../contrib/tasks/electron-sandbox/taskService.ts | 7 +- 3 files changed, 98 insertions(+), 31 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 93cbe879116..89ec1896b26 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -78,7 +78,7 @@ import { ITextEditorSelection, TextEditorSelectionRevealType } from 'vs/platform import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { IViewsService, IViewDescriptorService } from 'vs/workbench/common/views'; -import { isWorkspaceFolder, ITaskQuickPickEntry, QUICKOPEN_DETAIL_CONFIG, TaskQuickPick, QUICKOPEN_SKIP_CONFIG, configureTaskIcon } from 'vs/workbench/contrib/tasks/browser/taskQuickPick'; +import { isWorkspaceFolder, ITaskQuickPickEntry, QUICKOPEN_DETAIL_CONFIG, TaskQuickPick, QUICKOPEN_SKIP_CONFIG, configureTaskIcon, ITaskTwoLevelQuickPickEntry } from 'vs/workbench/contrib/tasks/browser/taskQuickPick'; import { ILogService } from 'vs/platform/log/common/log'; import { once } from 'vs/base/common/functional'; import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService'; @@ -347,7 +347,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return this.inTerminal(); } - private _registerCommands(): void { + private async _registerCommands(): Promise { CommandsRegistry.registerCommand({ id: 'workbench.action.tasks.runTask', handler: async (accessor, arg) => { @@ -359,8 +359,30 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer description: 'Run Task', args: [{ name: 'args', + isOptional: true, + description: nls.localize('runTask.arg', "Filters the tasks shown in the quickpick"), schema: { - 'type': 'string', + anyOf: [ + { + type: 'string', + description: nls.localize('runTask.label', "The task's label or a term to filter by") + }, + { + type: 'object', + properties: { + type: { + type: 'string', + description: nls.localize('runTask.type', "The contributed task type"), + enum: Array.from(this._providerTypes.values()).map(provider => provider) + }, + taskName: { + type: 'string', + description: nls.localize('runTask.taskName', "The task's label or a term to filter by"), + enum: await this.tasks().then((tasks) => tasks.map(t => t._label)) + } + } + } + ] } }] } @@ -2521,11 +2543,11 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return entries; } - private async _showTwoLevelQuickPick(placeHolder: string, defaultEntry?: ITaskQuickPickEntry) { - return TaskQuickPick.show(this, this._configurationService, this._quickInputService, this._notificationService, this._dialogService, this._themeService, placeHolder, defaultEntry); + private async _showTwoLevelQuickPick(placeHolder: string, defaultEntry?: ITaskQuickPickEntry, filter?: string) { + return TaskQuickPick.show(this, this._configurationService, this._quickInputService, this._notificationService, this._dialogService, this._themeService, placeHolder, defaultEntry, filter); } - private async _showQuickPick(tasks: Promise | Task[], placeHolder: string, defaultEntry?: ITaskQuickPickEntry, group: boolean = false, sort: boolean = false, selectedEntry?: ITaskQuickPickEntry, additionalEntries?: ITaskQuickPickEntry[]): Promise { + private async _showQuickPick(tasks: Promise | Task[], placeHolder: string, defaultEntry?: ITaskQuickPickEntry, group: boolean = false, sort: boolean = false, selectedEntry?: ITaskQuickPickEntry, additionalEntries?: ITaskQuickPickEntry[], filter?: string): Promise { const tokenSource = new CancellationTokenSource(); const cancellationToken: CancellationToken = tokenSource.token; const createEntries = new Promise[]>((resolve) => { @@ -2564,7 +2586,6 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer const picker: IQuickPick = this._quickInputService.createQuickPick(); picker.placeholder = placeHolder; picker.matchOnDescription = true; - picker.onDidTriggerItemButton(context => { const task = context.item.task; this._quickInputService.cancel(); @@ -2580,7 +2601,9 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer picker.items = entries; }); picker.show(); - + if (filter) { + picker.value = filter; + } return new Promise(resolve => { this._register(picker.onDidAccept(async () => { let selection = picker.selectedItems ? picker.selectedItems[0] : undefined; @@ -2654,12 +2677,20 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer })) === true; } - private _runTaskCommand(arg?: any): void { + private async _runTaskCommand(filter?: { type?: string; taskName?: string } | string): Promise { if (!this._canRunCommand()) { return; } - const identifier = this._getTaskIdentifier(arg); - if (identifier !== undefined) { + + let typeFilter: boolean = false; + if (filter && typeof filter !== 'string') { + // name takes precedence + typeFilter = !filter?.taskName && !!filter?.type; + filter = filter?.taskName || filter?.type; + } + + const taskIdentifier: KeyedTaskIdentifier | undefined | string = this._getTaskIdentifier(filter); + if (taskIdentifier) { this._getGroupedTasks().then(async (grouped) => { const resolver = this._createResolver(grouped); const folderURIs: (URI | string)[] = this._contextService.getWorkspace().folders.map(folder => folder.uri); @@ -2668,7 +2699,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } folderURIs.push(USER_TASKS_GROUP_KEY); for (const uri of folderURIs) { - const task = await resolver.resolve(uri, identifier); + const task = await resolver.resolve(uri, taskIdentifier); if (task) { this.run(task).then(undefined, reason => { // eat the error, it has already been surfaced to the user and we don't care about it here @@ -2676,7 +2707,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return; } } - this._doRunTaskCommand(grouped.all()); + this._doRunTaskCommand(grouped.all(), typeof taskIdentifier === 'string' ? taskIdentifier : undefined, typeFilter); }, () => { this._doRunTaskCommand(); }); @@ -2716,7 +2747,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return { tasks, grouped }; } - private _doRunTaskCommand(tasks?: Task[]): void { + private _doRunTaskCommand(tasks?: Task[], filter?: string, typeFilter?: boolean): void { const pickThen = (task: Task | undefined | null) => { if (task === undefined) { return; @@ -2732,28 +2763,58 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer const placeholder = nls.localize('TaskService.pickRunTask', 'Select the task to run'); - this._showIgnoredFoldersMessage().then(() => { + this._showIgnoredFoldersMessage().then(async () => { if (this._configurationService.getValue(USE_SLOW_PICKER)) { let taskResult: { tasks: Promise; grouped: Promise } | undefined = undefined; if (!tasks) { taskResult = this._tasksAndGroupedTasks(); } + if (filter && typeFilter) { + const picker: IQuickPick = this._quickInputService.createQuickPick(); + picker.placeholder = nls.localize('TaskService.pickRunTask', 'Select the task to run'); + picker.matchOnDescription = true; + picker.ignoreFocusOut = false; + const taskQuickPick = new TaskQuickPick(this, this._configurationService, this._quickInputService, this._notificationService, this._themeService, this._dialogService); + const result = await taskQuickPick.doPickerSecondLevel(picker, filter); + if (result?.task) { + pickThen(result.task as Task); + taskQuickPick.dispose(); + } + return; + } this._showQuickPick(tasks ? tasks : taskResult!.tasks, placeholder, { label: '$(plus) ' + nls.localize('TaskService.noEntryToRun', 'Configure a Task'), task: null }, - true). + true, false, undefined, undefined, typeof filter === 'string' ? filter : undefined). then((entry) => { return pickThen(entry ? entry.task : undefined); }); } else { - this._showTwoLevelQuickPick(placeholder, - { - label: '$(plus) ' + nls.localize('TaskService.noEntryToRun', 'Configure a Task'), - task: null - }). - then(pickThen); + if (filter && typeFilter) { + const picker: IQuickPick = this._quickInputService.createQuickPick(); + picker.placeholder = nls.localize('TaskService.pickRunTask', 'Select the task to run'); + picker.matchOnDescription = true; + picker.ignoreFocusOut = false; + const taskQuickPick = new TaskQuickPick(this, this._configurationService, this._quickInputService, this._notificationService, this._themeService, this._dialogService); + const result = await taskQuickPick.doPickerSecondLevel(picker, filter); + if (result?.task) { + pickThen(result.task as Task); + picker.dispose(); + taskQuickPick.dispose(); + return; + } else { + return; + } + } else { + this._showTwoLevelQuickPick(placeholder, + { + label: '$(plus) ' + nls.localize('TaskService.noEntryToRun', 'Configure a Task'), + task: null + }, typeof filter === 'string' ? filter : undefined). + then(pickThen); + } } }); } @@ -3055,7 +3116,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } } - private _getTaskIdentifier(arg?: any): string | KeyedTaskIdentifier | undefined { + private _getTaskIdentifier(arg?: string | ITaskIdentifier): string | KeyedTaskIdentifier | undefined { let result: string | KeyedTaskIdentifier | undefined = undefined; if (Types.isString(arg)) { result = arg; diff --git a/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts b/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts index a2cc123e381..bf48327fe5d 100644 --- a/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts +++ b/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts @@ -218,12 +218,15 @@ export class TaskQuickPick extends Disposable { return undefined; } - public async show(placeHolder: string, defaultEntry?: ITaskQuickPickEntry, startAtType?: string): Promise { + public async show(placeHolder: string, defaultEntry?: ITaskQuickPickEntry, startAtType?: string, filter?: string): Promise { const picker: IQuickPick = this._quickInputService.createQuickPick(); picker.placeholder = placeHolder; picker.matchOnDescription = true; picker.ignoreFocusOut = false; picker.show(); + if (filter) { + picker.value = filter; + } picker.onDidTriggerItemButton(async (context) => { const task = context.item.task; @@ -268,7 +271,7 @@ export class TaskQuickPick extends Disposable { do { if (Types.isString(firstLevelTask)) { // Proceed to second level of quick pick - const selectedEntry = await this._doPickerSecondLevel(picker, firstLevelTask); + const selectedEntry = await this.doPickerSecondLevel(picker, firstLevelTask); if (selectedEntry && !selectedEntry.settingType && selectedEntry.task === null) { // The user has chosen to go back to the first level firstLevelTask = await this._doPickerFirstLevel(picker, (await this.getTopLevelEntries(defaultEntry)).entries); @@ -302,7 +305,7 @@ export class TaskQuickPick extends Disposable { return firstLevelPickerResult?.task; } - private async _doPickerSecondLevel(picker: IQuickPick, type: string) { + public async doPickerSecondLevel(picker: IQuickPick, type: string) { picker.busy = true; if (type === SHOW_ALL) { const items = (await this._taskService.tasks()).filter(t => !t.configurationProperties.hide).sort((a, b) => this._sorter.compare(a, b)).map(task => this._createTaskEntry(task)); @@ -312,13 +315,13 @@ export class TaskQuickPick extends Disposable { picker.value = ''; picker.items = await this._getEntriesForProvider(type); } + picker.show(); picker.busy = false; const secondLevelPickerResult = await new Promise(resolve => { Event.once(picker.onDidAccept)(async () => { resolve(picker.selectedItems ? picker.selectedItems[0] : undefined); }); }); - return secondLevelPickerResult; } @@ -398,8 +401,8 @@ export class TaskQuickPick extends Disposable { static async show(taskService: ITaskService, configurationService: IConfigurationService, quickInputService: IQuickInputService, notificationService: INotificationService, - dialogService: IDialogService, themeService: IThemeService, placeHolder: string, defaultEntry?: ITaskQuickPickEntry) { + dialogService: IDialogService, themeService: IThemeService, placeHolder: string, defaultEntry?: ITaskQuickPickEntry, filter?: string) { const taskQuickPick = new TaskQuickPick(taskService, configurationService, quickInputService, notificationService, themeService, dialogService); - return taskQuickPick.show(placeHolder, defaultEntry); + return taskQuickPick.show(placeHolder, defaultEntry, undefined, filter); } } diff --git a/src/vs/workbench/contrib/tasks/electron-sandbox/taskService.ts b/src/vs/workbench/contrib/tasks/electron-sandbox/taskService.ts index 0620c9bc2d5..050ef08efde 100644 --- a/src/vs/workbench/contrib/tasks/electron-sandbox/taskService.ts +++ b/src/vs/workbench/contrib/tasks/electron-sandbox/taskService.ts @@ -44,6 +44,7 @@ import { IWorkspaceTrustManagementService, IWorkspaceTrustRequestService } from import { ITerminalProfileResolverService } from 'vs/workbench/contrib/terminal/common/terminal'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; interface IWorkspaceFolderConfigurationResult { workspaceFolder: IWorkspaceFolder; @@ -85,7 +86,8 @@ export class TaskService extends AbstractTaskService { @IWorkspaceTrustRequestService workspaceTrustRequestService: IWorkspaceTrustRequestService, @IWorkspaceTrustManagementService workspaceTrustManagementService: IWorkspaceTrustManagementService, @ILogService logService: ILogService, - @IThemeService themeService: IThemeService) { + @IThemeService themeService: IThemeService, + @IInstantiationService instantiationService: IInstantiationService) { super(configurationService, markerService, outputService, @@ -118,7 +120,8 @@ export class TaskService extends AbstractTaskService { workspaceTrustRequestService, workspaceTrustManagementService, logService, - themeService); + themeService, + ); this._register(lifecycleService.onBeforeShutdown(event => event.veto(this.beforeShutdown(), 'veto.tasks'))); } -- cgit v1.2.3 From 6a900fc3714fcfe5bceed8f088be46eb35c8a410 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 13 Jul 2022 19:31:52 +0200 Subject: Fix #154950 (#155089) --- src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 31cc538fa56..ae27fb19d2b 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -33,7 +33,6 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IContextKeyService, ContextKeyExpr, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { getMaliciousExtensionsSet } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IHostService } from 'vs/workbench/services/host/browser/host'; @@ -60,6 +59,7 @@ import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/b import { coalesce } from 'vs/base/common/arrays'; import { extractEditorsAndFilesDropData } from 'vs/platform/dnd/browser/dnd'; import { extname } from 'vs/base/common/resources'; +import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; const SearchMarketplaceExtensionsContext = new RawContextKey('searchMarketplaceExtensions', false); const SearchIntalledExtensionsContext = new RawContextKey('searchInstalledExtensions', false); @@ -807,12 +807,11 @@ export class MaliciousExtensionChecker implements IWorkbenchContribution { } private checkForMaliciousExtensions(): Promise { - return this.extensionsManagementService.getExtensionsControlManifest().then(report => { - const maliciousSet = getMaliciousExtensionsSet(report); + return this.extensionsManagementService.getExtensionsControlManifest().then(extensionsControlManifest => { return this.extensionsManagementService.getInstalled(ExtensionType.User).then(installed => { const maliciousExtensions = installed - .filter(e => maliciousSet.has(e.identifier.id)); + .filter(e => extensionsControlManifest.malicious.some(identifier => areSameExtensions(e.identifier, identifier))); if (maliciousExtensions.length) { return Promises.settled(maliciousExtensions.map(e => this.extensionsManagementService.uninstall(e).then(() => { -- cgit v1.2.3 From 06443bcc10f3b0b0e498a484e74728895683d698 Mon Sep 17 00:00:00 2001 From: Rich Chiodo Date: Wed, 13 Jul 2022 11:42:57 -0700 Subject: Double check IW is the notebook changing when handling scroll events (#155095) * Fix 155092 * Review feedback --- .../workbench/contrib/interactive/browser/interactiveEditor.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts index 81992e7547d..3462892ee8a 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts @@ -57,6 +57,7 @@ import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/co import { NOTEBOOK_KERNEL } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { ICursorPositionChangedEvent } from 'vs/editor/common/cursorEvents'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { isEqual } from 'vs/base/common/resources'; const DECORATION_KEY = 'interactiveInputDecoration'; const INTERACTIVE_EDITOR_VIEW_STATE_PREFERENCE_KEY = 'InteractiveEditorViewState'; @@ -152,9 +153,11 @@ export class InteractiveEditor extends EditorPane { codeEditorService.registerDecorationType('interactive-decoration', DECORATION_KEY, {}); this._register(this.#keybindingService.onDidUpdateKeybindings(this.#updateInputDecoration, this)); this._register(this.#notebookExecutionStateService.onDidChangeCellExecution((e) => { - const cell = this.#notebookWidget.value?.getCellByHandle(e.cellHandle); - if (cell && e.changed?.state) { - this.#scrollIfNecessary(cell); + if (isEqual(e.notebook, this.#notebookWidget.value?.viewModel?.notebookDocument.uri)) { + const cell = this.#notebookWidget.value?.getCellByHandle(e.cellHandle); + if (cell && e.changed?.state) { + this.#scrollIfNecessary(cell); + } } })); } -- cgit v1.2.3 From 63c044b46236fdc2bf1f90131deb8e5d38b23e0b Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 13 Jul 2022 16:33:51 -0700 Subject: Add configure decorations ctx menu entry Fixes #155118 --- .../terminal/browser/xterm/decorationAddon.ts | 98 +++++++++++++++++++++- 1 file changed, 95 insertions(+), 3 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts index 31d764e8de2..feff72648ca 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts @@ -25,6 +25,8 @@ import { TERMINAL_COMMAND_DECORATION_DEFAULT_BACKGROUND_COLOR, TERMINAL_COMMAND_ import { Color } from 'vs/base/common/color'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IGenericMarkProperties } from 'vs/platform/terminal/common/terminalProcess'; +import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { Codicon } from 'vs/base/common/codicons'; const enum DecorationSelector { CommandDecoration = 'terminal-command-decoration', @@ -65,7 +67,8 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { @IHoverService private readonly _hoverService: IHoverService, @IConfigurationService private readonly _configurationService: IConfigurationService, @IThemeService private readonly _themeService: IThemeService, - @IOpenerService private readonly _openerService: IOpenerService + @IOpenerService private readonly _openerService: IOpenerService, + @IQuickInputService private readonly _quickInputService: IQuickInputService ) { super(); this._register(toDisposable(() => this._dispose())); @@ -431,13 +434,102 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { if (actions.length > 0) { actions.push(new Separator()); } - const label = localize("terminal.learnShellIntegration", 'Learn About Shell Integration'); + const labelConfigure = localize("terminal.configureCommandDecorations", 'Configure Command Decorations'); actions.push({ - class: undefined, tooltip: label, dispose: () => { }, id: 'terminal.learnShellIntegration', label, enabled: true, + class: undefined, tooltip: labelConfigure, dispose: () => { }, id: 'terminal.configureCommandDecorations', label: labelConfigure, enabled: true, + run: () => this._showConfigureCommandDecorationsQuickPick() + }); + const labelAbout = localize("terminal.learnShellIntegration", 'Learn About Shell Integration'); + actions.push({ + class: undefined, tooltip: labelAbout, dispose: () => { }, id: 'terminal.learnShellIntegration', label: labelAbout, enabled: true, run: () => this._openerService.open('https://code.visualstudio.com/docs/editor/integrated-terminal#_shell-integration') }); return actions; } + + private async _showConfigureCommandDecorationsQuickPick() { + const quickPick = this._quickInputService.createQuickPick(); + quickPick.items = [ + { id: 'a', label: localize('changeDefaultIcon', 'Change default icon') }, + { id: 'b', label: localize('changeSuccessIcon', 'Change success icon') }, + { id: 'c', label: localize('changeErrorIcon', 'Change error icon') }, + { type: 'separator' }, + { id: 'd', label: localize('toggleVisibility', 'Toggle visibility') }, + ]; + quickPick.canSelectMany = false; + quickPick.onDidAccept(async e => { + quickPick.hide(); + const result = quickPick.activeItems[0]; + let iconSetting: string | undefined; + switch (result.id) { + case 'a': iconSetting = TerminalSettingId.ShellIntegrationDecorationIcon; break; + case 'b': iconSetting = TerminalSettingId.ShellIntegrationDecorationIconSuccess; break; + case 'c': iconSetting = TerminalSettingId.ShellIntegrationDecorationIconError; break; + case 'd': this._showToggleVisibilityQuickPick(); break; + } + if (iconSetting) { + this._showChangeIconQuickPick(iconSetting); + } + }); + quickPick.show(); + } + + private async _showChangeIconQuickPick(iconSetting: string) { + type Item = IQuickPickItem & { icon: Codicon }; + const items: Item[] = []; + for (const icon of Codicon.getAll()) { + items.push({ label: `$(${icon.id})`, description: `${icon.id}`, icon }); + } + const result = await this._quickInputService.pick(items, { + matchOnDescription: true + }); + if (result) { + this._configurationService.updateValue(iconSetting, result.icon.id); + this._showConfigureCommandDecorationsQuickPick(); + } + } + + private _showToggleVisibilityQuickPick() { + const quickPick = this._quickInputService.createQuickPick(); + quickPick.hideInput = true; + quickPick.hideCheckAll = true; + quickPick.canSelectMany = true; + quickPick.title = localize('toggleVisibility', 'Toggle visibility'); + const configValue = this._configurationService.getValue(TerminalSettingId.ShellIntegrationDecorationsEnabled); + const gutterIcon: IQuickPickItem = { + label: localize('gutter', 'Gutter command decorations'), + picked: configValue !== 'never' && configValue !== 'overviewRuler' + }; + const overviewRulerIcon: IQuickPickItem = { + label: localize('overviewRuler', 'Overview ruler command decorations'), + picked: configValue !== 'never' && configValue !== 'gutter' + }; + quickPick.items = [gutterIcon, overviewRulerIcon]; + const selectedItems: IQuickPickItem[] = []; + if (configValue !== 'never') { + if (configValue !== 'gutter') { + selectedItems.push(gutterIcon); + } + if (configValue !== 'overviewRuler') { + selectedItems.push(overviewRulerIcon); + } + } + quickPick.selectedItems = selectedItems; + quickPick.onDidChangeSelection(async e => { + let newValue: 'both' | 'gutter' | 'overviewRuler' | 'never' = 'never'; + if (e.includes(gutterIcon)) { + if (e.includes(overviewRulerIcon)) { + newValue = 'both'; + } else { + newValue = 'gutter'; + } + } else if (e.includes(overviewRulerIcon)) { + newValue = 'overviewRuler'; + } + await this._configurationService.updateValue(TerminalSettingId.ShellIntegrationDecorationsEnabled, newValue); + }); + quickPick.show(); + } } let successColor: string | Color | undefined; let errorColor: string | Color | undefined; -- cgit v1.2.3 From e0a65a97d4f349cf11a7cae804a5553ccb412528 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Wed, 13 Jul 2022 18:16:53 -0700 Subject: support watch task reconnection (#155120) --- .../contrib/tasks/browser/abstractTaskService.ts | 33 ++++-- .../contrib/tasks/browser/terminalTaskSystem.ts | 115 +++++++++++++-------- .../workbench/contrib/tasks/common/taskSystem.ts | 1 + src/vs/workbench/contrib/tasks/common/tasks.ts | 3 +- .../workbench/contrib/terminal/browser/terminal.ts | 8 +- .../contrib/terminal/browser/terminalInstance.ts | 4 +- .../terminal/browser/terminalProcessManager.ts | 9 +- .../contrib/terminal/browser/terminalService.ts | 19 ++++ 8 files changed, 135 insertions(+), 57 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 89ec1896b26..33f885dfe72 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -339,6 +339,23 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this._onDidRegisterSupportedExecutions.fire(); } + private async _restartTasks(): Promise { + const recentlyUsedTasks = await this.readRecentTasks(); + if (!recentlyUsedTasks) { + return; + } + for (const task of recentlyUsedTasks) { + if (ConfiguringTask.is(task)) { + const resolved = await this.tryResolveTask(task); + if (resolved) { + this.run(resolved, undefined, TaskRunSource.Reconnect); + } + } else { + this.run(task, undefined, TaskRunSource.Reconnect); + } + } + } + public get onDidStateChange(): Event { return this._onDidStateChange.event; } @@ -405,7 +422,6 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this._runTerminateCommand(arg); } }); - CommandsRegistry.registerCommand('workbench.action.tasks.showLog', () => { if (!this._canRunCommand()) { return; @@ -602,7 +618,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return infosCount > 0; } - public registerTaskSystem(key: string, info: ITaskSystemInfo): void { + public async registerTaskSystem(key: string, info: ITaskSystemInfo): Promise { // Ideally the Web caller of registerRegisterTaskSystem would use the correct key. // However, the caller doesn't know about the workspace folders at the time of the call, even though we know about them here. if (info.platform === Platform.Platform.Web) { @@ -622,6 +638,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (this.hasTaskSystemInfo) { this._onDidChangeTaskSystemInfo.fire(); + await this._restartTasks(); } } @@ -661,7 +678,6 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } } } - return result; } @@ -917,7 +933,6 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer map.get(folder).push(task); } } - for (const entry of recentlyUsedTasks.entries()) { const key = entry[0]; const task = JSON.parse(entry[1]); @@ -926,6 +941,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } const readTasksMap: Map = new Map(); + async function readTasks(that: AbstractTaskService, map: Map, isWorkspaceFile: boolean) { for (const key of map.keys()) { const custom: CustomTask[] = []; @@ -954,7 +970,6 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } await readTasks(this, folderToTasksMap, false); await readTasks(this, workspaceToTaskMap, true); - for (const key of recentlyUsedTasks.keys()) { if (readTasksMap.has(key)) { tasks.push(readTasksMap.get(key)!); @@ -1749,8 +1764,11 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer ? await this.getTask(taskFolder, taskIdentifier) : task) ?? task; } await ProblemMatcherRegistry.onReady(); - const executeResult = this._getTaskSystem().run(taskToRun, resolver); - return this._handleExecuteResult(executeResult, runSource); + const executeResult = runSource === TaskRunSource.Reconnect ? this._getTaskSystem().reconnect(taskToRun, resolver) : this._getTaskSystem().run(taskToRun, resolver); + if (executeResult) { + return this._handleExecuteResult(executeResult, runSource); + } + return { exitCode: 0 }; } private async _handleExecuteResult(executeResult: ITaskExecuteResult, runSource?: TaskRunSource): Promise { @@ -1781,6 +1799,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer throw new TaskError(Severity.Warning, nls.localize('TaskSystem.active', 'There is already a task running. Terminate it first before executing another task.'), TaskErrors.RunningTask); } } + this._setRecentlyUsedTask(executeResult.task); return executeResult.promise; } diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index 7cfab363b6d..b46a625ed10 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -3,58 +3,52 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as path from 'vs/base/common/path'; -import * as nls from 'vs/nls'; -import * as Objects from 'vs/base/common/objects'; -import * as Types from 'vs/base/common/types'; -import * as Platform from 'vs/base/common/platform'; import * as Async from 'vs/base/common/async'; -import * as resources from 'vs/base/common/resources'; import { IStringDictionary } from 'vs/base/common/collections'; +import { Emitter, Event } from 'vs/base/common/event'; +import { isUNC } from 'vs/base/common/extpath'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { LinkedMap, Touch } from 'vs/base/common/map'; +import * as Objects from 'vs/base/common/objects'; +import * as path from 'vs/base/common/path'; +import * as Platform from 'vs/base/common/platform'; +import * as resources from 'vs/base/common/resources'; import Severity from 'vs/base/common/severity'; -import { Event, Emitter } from 'vs/base/common/event'; -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { isUNC } from 'vs/base/common/extpath'; +import * as Types from 'vs/base/common/types'; +import * as nls from 'vs/nls'; +import { IModelService } from 'vs/editor/common/services/model'; import { IFileService } from 'vs/platform/files/common/files'; import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers'; -import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { IModelService } from 'vs/editor/common/services/model'; -import { ProblemMatcher, ProblemMatcherRegistry /*, ProblemPattern, getResource */ } from 'vs/workbench/contrib/tasks/common/problemMatcher'; +import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { Markers } from 'vs/workbench/contrib/markers/common/markers'; +import { ProblemMatcher, ProblemMatcherRegistry /*, ProblemPattern, getResource */ } from 'vs/workbench/contrib/tasks/common/problemMatcher'; -import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; -import { ITerminalProfileResolverService, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal'; -import { ITerminalService, ITerminalInstance, ITerminalGroupService } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { IOutputService } from 'vs/workbench/services/output/common/output'; -import { StartStopProblemCollector, WatchingProblemCollector, ProblemCollectorEventKind, ProblemHandlingStrategy } from 'vs/workbench/contrib/tasks/common/problemCollectors'; -import { - Task, CustomTask, ContributedTask, RevealKind, CommandOptions, IShellConfiguration, RuntimeType, PanelKind, - TaskEvent, TaskEventKind, IShellQuotingOptions, ShellQuoting, CommandString, ICommandConfiguration, IExtensionTaskSource, TaskScope, RevealProblemKind, DependsOrder, TaskSourceKind, InMemoryTask, ITaskEvent, TaskSettingId -} from 'vs/workbench/contrib/tasks/common/tasks'; -import { - ITaskSystem, ITaskSummary, ITaskExecuteResult, TaskExecuteKind, TaskError, TaskErrors, ITaskResolver, - Triggers, ITaskTerminateResponse, ITaskSystemInfoResolver, ITaskSystemInfo, IResolveSet, IResolvedVariables -} from 'vs/workbench/contrib/tasks/common/taskSystem'; -import { URI } from 'vs/base/common/uri'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { Codicon } from 'vs/base/common/codicons'; import { Schemas } from 'vs/base/common/network'; -import { IPathService } from 'vs/workbench/services/path/common/pathService'; -import { IViewsService, IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; -import { ILogService } from 'vs/platform/log/common/log'; +import { URI } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IShellLaunchConfig, TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; -import { TerminalProcessExtHostProxy } from 'vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy'; -import { TaskTerminalStatus } from 'vs/workbench/contrib/tasks/browser/taskTerminalStatus'; -import { ITaskService } from 'vs/workbench/contrib/tasks/common/taskService'; -import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; +import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { IShellLaunchConfig, TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { formatMessageForTerminal } from 'vs/platform/terminal/common/terminalStrings'; +import { ThemeIcon } from 'vs/platform/theme/common/themeService'; +import { IViewDescriptorService, IViewsService, ViewContainerLocation } from 'vs/workbench/common/views'; +import { TaskTerminalStatus } from 'vs/workbench/contrib/tasks/browser/taskTerminalStatus'; +import { ProblemCollectorEventKind, ProblemHandlingStrategy, StartStopProblemCollector, WatchingProblemCollector } from 'vs/workbench/contrib/tasks/common/problemCollectors'; import { GroupKind } from 'vs/workbench/contrib/tasks/common/taskConfiguration'; -import { Codicon } from 'vs/base/common/codicons'; +import { CommandOptions, CommandString, ContributedTask, CustomTask, DependsOrder, ICommandConfiguration, IExtensionTaskSource, InMemoryTask, IShellConfiguration, IShellQuotingOptions, ITaskEvent, PanelKind, RevealKind, RevealProblemKind, RuntimeType, ShellQuoting, Task, TaskEvent, TaskEventKind, TaskScope, TaskSettingId, TaskSourceKind } from 'vs/workbench/contrib/tasks/common/tasks'; +import { ITaskService } from 'vs/workbench/contrib/tasks/common/taskService'; +import { IResolvedVariables, IResolveSet, ITaskExecuteResult, ITaskResolver, ITaskSummary, ITaskSystem, ITaskSystemInfo, ITaskSystemInfoResolver, ITaskTerminateResponse, TaskError, TaskErrors, TaskExecuteKind, Triggers } from 'vs/workbench/contrib/tasks/common/taskSystem'; +import { ITerminalGroupService, ITerminalInstance, ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { VSCodeOscProperty, VSCodeOscPt, VSCodeSequence } from 'vs/workbench/contrib/terminal/browser/terminalEscapeSequences'; +import { TerminalProcessExtHostProxy } from 'vs/workbench/contrib/terminal/browser/terminalProcessExtHostProxy'; +import { ITerminalProfileResolverService, TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IOutputService } from 'vs/workbench/services/output/common/output'; +import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; +import { IPathService } from 'vs/workbench/services/path/common/pathService'; interface ITerminalData { terminal: ITerminalInstance; @@ -205,7 +199,8 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { private _previousTerminalInstance: ITerminalInstance | undefined; private _terminalStatusManager: TaskTerminalStatus; private _terminalCreationQueue: Promise = Promise.resolve(); - + private _hasReconnected: boolean = false; + private _tasksToReconnect: string[] = []; private readonly _onDidStateChange: Emitter; get taskShellIntegrationStartSequence(): string { @@ -245,12 +240,23 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { this._terminals = Object.create(null); this._idleTaskTerminals = new LinkedMap(); this._sameTaskTerminals = Object.create(null); - this._onDidStateChange = new Emitter(); this._taskSystemInfoResolver = taskSystemInfoResolver; this._register(this._terminalStatusManager = new TaskTerminalStatus(taskService)); } + private _reconnectToTerminals(terminals: ITerminalInstance[]): void { + for (const terminal of terminals) { + const taskForTerminal = terminal.shellLaunchConfig.attachPersistentProcess?.task; + if (taskForTerminal?.id && taskForTerminal?.lastTask) { + this._tasksToReconnect.push(taskForTerminal.id); + this._terminals[terminal.instanceId] = { terminal, lastTask: taskForTerminal.lastTask, group: taskForTerminal.group }; + } else { + this._logService.trace(`Could not reconnect to terminal ${terminal.instanceId} with process details ${terminal.shellLaunchConfig.attachPersistentProcess}`); + } + } + } + public get onDidStateChange(): Event { return this._onDidStateChange.event; } @@ -263,6 +269,19 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { this._outputService.showChannel(this._outputChannelId, true); } + public reconnect(task: Task, resolver: ITaskResolver, trigger: string = Triggers.command): ITaskExecuteResult | undefined { + const terminals = this._terminalService.getReconnectedTerminals('Task'); + if (!this._hasReconnected && terminals && terminals.length > 0) { + this._reconnectToTerminals(terminals); + this._hasReconnected = true; + } + if (this._tasksToReconnect.includes(task._id)) { + this._lastTask = new VerifiedTask(task, resolver, trigger); + this.rerun(); + } + return undefined; + } + public run(task: Task, resolver: ITaskResolver, trigger: string = Triggers.command): ITaskExecuteResult { task = task.clone(); // A small amount of task state is stored in the task (instance) and tasks passed in to run may have that set already. const recentTaskKey = task.getRecentlyUsedKey() ?? ''; @@ -1269,7 +1288,18 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { return createdTerminal; } + private _reviveTerminals(): void { + if (Object.entries(this._terminals).length === 0) { + for (const terminal of this._terminalService.instances) { + if (terminal.shellLaunchConfig.attachPersistentProcess?.task?.lastTask) { + this._terminals[terminal.instanceId] = { lastTask: terminal.shellLaunchConfig.attachPersistentProcess.task.lastTask, group: terminal.shellLaunchConfig.attachPersistentProcess.task.group, terminal }; + } + } + } + } + private async _createTerminal(task: CustomTask | ContributedTask, resolver: VariableResolver, workspaceFolder: IWorkspaceFolder | undefined): Promise<[ITerminalInstance | undefined, TaskError | undefined]> { + this._reviveTerminals(); const platform = resolver.taskSystemInfo ? resolver.taskSystemInfo.platform : Platform.platform; const options = await this._resolveOptions(resolver, task.command.options); const presentationOptions = task.command.presentation; @@ -1308,7 +1338,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { }, 'Executing task: {0}', task._label), { excludeLeadingNewLine: true }) : undefined, isFeatureTerminal: true, icon: task.configurationProperties.icon?.id ? ThemeIcon.fromId(task.configurationProperties.icon.id) : undefined, - color: task.configurationProperties.icon?.color || undefined, + color: task.configurationProperties.icon?.color || undefined }; } else { const resolvedResult: { command: CommandString; args: CommandString[] } = await this._resolveCommandAndArgs(resolver, task.command); @@ -1369,9 +1399,10 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { this._terminalCreationQueue = this._terminalCreationQueue.then(() => this._doCreateTerminal(group, launchConfigs!)); const result: ITerminalInstance = (await this._terminalCreationQueue)!; - + result.shellLaunchConfig.task = { lastTask: taskKey, group, label: task._label, id: task._id }; + result.shellLaunchConfig.reconnectionOwner = 'Task'; const terminalKey = result.instanceId.toString(); - result.onDisposed((terminal) => { + result.onDisposed(() => { const terminalData = this._terminals[terminalKey]; if (terminalData) { delete this._terminals[terminalKey]; diff --git a/src/vs/workbench/contrib/tasks/common/taskSystem.ts b/src/vs/workbench/contrib/tasks/common/taskSystem.ts index cd204106e8e..7b7b67f3a19 100644 --- a/src/vs/workbench/contrib/tasks/common/taskSystem.ts +++ b/src/vs/workbench/contrib/tasks/common/taskSystem.ts @@ -102,6 +102,7 @@ export interface ITaskSystemInfoResolver { export interface ITaskSystem { onDidStateChange: Event; run(task: Task, resolver: ITaskResolver): ITaskExecuteResult; + reconnect(task: Task, resolver: ITaskResolver): ITaskExecuteResult | undefined; rerun(): ITaskExecuteResult | undefined; isActive(): Promise; isActiveSync(): boolean; diff --git a/src/vs/workbench/contrib/tasks/common/tasks.ts b/src/vs/workbench/contrib/tasks/common/tasks.ts index 5877beb6437..2033ec632d9 100644 --- a/src/vs/workbench/contrib/tasks/common/tasks.ts +++ b/src/vs/workbench/contrib/tasks/common/tasks.ts @@ -1131,7 +1131,8 @@ export const enum TaskRunSource { System, User, FolderOpen, - ConfigurationChange + ConfigurationChange, + Reconnect } export namespace TaskEvent { diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index aa125ac3d78..45871689f60 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -133,6 +133,7 @@ export interface ITerminalService extends ITerminalInstanceHost { readonly connectionState: TerminalConnectionState; readonly defaultLocation: TerminalLocation; + initializeTerminals(): Promise; onDidChangeActiveGroup: Event; onDidDisposeGroup: Event; @@ -163,6 +164,11 @@ export interface ITerminalService extends ITerminalInstanceHost { getInstanceFromId(terminalId: number): ITerminalInstance | undefined; getInstanceFromIndex(terminalIndex: number): ITerminalInstance; + /** + * An owner of terminals might be created after reconnection has occurred, + * so store them to be requested/adopted later + */ + getReconnectedTerminals(reconnectionOwner: string): ITerminalInstance[] | undefined; getActiveOrCreateInstance(): Promise; moveToEditor(source: ITerminalInstance): void; @@ -439,7 +445,7 @@ export interface ITerminalInstance { readonly fixedRows?: number; readonly icon?: TerminalIcon; readonly color?: string; - + readonly reconnectionOwner?: string; readonly processName: string; readonly sequence?: string; readonly staticTitle?: string; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 761739d44a9..bbfb4d97207 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -276,6 +276,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // TODO: Should this be an event as it can fire twice? get processReady(): Promise { return this._processManager.ptyProcessReady; } get hasChildProcesses(): boolean { return this.shellLaunchConfig.attachPersistentProcess?.hasChildProcesses || this._processManager.hasChildProcesses; } + get reconnectionOwner(): string | undefined { return this.shellLaunchConfig.attachPersistentProcess?.reconnectionOwner || this.shellLaunchConfig.reconnectionOwner; } get areLinksReady(): boolean { return this._areLinksReady; } get initialDataEvents(): string[] | undefined { return this._initialDataEvents; } get exitCode(): number | undefined { return this._exitCode; } @@ -1726,7 +1727,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { if (this._isExiting) { return; } - const parsedExitResult = parseExitResult(exitCodeOrError, this.shellLaunchConfig, this._processManager.processState, this._initialCwd); if (this._usedShellIntegrationInjection && this._processManager.processState === ProcessState.KilledDuringLaunch && parsedExitResult?.code !== 0) { @@ -2355,7 +2355,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { info.requiresAction && this._configHelper.config.environmentChangesRelaunch && !this._processManager.hasWrittenData && - !this._shellLaunchConfig.isFeatureTerminal && + (this.reconnectionOwner || !this._shellLaunchConfig.isFeatureTerminal) && !this._shellLaunchConfig.customPtyImplementation && !this._shellLaunchConfig.isExtensionOwnedTerminal && !this._shellLaunchConfig.attachPersistentProcess diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index ba6540e654c..901505cb26a 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -113,9 +113,10 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce readonly onRestoreCommands = this._onRestoreCommands.event; get persistentProcessId(): number | undefined { return this._process?.id; } - get shouldPersist(): boolean { return this._process ? this._process.shouldPersist : false; } + get shouldPersist(): boolean { return !!this.reconnectionOwner || (this._process ? this._process.shouldPersist : false); } get hasWrittenData(): boolean { return this._hasWrittenData; } get hasChildProcesses(): boolean { return this._hasChildProcesses; } + get reconnectionOwner(): string | undefined { return this._shellLaunchConfig?.attachPersistentProcess?.reconnectionOwner || this._shellLaunchConfig?.reconnectionOwner || undefined; } constructor( private readonly _instanceId: number, @@ -245,7 +246,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce // this is a copy of what the merged environment collection is on the remote side const env = await this._resolveEnvironment(backend, variableResolver, shellLaunchConfig); - const shouldPersist = !shellLaunchConfig.isFeatureTerminal && this._configHelper.config.enablePersistentSessions && !shellLaunchConfig.isTransient; + const shouldPersist = (!!shellLaunchConfig.reconnectionOwner || !shellLaunchConfig.isFeatureTerminal) && this._configHelper.config.enablePersistentSessions && !shellLaunchConfig.isTransient; if (shellLaunchConfig.attachPersistentProcess) { const result = await backend.attachToProcess(shellLaunchConfig.attachPersistentProcess.id); if (result) { @@ -461,7 +462,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce windowsEnableConpty: this._configHelper.config.windowsEnableConpty && !isScreenReaderModeEnabled, environmentVariableCollections: this._extEnvironmentVariableCollection ? serializeEnvironmentVariableCollections(this._extEnvironmentVariableCollection.collections) : undefined }; - const shouldPersist = this._configHelper.config.enablePersistentSessions && !shellLaunchConfig.isFeatureTerminal; + const shouldPersist = this._configHelper.config.enablePersistentSessions && (!!this.reconnectionOwner || !shellLaunchConfig.isFeatureTerminal); return await backend.createProcess(shellLaunchConfig, initialCwd, cols, rows, this._configHelper.config.unicodeVersion, env, options, shouldPersist); } @@ -494,7 +495,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce this._ptyResponsiveListener?.dispose(); this._ptyResponsiveListener = undefined; if (this._shellLaunchConfig) { - if (this._shellLaunchConfig.isFeatureTerminal) { + if (this._shellLaunchConfig.isFeatureTerminal && !this.reconnectionOwner) { // Indicate the process is exited (and gone forever) only for feature terminals // so they can react to the exit, this is particularly important for tasks so // that it knows that the process is not still active. Note that this is not diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 7ed18631566..f315f11cd30 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -84,6 +84,12 @@ export class TerminalService implements ITerminalService { return this._terminalGroupService.instances.concat(this._terminalEditorService.instances); } + + private _reconnectedTerminals: Map = new Map(); + getReconnectedTerminals(reconnectionOwner: string): ITerminalInstance[] | undefined { + return this._reconnectedTerminals.get(reconnectionOwner); + } + get defaultLocation(): TerminalLocation { return this.configHelper.config.defaultLocation === TerminalLocationString.Editor ? TerminalLocation.Editor : TerminalLocation.Panel; } private _activeInstance: ITerminalInstance | undefined; @@ -1039,9 +1045,21 @@ export class TerminalService implements ITerminalService { shellLaunchConfig.parentTerminalId = parent.instanceId; instance = group.split(shellLaunchConfig); } + this._addToReconnected(instance); return instance; } + private _addToReconnected(instance: ITerminalInstance): void { + if (instance.reconnectionOwner) { + const reconnectedTerminals = this._reconnectedTerminals.get(instance.reconnectionOwner); + if (reconnectedTerminals) { + reconnectedTerminals.push(instance); + } else { + this._reconnectedTerminals.set(instance.reconnectionOwner, [instance]); + } + } + } + private _createTerminal(shellLaunchConfig: IShellLaunchConfig, location: TerminalLocation, options?: ICreateTerminalOptions): ITerminalInstance { let instance; const editorOptions = this._getEditorOptions(options?.location); @@ -1054,6 +1072,7 @@ export class TerminalService implements ITerminalService { const group = this._terminalGroupService.createGroup(shellLaunchConfig); instance = group.terminalInstances[0]; } + this._addToReconnected(instance); return instance; } -- cgit v1.2.3 From f947fab886dab0f6f60eeda7e25b156b75bf5d8d Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Thu, 14 Jul 2022 08:26:56 +0200 Subject: editors - clarify view state resource (#155136) --- src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts | 1 - 1 file changed, 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts index abd5efbe468..74e3ee14e88 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts @@ -452,7 +452,6 @@ export class MergeEditor extends AbstractTextEditor { protected computeEditorViewState(resource: URI): IMergeEditorViewState | undefined { if (!isEqual(this.model?.result.uri, resource)) { - // TODO@bpasero Why not check `input#resource` and don't ask me for "forgein" resources? return undefined; } const result = this.inputResultView.editor.saveViewState(); -- cgit v1.2.3 From 461f8694f5e489c6da83e72f1267fef5d0a5e60f Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Thu, 14 Jul 2022 02:34:23 -0400 Subject: Throw error on legacy walkthrough (#155104) Remove old walkthrough format --- .../browser/gettingStartedService.ts | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts index dcbddfc4097..210f171d0a8 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService.ts @@ -364,28 +364,14 @@ export class WalkthroughsService extends Disposable implements IWalkthroughsServ }; } - // Legacy media config (only in use by remote-wsl at the moment) + // Throw error for unknown walkthrough format else { - const legacyMedia = step.media as unknown as { path: string; altText: string }; - if (typeof legacyMedia.path === 'string' && legacyMedia.path.endsWith('.md')) { - media = { - type: 'markdown', - path: convertExtensionPathToFileURI(legacyMedia.path), - base: convertExtensionPathToFileURI(dirname(legacyMedia.path)), - root: FileAccess.asFileUri(extension.extensionLocation), - }; - } - else { - const altText = legacyMedia.altText; - if (altText === undefined) { - console.error('Walkthrough item:', fullyQualifiedID, 'is missing altText for its media element.'); - } - media = { type: 'image', altText, path: convertExtensionRelativePathsToBrowserURIs(legacyMedia.path) }; - } + throw new Error('Unknown walkthrough format detected for ' + fullyQualifiedID); } return ({ - description, media, + description, + media, completionEvents: step.completionEvents?.filter(x => typeof x === 'string') ?? [], id: fullyQualifiedID, title: step.title, -- cgit v1.2.3 From 191d74549cf2623fa09f197993efcf4c91e3958d Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Thu, 14 Jul 2022 07:41:08 +0000 Subject: =?UTF-8?q?=F0=9F=94=A8=20Apply=20"Surround=20With=20Snippet"=20co?= =?UTF-8?q?de=20actions=20via=20workspace=20edits?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Babak K. Shandiz --- .../snippets/browser/surroundWithSnippet.ts | 25 +++++++++++++++------- 1 file changed, 17 insertions(+), 8 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts b/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts index 44b03099e4c..917e04e2288 100644 --- a/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts +++ b/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts @@ -20,7 +20,8 @@ import { ITextModel } from 'vs/editor/common/model'; import { CodeAction, CodeActionProvider, CodeActionContext, CodeActionList } from 'vs/editor/common/languages'; import { CodeActionKind } from 'vs/editor/contrib/codeAction/browser/types'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; -import { Range } from 'vs/editor/common/core/range'; +import { Range, IRange } from 'vs/editor/common/core/range'; +import { URI } from 'vs/base/common/uri'; import { Selection } from 'vs/editor/common/core/selection'; import { Snippet } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -45,15 +46,23 @@ const options = { const MAX_SNIPPETS_ON_CODE_ACTIONS_MENU = 6; -function makeCodeActionForSnippet(snippet: Snippet): CodeAction { +function makeCodeActionForSnippet(snippet: Snippet, resource: URI, range: IRange): CodeAction { const title = localize('codeAction', "Surround With Snippet: {0}", snippet.name); return { title, - command: { - id: 'editor.action.insertSnippet', - title, - arguments: [{ name: snippet.name }] - }, + edit: { + edits: [ + { + versionId: undefined, + resource: resource, + textEdit: { + insertAsSnippet: true, + text: snippet.body, + range: range + } + } + ] + } }; } @@ -153,7 +162,7 @@ export class SurroundWithSnippetCodeActionProvider extends Disposable implements } return { actions: snippets.length <= MAX_SNIPPETS_ON_CODE_ACTIONS_MENU - ? snippets.map(x => makeCodeActionForSnippet(x)) + ? snippets.map(x => makeCodeActionForSnippet(x, model.uri, range)) : [SurroundWithSnippetCodeActionProvider.codeAction], dispose: () => { } }; -- cgit v1.2.3 From c9ea02399b985dd78ae63dc1ecd5b5175e43ba81 Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Thu, 14 Jul 2022 07:42:00 +0000 Subject: =?UTF-8?q?=F0=9F=92=84=20Improve=20field=20name=20for=20"Surround?= =?UTF-8?q?=20With=20Snippet"=20overflow=20code=20action?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Babak K. Shandiz --- src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts b/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts index 917e04e2288..edac26fe643 100644 --- a/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts +++ b/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts @@ -134,7 +134,7 @@ registerAction2(class SurroundWithSnippetEditorAction extends EditorAction2 { }); export class SurroundWithSnippetCodeActionProvider extends Disposable implements CodeActionProvider, IWorkbenchContribution { - private static readonly codeAction: CodeAction = { + private static readonly overflowCodeAction: CodeAction = { kind: CodeActionKind.Refactor.value, title: options.title.value, command: { @@ -163,7 +163,7 @@ export class SurroundWithSnippetCodeActionProvider extends Disposable implements return { actions: snippets.length <= MAX_SNIPPETS_ON_CODE_ACTIONS_MENU ? snippets.map(x => makeCodeActionForSnippet(x, model.uri, range)) - : [SurroundWithSnippetCodeActionProvider.codeAction], + : [SurroundWithSnippetCodeActionProvider.overflowCodeAction], dispose: () => { } }; } -- cgit v1.2.3 From c12daa6ea98ff1b08cb5aebae78aa743c7f58e82 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 14 Jul 2022 10:52:14 +0200 Subject: add 'Open File' command to merge editor title bar (#155159) fixes https://github.com/microsoft/vscode/issues/153690 --- .../mergeEditor/browser/commands/commands.ts | 31 ++++++++++++++++++++++ .../browser/mergeEditor.contribution.ts | 3 ++- 2 files changed, 33 insertions(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts index 892febf378e..2a0d776fb18 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts @@ -167,6 +167,35 @@ const mergeEditorCategory: ILocalizedString = { original: 'Merge Editor', }; +export class OpenResultResource extends Action2 { + constructor() { + super({ + id: 'merge.openResult', + icon: Codicon.goToFile, + title: { + value: localize('openfile', 'Open File'), + original: 'Open File', + }, + category: mergeEditorCategory, + menu: [{ + id: MenuId.EditorTitle, + when: ctxIsMergeEditor, + group: 'navigation', + order: 1, + }], + precondition: ctxIsMergeEditor, + }); + } + + async run(accessor: ServicesAccessor): Promise { + const opener = accessor.get(IOpenerService); + const { activeEditor } = accessor.get(IEditorService); + if (activeEditor instanceof MergeEditorInput) { + await opener.open(activeEditor.result); + } + } +} + export class GoToNextConflict extends Action2 { constructor() { super({ @@ -182,6 +211,7 @@ export class GoToNextConflict extends Action2 { id: MenuId.EditorTitle, when: ctxIsMergeEditor, group: 'navigation', + order: 3 }, ], f1: true, @@ -215,6 +245,7 @@ export class GoToPreviousConflict extends Action2 { id: MenuId.EditorTitle, when: ctxIsMergeEditor, group: 'navigation', + order: 2 }, ], f1: true, diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts index 09206f43520..cba7f643cec 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts @@ -10,7 +10,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/editor'; -import { CompareInput1WithBaseCommand, CompareInput2WithBaseCommand, GoToNextConflict, GoToPreviousConflict, OpenBaseFile, OpenMergeEditor, SetColumnLayout, SetMixedLayout, ToggleActiveConflictInput1, ToggleActiveConflictInput2 } from 'vs/workbench/contrib/mergeEditor/browser/commands/commands'; +import { CompareInput1WithBaseCommand, CompareInput2WithBaseCommand, GoToNextConflict, GoToPreviousConflict, OpenBaseFile, OpenMergeEditor, OpenResultResource, SetColumnLayout, SetMixedLayout, ToggleActiveConflictInput1, ToggleActiveConflictInput2 } from 'vs/workbench/contrib/mergeEditor/browser/commands/commands'; import { MergeEditorCopyContentsToJSON, MergeEditorOpenContents } from 'vs/workbench/contrib/mergeEditor/browser/commands/devCommands'; import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; import { MergeEditor, MergeEditorOpenHandlerContribution } from 'vs/workbench/contrib/mergeEditor/browser/view/mergeEditor'; @@ -33,6 +33,7 @@ Registry.as(EditorExtensions.EditorFactory).registerEdit MergeEditorSerializer ); +registerAction2(OpenResultResource); registerAction2(SetMixedLayout); registerAction2(SetColumnLayout); registerAction2(OpenMergeEditor); -- cgit v1.2.3 From 86fc94416c235ff96a4a43709be63a8e4bd37f19 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 14 Jul 2022 14:29:42 +0200 Subject: tweak message when closing dirty and conflicting merge editor (#155176) fyi @bpasero this ensures the close handler is always called with `IEditorIdentifier[]` re https://github.com/microsoft/vscode/issues/152841 --- .../contrib/mergeEditor/browser/mergeEditorInput.ts | 18 +++++++++++------- .../contrib/terminal/browser/terminalEditorInput.ts | 4 ++-- 2 files changed, 13 insertions(+), 9 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts index 4f29941dc13..864214ae210 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts @@ -173,21 +173,25 @@ class MergeEditorCloseHandler implements IEditorCloseHandler { return !this._ignoreUnhandledConflicts && this._model.hasUnhandledConflicts.get(); } - async confirm(editors?: readonly IEditorIdentifier[] | undefined): Promise { + async confirm(editors: readonly IEditorIdentifier[]): Promise { - const handler: MergeEditorCloseHandler[] = [this]; - editors?.forEach(candidate => candidate.editor.closeHandler instanceof MergeEditorCloseHandler && handler.push(candidate.editor.closeHandler)); + const handler: MergeEditorCloseHandler[] = []; + let someAreDirty = false; - const inputsWithUnhandledConflicts = handler - .filter(input => input._model && input._model.hasUnhandledConflicts.get()); + for (const { editor } of editors) { + if (editor.closeHandler instanceof MergeEditorCloseHandler && editor.closeHandler._model.hasUnhandledConflicts.get()) { + handler.push(editor.closeHandler); + someAreDirty = someAreDirty || editor.isDirty(); + } + } - if (inputsWithUnhandledConflicts.length === 0) { + if (handler.length === 0) { // shouldn't happen return ConfirmResult.SAVE; } const actions: string[] = [ - localize('unhandledConflicts.ignore', "Continue with Conflicts"), + someAreDirty ? localize('unhandledConflicts.saveAndIgnore', "Save & Continue with Conflicts") : localize('unhandledConflicts.ignore', "Continue with Conflicts"), localize('unhandledConflicts.discard', "Discard Merge Changes"), localize('unhandledConflicts.cancel', "Cancel"), ]; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts index 55032336cd5..dd5684d1ad8 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts @@ -100,7 +100,7 @@ export class TerminalEditorInput extends EditorInput implements IEditorCloseHand return false; } - async confirm(terminals?: ReadonlyArray): Promise { + async confirm(terminals: ReadonlyArray): Promise { const { choice } = await this._dialogService.show( Severity.Warning, localize('confirmDirtyTerminal.message', "Do you want to terminate running processes?"), @@ -110,7 +110,7 @@ export class TerminalEditorInput extends EditorInput implements IEditorCloseHand ], { cancelId: 1, - detail: terminals && terminals.length > 1 ? + detail: terminals.length > 1 ? terminals.map(terminal => terminal.editor.getName()).join('\n') + '\n\n' + localize('confirmDirtyTerminals.detail', "Closing will terminate the running processes in the terminals.") : localize('confirmDirtyTerminal.detail', "Closing will terminate the running processes in this terminal.") } -- cgit v1.2.3 From 2d126f68851876e1e367dcb42491d5359897a185 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 14 Jul 2022 05:44:54 -0700 Subject: Fix link regex groups Fixes #155065 --- .../contrib/terminal/browser/links/terminalLocalLinkDetector.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts index cec80cf90b1..a8452caf598 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts @@ -53,7 +53,8 @@ export const lineAndColumnClause = [ '((\\S*)[\'"], line ((\\d+)( column (\\d+))?))', // "(file path)", line 45 [see #40468] '((\\S*)[\'"],((\\d+)(:(\\d+))?))', // "(file path)",45 [see #78205] '((\\S*) on line ((\\d+)(, column (\\d+))?))', // (file path) on line 8, column 13 - '((\\S*):\\s?line ((\\d+)(, col(umn)? (\\d+))?))', // (file path):line 8, column 13, (file path): line 8, col 13 + '((\\S*):\\s?line ((\\d+)(, column (\\d+))?))', // (file path):line 8, column 13 + '((\\S*):\\s?line ((\\d+)(, col (\\d+))?))', // (file path): line 8, col 13 '(([^\\s\\(\\)]*)(\\s?[\\(\\[](\\d+)(,\\s?(\\d+))?)[\\)\\]])', // (file path)(45), (file path) (45), (file path)(45,18), (file path) (45,18), (file path)(45, 18), (file path) (45, 18), also with [] '(([^:\\s\\(\\)<>\'\"\\[\\]]*)(:(\\d+))?(:(\\d+))?)' // (file path):336, (file path):336:9 ].join('|').replace(/ /g, `[${'\u00A0'} ]`); -- cgit v1.2.3 From 89f9e79d5dca36f1df4c3aab346233f92d25cc21 Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Thu, 14 Jul 2022 09:08:33 -0400 Subject: Polish New File... based on feedback (#155115) --- src/vs/workbench/contrib/files/browser/fileCommands.ts | 2 +- src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index 0cea70cc910..d3ae1ad9f6b 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -651,7 +651,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ const editorService = accessor.get(IEditorService); await editorService.openEditor({ - resource: args?.path ? URI.from({ scheme: Schemas.untitled, path: `Untitled-${args.path}` }) : undefined, + resource: args?.path ? URI.from({ scheme: Schemas.untitled, path: args.path }) : undefined, options: { override: args?.viewType, pinned: true diff --git a/src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts b/src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts index 02106a3341c..9bbe2ed1d27 100644 --- a/src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts +++ b/src/vs/workbench/contrib/welcomeViews/common/newFile.contribution.ts @@ -164,6 +164,7 @@ class NewFileTemplatesManager extends Disposable { disposables.add(qp.onDidChangeValue((val: string) => { if (val === '') { + refreshQp(entries); return; } const currentTextEntry: NewFileItem = { -- cgit v1.2.3 From ef1da197c1951b0bf0ae01c4841734f9560901c4 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 14 Jul 2022 16:04:00 +0200 Subject: add clear display language action (#155194) --- .../contrib/extensions/browser/extensionEditor.ts | 5 ++- .../extensions/browser/extensions.contribution.ts | 20 ++++++++- .../extensions/browser/extensionsActions.ts | 50 ++++++++++++++++++++-- .../extensions/browser/media/extensionActions.css | 1 + 4 files changed, 70 insertions(+), 6 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index c8632a19ecb..d155971ec3a 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -29,7 +29,7 @@ import { UpdateAction, ReloadAction, EnableDropDownAction, DisableDropDownAction, ExtensionStatusLabelAction, SetFileIconThemeAction, SetColorThemeAction, RemoteInstallAction, ExtensionStatusAction, LocalInstallAction, ToggleSyncExtensionAction, SetProductIconThemeAction, ActionWithDropDownAction, InstallDropdownAction, InstallingLabelAction, UninstallAction, ExtensionActionWithDropdownActionViewItem, ExtensionDropDownAction, - InstallAnotherVersionAction, ExtensionEditorManageExtensionAction, WebInstallAction, SwitchToPreReleaseVersionAction, SwitchToReleasedVersionAction, MigrateDeprecatedExtensionAction, SetLanguageAction + InstallAnotherVersionAction, ExtensionEditorManageExtensionAction, WebInstallAction, SwitchToPreReleaseVersionAction, SwitchToReleasedVersionAction, MigrateDeprecatedExtensionAction, SetLanguageAction, ClearLanguageAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; @@ -326,10 +326,11 @@ export class ExtensionEditor extends EditorPane { this.instantiationService.createInstance(SetColorThemeAction), this.instantiationService.createInstance(SetFileIconThemeAction), this.instantiationService.createInstance(SetProductIconThemeAction), + this.instantiationService.createInstance(SetLanguageAction), + this.instantiationService.createInstance(ClearLanguageAction), this.instantiationService.createInstance(EnableDropDownAction), this.instantiationService.createInstance(DisableDropDownAction), - this.instantiationService.createInstance(SetLanguageAction), this.instantiationService.createInstance(RemoteInstallAction, false), this.instantiationService.createInstance(LocalInstallAction), this.instantiationService.createInstance(WebInstallAction), diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index f53f6a7de8a..13edddae38d 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -15,7 +15,7 @@ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWo import { IOutputChannelRegistry, Extensions as OutputExtensions } from 'vs/workbench/services/output/common/output'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { VIEWLET_ID, IExtensionsWorkbenchService, IExtensionsViewPaneContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID, INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID, DefaultViewsContext, ExtensionsSortByContext, WORKSPACE_RECOMMENDATIONS_VIEW_ID, IWorkspaceRecommendedExtensionsView, AutoUpdateConfigurationKey, HasOutdatedExtensionsContext, SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID, LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID, ExtensionEditorTab, THEME_ACTIONS_GROUP, INSTALL_ACTIONS_GROUP } from 'vs/workbench/contrib/extensions/common/extensions'; -import { ReinstallAction, InstallSpecificVersionOfExtensionAction, ConfigureWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, PromptExtensionInstallFailureAction, SearchExtensionsAction, SwitchToPreReleaseVersionAction, SwitchToReleasedVersionAction, SetColorThemeAction, SetFileIconThemeAction, SetProductIconThemeAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; +import { ReinstallAction, InstallSpecificVersionOfExtensionAction, ConfigureWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, PromptExtensionInstallFailureAction, SearchExtensionsAction, SwitchToPreReleaseVersionAction, SwitchToReleasedVersionAction, SetColorThemeAction, SetFileIconThemeAction, SetProductIconThemeAction, ClearLanguageAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; import { ExtensionEditor } from 'vs/workbench/contrib/extensions/browser/extensionEditor'; import { StatusUpdater, MaliciousExtensionChecker, ExtensionsViewletViewsContribution, ExtensionsViewPaneContainer } from 'vs/workbench/contrib/extensions/browser/extensionsViewlet'; @@ -1339,6 +1339,24 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi } } }); + this.registerExtensionAction({ + id: ClearLanguageAction.ID, + title: ClearLanguageAction.TITLE, + menu: { + id: MenuId.ExtensionContext, + group: INSTALL_ACTIONS_GROUP, + order: 0, + when: ContextKeyExpr.and(ContextKeyExpr.not('inExtensionEditor'), ContextKeyExpr.has('canSetLanguage'), ContextKeyExpr.has('isActiveLanguagePackExtension')) + }, + run: async (accessor: ServicesAccessor, extensionId: string) => { + const instantiationService = accessor.get(IInstantiationService); + const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService); + const extension = (await extensionsWorkbenchService.getExtensions([{ id: extensionId }], CancellationToken.None))[0]; + const action = instantiationService.createInstance(ClearLanguageAction); + action.extension = extension; + return action.run(); + } + }); this.registerExtensionAction({ id: 'workbench.extensions.action.copyExtension', diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 23ddb1a771c..d2247bf2519 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -67,6 +67,7 @@ import { flatten } from 'vs/base/common/arrays'; import { fromNow } from 'vs/base/common/date'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks'; +import { ILocaleService } from 'vs/workbench/contrib/localization/common/locale'; export class PromptExtensionInstallFailureAction extends Action { @@ -981,6 +982,8 @@ export class DropDownMenuActionViewItem extends ActionViewItem { async function getContextMenuActionsGroups(extension: IExtension | undefined | null, contextKeyService: IContextKeyService, instantiationService: IInstantiationService): Promise<[string, Array][]> { return instantiationService.invokeFunction(async accessor => { + const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService); + const languagePackService = accessor.get(ILanguagePackService); const menuService = accessor.get(IMenuService); const extensionRecommendationsService = accessor.get(IExtensionRecommendationsService); const extensionIgnoredRecommendationsService = accessor.get(IExtensionIgnoredRecommendationsService); @@ -1006,6 +1009,9 @@ async function getContextMenuActionsGroups(extension: IExtension | undefined | n cksOverlay.push(['extensionHasColorThemes', colorThemes.some(theme => isThemeFromExtension(theme, extension))]); cksOverlay.push(['extensionHasFileIconThemes', fileIconThemes.some(theme => isThemeFromExtension(theme, extension))]); cksOverlay.push(['extensionHasProductIconThemes', productIconThemes.some(theme => isThemeFromExtension(theme, extension))]); + + cksOverlay.push(['canSetLanguage', extensionsWorkbenchService.canSetLanguage(extension)]); + cksOverlay.push(['isActiveLanguagePackExtension', extension.gallery && language === languagePackService.getLocale(extension.gallery)]); } const menu = menuService.createMenu(MenuId.ExtensionContext, contextKeyService.createOverlay(cksOverlay)); @@ -1791,10 +1797,10 @@ export class SetProductIconThemeAction extends ExtensionAction { export class SetLanguageAction extends ExtensionAction { - static readonly ID = 'workbench.extensions.action.setLanguageTheme'; - static readonly TITLE = { value: localize('workbench.extensions.action.setLanguageTheme', "Set Display Language"), original: 'Set Display Language' }; + static readonly ID = 'workbench.extensions.action.setDisplayLanguage'; + static readonly TITLE = { value: localize('workbench.extensions.action.setDisplayLanguage', "Set Display Language"), original: 'Set Display Language' }; - private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} theme`; + private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} language`; private static readonly DisabledClass = `${SetLanguageAction.EnabledClass} disabled`; constructor( @@ -1826,6 +1832,44 @@ export class SetLanguageAction extends ExtensionAction { } } +export class ClearLanguageAction extends ExtensionAction { + + static readonly ID = 'workbench.extensions.action.clearLanguage'; + static readonly TITLE = { value: localize('workbench.extensions.action.clearLanguage', "Clear Display Language"), original: 'Clear Display Language' }; + + private static readonly EnabledClass = `${ExtensionAction.LABEL_ACTION_CLASS} language`; + private static readonly DisabledClass = `${ClearLanguageAction.EnabledClass} disabled`; + + constructor( + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @ILanguagePackService private readonly languagePackService: ILanguagePackService, + @ILocaleService private readonly localeService: ILocaleService, + ) { + super(ClearLanguageAction.ID, ClearLanguageAction.TITLE.value, ClearLanguageAction.DisabledClass, false); + this.update(); + } + + update(): void { + this.enabled = false; + this.class = ClearLanguageAction.DisabledClass; + if (!this.extension) { + return; + } + if (!this.extensionsWorkbenchService.canSetLanguage(this.extension)) { + return; + } + if (this.extension.gallery && language !== this.languagePackService.getLocale(this.extension.gallery)) { + return; + } + this.enabled = true; + this.class = ClearLanguageAction.EnabledClass; + } + + override async run(): Promise { + return this.extension && this.localeService.clearLocalePreference(); + } +} + export class ShowRecommendedExtensionAction extends Action { static readonly ID = 'workbench.extensions.action.showRecommendedExtension'; diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css b/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css index 13d0d87abca..b50af123be2 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css @@ -54,6 +54,7 @@ .monaco-action-bar .action-item.disabled .action-label.extension-action.update, .monaco-action-bar .action-item.disabled .action-label.extension-action.migrate, .monaco-action-bar .action-item.disabled .action-label.extension-action.theme, +.monaco-action-bar .action-item.disabled .action-label.extension-action.language, .monaco-action-bar .action-item.disabled .action-label.extension-action.extension-sync, .monaco-action-bar .action-item.action-dropdown-item.disabled, .monaco-action-bar .action-item.action-dropdown-item .action-label.extension-action.hide, -- cgit v1.2.3 From 50229828292ccc02a8270aed44c7b70c20ead95c Mon Sep 17 00:00:00 2001 From: Ladislau Szomoru <3372902+lszomoru@users.noreply.github.com> Date: Thu, 14 Jul 2022 16:07:06 +0200 Subject: Settings editor - Boolean setting description should take up the full width instead of just 60% (#155195) Boolean setting description should take up the full width instead of just 60% --- src/vs/workbench/contrib/preferences/browser/media/settingsWidgets.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsWidgets.css b/src/vs/workbench/contrib/preferences/browser/media/settingsWidgets.css index f92d003cc0d..ac9b41bff4c 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/settingsWidgets.css +++ b/src/vs/workbench/contrib/preferences/browser/media/settingsWidgets.css @@ -30,7 +30,8 @@ .settings-editor > .settings-body .settings-tree-container .setting-item-bool .setting-list-object-input-key-checkbox .setting-value-checkbox { margin-top: 3px; } -.settings-editor > .settings-body .settings-tree-container .setting-item-bool .setting-list-object-value { +.settings-editor > .settings-body .settings-tree-container .setting-item.setting-item-list .setting-list-object-widget .setting-item-bool .setting-list-object-value { + width: 100%; cursor: pointer; } -- cgit v1.2.3 From 8f58d9df260144d72545f66a964e88232d751f5f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 14 Jul 2022 08:12:12 -0700 Subject: Use a non-capturing group to fix group indexes --- .../contrib/terminal/browser/links/terminalLocalLinkDetector.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts index a8452caf598..dd4e9840004 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLocalLinkDetector.ts @@ -53,8 +53,7 @@ export const lineAndColumnClause = [ '((\\S*)[\'"], line ((\\d+)( column (\\d+))?))', // "(file path)", line 45 [see #40468] '((\\S*)[\'"],((\\d+)(:(\\d+))?))', // "(file path)",45 [see #78205] '((\\S*) on line ((\\d+)(, column (\\d+))?))', // (file path) on line 8, column 13 - '((\\S*):\\s?line ((\\d+)(, column (\\d+))?))', // (file path):line 8, column 13 - '((\\S*):\\s?line ((\\d+)(, col (\\d+))?))', // (file path): line 8, col 13 + '((\\S*):\\s?line ((\\d+)(, col(?:umn)? (\\d+))?))', // (file path):line 8, column 13, (file path): line 8, col 13 '(([^\\s\\(\\)]*)(\\s?[\\(\\[](\\d+)(,\\s?(\\d+))?)[\\)\\]])', // (file path)(45), (file path) (45), (file path)(45,18), (file path) (45,18), (file path)(45, 18), (file path) (45, 18), also with [] '(([^:\\s\\(\\)<>\'\"\\[\\]]*)(:(\\d+))?(:(\\d+))?)' // (file path):336, (file path):336:9 ].join('|').replace(/ /g, `[${'\u00A0'} ]`); -- cgit v1.2.3 From 39c38c884aaffa7b5db13e4fdebff8eba0b7d876 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Thu, 14 Jul 2022 10:21:57 -0700 Subject: Re #154948. No MenuItemAction in notebook (#155096) --- .../interactive/browser/interactiveEditor.ts | 1 + .../notebook/browser/controller/editActions.ts | 37 +++++++----------- .../contrib/notebook/browser/notebookBrowser.ts | 1 + .../notebook/browser/notebookEditorWidget.ts | 1 + .../browser/view/cellParts/cellToolbars.ts | 45 +++++++++++----------- .../browser/view/renderers/cellRenderer.ts | 2 + 6 files changed, 42 insertions(+), 45 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts index 3462892ee8a..549ca51417f 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts @@ -336,6 +336,7 @@ export class InteractiveEditor extends EditorPane { menuIds: { notebookToolbar: MenuId.InteractiveToolbar, cellTitleToolbar: MenuId.InteractiveCellTitle, + cellDeleteToolbar: MenuId.InteractiveCellDelete, cellInsertToolbar: MenuId.NotebookCellBetween, cellTopInsertToolbar: MenuId.NotebookCellListTop, cellExecuteToolbar: MenuId.InteractiveCellExecute, diff --git a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts index 1f64bddb5e3..06e089a7c6b 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/editActions.ts @@ -11,9 +11,8 @@ import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; import { IModelService } from 'vs/editor/common/services/model'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { localize } from 'vs/nls'; -import { MenuId, MenuItemAction, registerAction2 } from 'vs/platform/actions/common/actions'; -import { ICommandService } from 'vs/platform/commands/common/commands'; -import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContext, InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -35,26 +34,6 @@ const EDIT_CELL_COMMAND_ID = 'notebook.cell.edit'; const DELETE_CELL_COMMAND_ID = 'notebook.cell.delete'; const CLEAR_CELL_OUTPUTS_COMMAND_ID = 'notebook.cell.clearOutputs'; -export class DeleteCellAction extends MenuItemAction { - constructor( - @IContextKeyService contextKeyService: IContextKeyService, - @ICommandService commandService: ICommandService - ) { - super( - { - id: DELETE_CELL_COMMAND_ID, - title: localize('notebookActions.deleteCell', "Delete Cell"), - icon: icons.deleteCellIcon, - precondition: NOTEBOOK_EDITOR_EDITABLE.isEqualTo(true) - }, - undefined, - { shouldForwardArgs: true }, - undefined, - contextKeyService, - commandService); - } -} - registerAction2(class EditCellAction extends NotebookCellAction { constructor() { super( @@ -158,6 +137,18 @@ registerAction2(class DeleteCellAction extends NotebookCellAction { when: ContextKeyExpr.and(NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_EDITABLE, ContextKeyExpr.not(InputFocusedContextKey)), weight: KeybindingWeight.WorkbenchContrib }, + menu: [ + { + id: MenuId.NotebookCellDelete, + when: NOTEBOOK_EDITOR_EDITABLE, + group: CELL_TITLE_CELL_GROUP_ID + }, + { + id: MenuId.InteractiveCellDelete, + when: NOTEBOOK_EDITOR_EDITABLE, + group: CELL_TITLE_CELL_GROUP_ID + } + ], icon: icons.deleteCellIcon }); } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 84614d859fe..9b245482c3d 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -325,6 +325,7 @@ export interface INotebookEditorCreationOptions { readonly menuIds: { notebookToolbar: MenuId; cellTitleToolbar: MenuId; + cellDeleteToolbar: MenuId; cellInsertToolbar: MenuId; cellTopInsertToolbar: MenuId; cellExecuteToolbar: MenuId; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 3debc2599a6..6402451333d 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -190,6 +190,7 @@ export function getDefaultNotebookCreationOptions(): INotebookEditorCreationOpti menuIds: { notebookToolbar: MenuId.NotebookToolbar, cellTitleToolbar: MenuId.NotebookCellTitle, + cellDeleteToolbar: MenuId.NotebookCellDelete, cellInsertToolbar: MenuId.NotebookCellBetween, cellTopInsertToolbar: MenuId.NotebookCellListTop, cellExecuteToolbar: MenuId.NotebookCellExecute, diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts index 35502e4e1a9..9b3a6397be2 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts @@ -18,7 +18,6 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotebookCellActionContext } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; -import { DeleteCellAction } from 'vs/workbench/contrib/notebook/browser/controller/editActions'; import { ICellViewModel, INotebookEditorDelegate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CodiconActionViewItem } from 'vs/workbench/contrib/notebook/browser/view/cellParts/cellActionView'; import { CellPart } from 'vs/workbench/contrib/notebook/browser/view/cellPart'; @@ -93,22 +92,23 @@ export interface ICssClassDelegate { export class CellTitleToolbarPart extends CellPart { private _toolbar: ToolBar; - private _deleteToolbar: ToolBar; private _titleMenu: IMenu; - private _actionsDisposables = this._register(new DisposableStore()); - - private _hasActions = false; + private _deleteToolbar: ToolBar; + private _deleteMenu: IMenu; + private _toolbarActionsDisposables = this._register(new DisposableStore()); + private _deleteActionsDisposables = this._register(new DisposableStore()); private readonly _onDidUpdateActions: Emitter = this._register(new Emitter()); readonly onDidUpdateActions: Event = this._onDidUpdateActions.event; get hasActions(): boolean { - return this._hasActions; + return this._toolbar.getItemsLength() + this._deleteToolbar.getItemsLength() > 0; } constructor( private readonly toolbarContainer: HTMLElement, private readonly _rootClassDelegate: ICssClassDelegate, toolbarId: MenuId, + deleteToolbarId: MenuId, private readonly _notebookEditor: INotebookEditorDelegate, @IContextKeyService contextKeyService: IContextKeyService, @IMenuService menuService: IMenuService, @@ -120,11 +120,14 @@ export class CellTitleToolbarPart extends CellPart { this._titleMenu = this._register(menuService.createMenu(toolbarId, contextKeyService)); this._deleteToolbar = this._register(instantiationService.invokeFunction(accessor => createToolbar(accessor, toolbarContainer, 'cell-delete-toolbar'))); + this._deleteMenu = this._register(menuService.createMenu(deleteToolbarId, contextKeyService)); if (!this._notebookEditor.creationOptions.isReadOnly) { - this._deleteToolbar.setActions([instantiationService.createInstance(DeleteCellAction)]); + const deleteActions = getCellToolbarActions(this._deleteMenu); + this._deleteToolbar.setActions(deleteActions.primary, deleteActions.secondary); } - this.setupChangeListeners(); + this.setupChangeListeners(this._toolbar, this._titleMenu, this._toolbarActionsDisposables); + this.setupChangeListeners(this._deleteToolbar, this._deleteMenu, this._deleteActionsDisposables); } override didRenderCell(element: ICellViewModel): void { @@ -143,22 +146,22 @@ export class CellTitleToolbarPart extends CellPart { this._deleteToolbar.context = toolbarContext; } - private setupChangeListeners(): void { + private setupChangeListeners(toolbar: ToolBar, menu: IMenu, actionDisposables: DisposableStore): void { // #103926 let dropdownIsVisible = false; let deferredUpdate: (() => void) | undefined; - this.updateActions(); - this._register(this._titleMenu.onDidChange(() => { + this.updateActions(toolbar, menu, actionDisposables); + this._register(menu.onDidChange(() => { if (dropdownIsVisible) { - deferredUpdate = () => this.updateActions(); + deferredUpdate = () => this.updateActions(toolbar, menu, actionDisposables); return; } - this.updateActions(); + this.updateActions(toolbar, menu, actionDisposables); })); this._rootClassDelegate.toggle('cell-toolbar-dropdown-active', false); - this._register(this._toolbar.onDidChangeDropdownVisibility(visible => { + this._register(toolbar.onDidChangeDropdownVisibility(visible => { dropdownIsVisible = visible; this._rootClassDelegate.toggle('cell-toolbar-dropdown-active', visible); @@ -172,24 +175,22 @@ export class CellTitleToolbarPart extends CellPart { })); } - private updateActions() { - this._actionsDisposables.clear(); - const actions = getCellToolbarActions(this._titleMenu); - this._actionsDisposables.add(actions.disposable); + private updateActions(toolbar: ToolBar, menu: IMenu, actionDisposables: DisposableStore) { + actionDisposables.clear(); + const actions = getCellToolbarActions(menu); + actionDisposables.add(actions.disposable); - const hadFocus = DOM.isAncestor(document.activeElement, this._toolbar.getElement()); - this._toolbar.setActions(actions.primary, actions.secondary); + const hadFocus = DOM.isAncestor(document.activeElement, toolbar.getElement()); + toolbar.setActions(actions.primary, actions.secondary); if (hadFocus) { this._notebookEditor.focus(); } if (actions.primary.length || actions.secondary.length) { this._rootClassDelegate.toggle('cell-has-toolbar-actions', true); - this._hasActions = true; this._onDidUpdateActions.fire(); } else { this._rootClassDelegate.toggle('cell-has-toolbar-actions', false); - this._hasActions = false; this._onDidUpdateActions.fire(); } } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts index 2bcf55c8531..77ed53a9ce3 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts @@ -160,6 +160,7 @@ export class MarkupCellRenderer extends AbstractCellRenderer implements IListRen titleToolbarContainer, rootClassDelegate, this.notebookEditor.creationOptions.menuIds.cellTitleToolbar, + this.notebookEditor.creationOptions.menuIds.cellDeleteToolbar, this.notebookEditor)); const focusIndicatorBottom = new FastDomNode(DOM.append(container, $('.cell-focus-indicator.cell-focus-indicator-bottom'))); @@ -299,6 +300,7 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende titleToolbarContainer, rootClassDelegate, this.notebookEditor.creationOptions.menuIds.cellTitleToolbar, + this.notebookEditor.creationOptions.menuIds.cellDeleteToolbar, this.notebookEditor)); const focusIndicatorPart = templateDisposables.add(new CellFocusIndicator(this.notebookEditor, titleToolbar, focusIndicatorTop, focusIndicatorLeft, focusIndicatorRight, focusIndicatorBottom)); -- cgit v1.2.3 From 0d9db9f16eccf212ed3ecb15cfb5dc5361133577 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 14 Jul 2022 11:40:06 -0700 Subject: Don't ask debug adapter for initial configs when opening launch.json to add a dynamic config (#155215) Fixes #153388 --- .../workbench/contrib/debug/browser/debugCommands.ts | 2 +- .../contrib/debug/browser/debugConfigurationManager.ts | 18 ++++++++++-------- .../contrib/debug/browser/debugQuickAccess.ts | 2 +- src/vs/workbench/contrib/debug/browser/debugService.ts | 6 +++--- src/vs/workbench/contrib/debug/browser/debugViewlet.ts | 2 +- src/vs/workbench/contrib/debug/common/debug.ts | 2 +- 6 files changed, 17 insertions(+), 15 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index 596bbd9c32d..20390fcd0f3 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -917,7 +917,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ const launch = manager.getLaunches().find(l => l.uri.toString() === launchUri) || manager.selectedConfiguration.launch; if (launch) { - const { editor, created } = await launch.openConfigFile(false); + const { editor, created } = await launch.openConfigFile({ preserveFocus: false }); if (editor && !created) { const codeEditor = editor.getControl(); if (codeEditor) { diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index 0245dbd5a2d..a00d75ef26a 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -215,7 +215,7 @@ export class ConfigurationManager implements IConfigurationManager { disposables.add(input.onDidTriggerItemButton(async (context) => { resolve(undefined); const { launch, config } = context.item; - await launch.openConfigFile(false, config.type); + await launch.openConfigFile({ preserveFocus: false, type: config.type }); // Only Launch have a pin trigger button await (launch as Launch).writeConfiguration(config); await this.selectConfiguration(launch, config.name); @@ -521,11 +521,13 @@ abstract class AbstractLaunch { return configuration; } - async getInitialConfigurationContent(folderUri?: uri, type?: string, token?: CancellationToken): Promise { + async getInitialConfigurationContent(folderUri?: uri, type?: string, useInitialConfigs?: boolean, token?: CancellationToken): Promise { let content = ''; const adapter = type ? this.adapterManager.getEnabledDebugger(type) : await this.adapterManager.guessDebugger(true); if (adapter) { - const initialConfigs = await this.configurationManager.provideDebugConfigurations(folderUri, adapter.type, token || CancellationToken.None); + const initialConfigs = useInitialConfigs ? + await this.configurationManager.provideDebugConfigurations(folderUri, adapter.type, token || CancellationToken.None) : + []; content = await adapter.getInitialConfigurationContent(initialConfigs); } return content; @@ -562,7 +564,7 @@ class Launch extends AbstractLaunch implements ILaunch { return this.configurationService.inspect('launch', { resource: this.workspace.uri }).workspaceFolderValue; } - async openConfigFile(preserveFocus: boolean, type?: string, token?: CancellationToken): Promise<{ editor: IEditorPane | null; created: boolean }> { + async openConfigFile({ preserveFocus, type, useInitialConfigs }: { preserveFocus: boolean; type?: string; useInitialConfigs?: boolean }, token?: CancellationToken): Promise<{ editor: IEditorPane | null; created: boolean }> { const resource = this.uri; let created = false; let content = ''; @@ -571,7 +573,7 @@ class Launch extends AbstractLaunch implements ILaunch { content = fileContent.value.toString(); } catch { // launch.json not found: create one by collecting launch configs from debugConfigProviders - content = await this.getInitialConfigurationContent(this.workspace.uri, type, token); + content = await this.getInitialConfigurationContent(this.workspace.uri, type, useInitialConfigs, token); if (!content) { // Cancelled return { editor: null, created: false }; @@ -647,11 +649,11 @@ class WorkspaceLaunch extends AbstractLaunch implements ILaunch { return this.configurationService.inspect('launch').workspaceValue; } - async openConfigFile(preserveFocus: boolean, type?: string, token?: CancellationToken): Promise<{ editor: IEditorPane | null; created: boolean }> { + async openConfigFile({ preserveFocus, type, useInitialConfigs }: { preserveFocus: boolean; type?: string; useInitialConfigs?: boolean }, token?: CancellationToken): Promise<{ editor: IEditorPane | null; created: boolean }> { const launchExistInFile = !!this.getConfig(); if (!launchExistInFile) { // Launch property in workspace config not found: create one by collecting launch configs from debugConfigProviders - const content = await this.getInitialConfigurationContent(undefined, type, token); + const content = await this.getInitialConfigurationContent(undefined, type, useInitialConfigs, token); if (content) { await this.configurationService.updateValue('launch', json.parse(content), ConfigurationTarget.WORKSPACE); } else { @@ -702,7 +704,7 @@ class UserLaunch extends AbstractLaunch implements ILaunch { return this.configurationService.inspect('launch').userValue; } - async openConfigFile(preserveFocus: boolean): Promise<{ editor: IEditorPane | null; created: boolean }> { + async openConfigFile({ preserveFocus, type, useInitialContent }: { preserveFocus: boolean; type?: string; useInitialContent?: boolean }): Promise<{ editor: IEditorPane | null; created: boolean }> { const editor = await this.preferencesService.openUserSettings({ jsonEditor: true, preserveFocus, revealSetting: { key: 'launch' } }); return ({ editor: withUndefinedAsNull(editor), diff --git a/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts b/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts index df5f79031ea..6cd33d34f5c 100644 --- a/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts +++ b/src/vs/workbench/contrib/debug/browser/debugQuickAccess.ts @@ -63,7 +63,7 @@ export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider { - config.launch.openConfigFile(false); + config.launch.openConfigFile({ preserveFocus: false, useInitialConfigs: false }); return TriggerAction.CLOSE_PICKER; }, diff --git a/src/vs/workbench/contrib/debug/browser/debugService.ts b/src/vs/workbench/contrib/debug/browser/debugService.ts index 2474922a5ad..f6db1acdb80 100644 --- a/src/vs/workbench/contrib/debug/browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/browser/debugService.ts @@ -474,7 +474,7 @@ export class DebugService implements IDebugService { const cfg = await this.configurationManager.resolveDebugConfigurationWithSubstitutedVariables(launch && launch.workspace ? launch.workspace.uri : undefined, type, resolvedConfig, initCancellationToken.token); if (!cfg) { if (launch && type && cfg === null && !initCancellationToken.token.isCancellationRequested) { // show launch.json only for "config" being "null". - await launch.openConfigFile(true, type, initCancellationToken.token); + await launch.openConfigFile({ preserveFocus: true, type }, initCancellationToken.token); } return false; } @@ -526,7 +526,7 @@ export class DebugService implements IDebugService { await this.showError(nls.localize('noFolderWorkspaceDebugError', "The active file can not be debugged. Make sure it is saved and that you have a debug extension installed for that file type.")); } if (launch && !initCancellationToken.token.isCancellationRequested) { - await launch.openConfigFile(true, undefined, initCancellationToken.token); + await launch.openConfigFile({ preserveFocus: true }, initCancellationToken.token); } return false; @@ -534,7 +534,7 @@ export class DebugService implements IDebugService { } if (launch && type && configByProviders === null && !initCancellationToken.token.isCancellationRequested) { // show launch.json only for "config" being "null". - await launch.openConfigFile(true, type, initCancellationToken.token); + await launch.openConfigFile({ preserveFocus: true, type }, initCancellationToken.token); } return false; diff --git a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts index ae38d059d88..ca8b89ac152 100644 --- a/src/vs/workbench/contrib/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts @@ -231,7 +231,7 @@ registerAction2(class extends Action2 { } if (launch) { - await launch.openConfigFile(false); + await launch.openConfigFile({ preserveFocus: false }); } } }); diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 21dc92d475c..ca1c5e318d2 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -927,7 +927,7 @@ export interface ILaunch { /** * Opens the launch.json file. Creates if it does not exist. */ - openConfigFile(preserveFocus: boolean, type?: string, token?: CancellationToken): Promise<{ editor: IEditorPane | null; created: boolean }>; + openConfigFile(options: { preserveFocus: boolean; type?: string; useInitialConfigs?: boolean }, token?: CancellationToken): Promise<{ editor: IEditorPane | null; created: boolean }>; } // Debug service interfaces -- cgit v1.2.3 From 442f0bed1ad793ab973348906e75296f2d177b8d Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Thu, 14 Jul 2022 11:42:04 -0700 Subject: Fix: do not delete partially applied edit sessions (#155218) --- .../workbench/contrib/editSessions/browser/editSessions.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts index e292a79c084..34d89d716a2 100644 --- a/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts +++ b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts @@ -270,7 +270,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo const folderRoot = this.contextService.getWorkspace().folders.find((f) => f.name === folder.name); if (!folderRoot) { this.logService.info(`Skipping applying ${folder.workingChanges.length} changes from edit session with ref ${ref} as no corresponding workspace folder named ${folder.name} is currently open.`); - continue; + return; } for (const repository of this.scmService.repositories) { -- cgit v1.2.3 From 4b6b842d031ccfcbc27d23093ee7cfda6a674fb5 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Thu, 14 Jul 2022 12:30:49 -0700 Subject: Fix unhandled promise rejections for debug commands (#155220) Fixes #154763 --- .../contrib/debug/browser/debugCommands.ts | 42 +++++++++++----------- 1 file changed, 20 insertions(+), 22 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index 20390fcd0f3..92b3b970f07 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -304,27 +304,27 @@ CommandsRegistry.registerCommand({ CommandsRegistry.registerCommand({ id: REVERSE_CONTINUE_ID, - handler: (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => { - getThreadAndRun(accessor, context, thread => thread.reverseContinue()); + handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => { + await getThreadAndRun(accessor, context, thread => thread.reverseContinue()); } }); CommandsRegistry.registerCommand({ id: STEP_BACK_ID, - handler: (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => { + handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => { const contextKeyService = accessor.get(IContextKeyService); if (CONTEXT_DISASSEMBLY_VIEW_FOCUS.getValue(contextKeyService)) { - getThreadAndRun(accessor, context, (thread: IThread) => thread.stepBack('instruction')); + await getThreadAndRun(accessor, context, (thread: IThread) => thread.stepBack('instruction')); } else { - getThreadAndRun(accessor, context, (thread: IThread) => thread.stepBack()); + await getThreadAndRun(accessor, context, (thread: IThread) => thread.stepBack()); } } }); CommandsRegistry.registerCommand({ id: TERMINATE_THREAD_ID, - handler: (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => { - getThreadAndRun(accessor, context, thread => thread.terminate()); + handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => { + await getThreadAndRun(accessor, context, thread => thread.terminate()); } }); @@ -467,12 +467,12 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ weight: KeybindingWeight.WorkbenchContrib, primary: isWeb ? (KeyMod.Alt | KeyCode.F10) : KeyCode.F10, // Browsers do not allow F10 to be binded so we have to bind an alternative when: CONTEXT_DEBUG_STATE.isEqualTo('stopped'), - handler: (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => { + handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => { const contextKeyService = accessor.get(IContextKeyService); if (CONTEXT_DISASSEMBLY_VIEW_FOCUS.getValue(contextKeyService)) { - getThreadAndRun(accessor, context, (thread: IThread) => thread.next('instruction')); + await getThreadAndRun(accessor, context, (thread: IThread) => thread.next('instruction')); } else { - getThreadAndRun(accessor, context, (thread: IThread) => thread.next()); + await getThreadAndRun(accessor, context, (thread: IThread) => thread.next()); } } }); @@ -486,12 +486,12 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ primary: STEP_INTO_KEYBINDING, // Use a more flexible when clause to not allow full screen command to take over when F11 pressed a lot of times when: CONTEXT_DEBUG_STATE.notEqualsTo('inactive'), - handler: (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => { + handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => { const contextKeyService = accessor.get(IContextKeyService); if (CONTEXT_DISASSEMBLY_VIEW_FOCUS.getValue(contextKeyService)) { - getThreadAndRun(accessor, context, (thread: IThread) => thread.stepIn('instruction')); + await getThreadAndRun(accessor, context, (thread: IThread) => thread.stepIn('instruction')); } else { - getThreadAndRun(accessor, context, (thread: IThread) => thread.stepIn()); + await getThreadAndRun(accessor, context, (thread: IThread) => thread.stepIn()); } } }); @@ -501,12 +501,12 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.Shift | KeyCode.F11, when: CONTEXT_DEBUG_STATE.isEqualTo('stopped'), - handler: (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => { + handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => { const contextKeyService = accessor.get(IContextKeyService); if (CONTEXT_DISASSEMBLY_VIEW_FOCUS.getValue(contextKeyService)) { - getThreadAndRun(accessor, context, (thread: IThread) => thread.stepOut('instruction')); + await getThreadAndRun(accessor, context, (thread: IThread) => thread.stepOut('instruction')); } else { - getThreadAndRun(accessor, context, (thread: IThread) => thread.stepOut()); + await getThreadAndRun(accessor, context, (thread: IThread) => thread.stepOut()); } } }); @@ -516,8 +516,8 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ weight: KeybindingWeight.WorkbenchContrib + 2, // take priority over focus next part while we are debugging primary: KeyCode.F6, when: CONTEXT_DEBUG_STATE.isEqualTo('running'), - handler: (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => { - getThreadAndRun(accessor, context, thread => thread.pause()); + handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => { + await getThreadAndRun(accessor, context, thread => thread.pause()); } }); @@ -649,17 +649,15 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ weight: KeybindingWeight.WorkbenchContrib + 10, // Use a stronger weight to get priority over start debugging F5 shortcut primary: KeyCode.F5, when: CONTEXT_DEBUG_STATE.isEqualTo('stopped'), - handler: (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => { - getThreadAndRun(accessor, context, thread => thread.continue()); + handler: async (accessor: ServicesAccessor, _: string, context: CallStackContext | unknown) => { + await getThreadAndRun(accessor, context, thread => thread.continue()); } }); CommandsRegistry.registerCommand({ id: SHOW_LOADED_SCRIPTS_ID, handler: async (accessor) => { - await showLoadedScriptMenu(accessor); - } }); -- cgit v1.2.3 From 29a4da129d1a68de066ffa79d7aad2906f50502a Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Thu, 14 Jul 2022 13:46:30 -0700 Subject: remove terminal data when terminal is disposed of for reconnected terminals (#155233) --- .../contrib/tasks/browser/abstractTaskService.ts | 12 ++- .../contrib/tasks/browser/terminalTaskSystem.ts | 88 +++++++++++++--------- 2 files changed, 60 insertions(+), 40 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 33f885dfe72..b1369374838 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -200,6 +200,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer private static _nextHandle: number = 0; + private _tasksReconnected: boolean = false; private _schemaVersion: JsonSchemaVersion | undefined; private _executionEngine: ExecutionEngine | undefined; private _workspaceFolders: IWorkspaceFolder[] | undefined; @@ -337,11 +338,14 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer processContext.set(process && !isVirtual); } this._onDidRegisterSupportedExecutions.fire(); + if (this._jsonTasksSupported && !this._tasksReconnected) { + this._reconnectTasks(); + } } - private async _restartTasks(): Promise { + private async _reconnectTasks(): Promise { const recentlyUsedTasks = await this.readRecentTasks(); - if (!recentlyUsedTasks) { + if (!recentlyUsedTasks.length) { return; } for (const task of recentlyUsedTasks) { @@ -354,6 +358,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this.run(task, undefined, TaskRunSource.Reconnect); } } + this._tasksReconnected = true; } public get onDidStateChange(): Event { @@ -618,7 +623,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return infosCount > 0; } - public async registerTaskSystem(key: string, info: ITaskSystemInfo): Promise { + public registerTaskSystem(key: string, info: ITaskSystemInfo): void { // Ideally the Web caller of registerRegisterTaskSystem would use the correct key. // However, the caller doesn't know about the workspace folders at the time of the call, even though we know about them here. if (info.platform === Platform.Platform.Web) { @@ -638,7 +643,6 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (this.hasTaskSystemInfo) { this._onDidChangeTaskSystemInfo.fire(); - await this._restartTasks(); } } diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index b46a625ed10..23bc33a957e 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -63,6 +63,8 @@ interface IActiveTerminalData { state?: TaskEventKind; } +const ReconnectionType = 'Task'; + class InstanceManager { private _currentInstances: number = 0; private _counter: number = 0; @@ -255,6 +257,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { this._logService.trace(`Could not reconnect to terminal ${terminal.instanceId} with process details ${terminal.shellLaunchConfig.attachPersistentProcess}`); } } + this._hasReconnected = true; } public get onDidStateChange(): Event { @@ -270,10 +273,13 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { } public reconnect(task: Task, resolver: ITaskResolver, trigger: string = Triggers.command): ITaskExecuteResult | undefined { - const terminals = this._terminalService.getReconnectedTerminals('Task'); + const terminals = this._terminalService.getReconnectedTerminals(ReconnectionType); + if (!terminals || terminals?.length === 0) { + return; + } if (!this._hasReconnected && terminals && terminals.length > 0) { + this._reviveTerminals(); this._reconnectToTerminals(terminals); - this._hasReconnected = true; } if (this._tasksToReconnect.includes(task._id)) { this._lastTask = new VerifiedTask(task, resolver, trigger); @@ -449,12 +455,14 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { } } - private _removeFromActiveTasks(task: Task): void { - if (!this._activeTasks[task.getMapKey()]) { + private _removeFromActiveTasks(task: Task | string): void { + const key = typeof task === 'string' ? task : task.getMapKey(); + if (!this._activeTasks[key]) { return; } - delete this._activeTasks[task.getMapKey()]; - this._removeInstances(task); + const taskToRemove = this._activeTasks[key]; + delete this._activeTasks[key]; + this._removeInstances(taskToRemove.task); } private _fireTaskEvent(event: ITaskEvent) { @@ -1059,7 +1067,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { const isShellCommand = task.command.runtime === RuntimeType.Shell; const needsFolderQualification = this._contextService.getWorkbenchState() === WorkbenchState.WORKSPACE; const terminalName = this._createTerminalName(task); - const type = 'Task'; + const type = ReconnectionType; const originalCommand = task.command.name; if (isShellCommand) { let os: Platform.OperatingSystem; @@ -1289,17 +1297,40 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { } private _reviveTerminals(): void { - if (Object.entries(this._terminals).length === 0) { - for (const terminal of this._terminalService.instances) { - if (terminal.shellLaunchConfig.attachPersistentProcess?.task?.lastTask) { - this._terminals[terminal.instanceId] = { lastTask: terminal.shellLaunchConfig.attachPersistentProcess.task.lastTask, group: terminal.shellLaunchConfig.attachPersistentProcess.task.group, terminal }; - } + if (Object.entries(this._terminals).length > 0) { + return; + } + const terminals = this._terminalService.getReconnectedTerminals(ReconnectionType)?.filter(t => !t.isDisposed); + if (!terminals?.length) { + return; + } + for (const terminal of terminals) { + const task = terminal.shellLaunchConfig.attachPersistentProcess?.task; + if (!task) { + continue; } + const terminalData = { lastTask: task.lastTask, group: task.group, terminal }; + this._terminals[terminal.instanceId] = terminalData; + terminal.onDisposed(() => this._deleteTaskAndTerminal(terminal, terminalData)); + } + } + + private _deleteTaskAndTerminal(terminal: ITerminalInstance, terminalData: ITerminalData): void { + delete this._terminals[terminal.instanceId]; + delete this._sameTaskTerminals[terminalData.lastTask]; + this._idleTaskTerminals.delete(terminalData.lastTask); + // Delete the task now as a work around for cases when the onExit isn't fired. + // This can happen if the terminal wasn't shutdown with an "immediate" flag and is expected. + // For correct terminal re-use, the task needs to be deleted immediately. + // Note that this shouldn't be a problem anymore since user initiated terminal kills are now immediate. + const mapKey = terminalData.lastTask; + this._removeFromActiveTasks(mapKey); + if (this._busyTasks[mapKey]) { + delete this._busyTasks[mapKey]; } } private async _createTerminal(task: CustomTask | ContributedTask, resolver: VariableResolver, workspaceFolder: IWorkspaceFolder | undefined): Promise<[ITerminalInstance | undefined, TaskError | undefined]> { - this._reviveTerminals(); const platform = resolver.taskSystemInfo ? resolver.taskSystemInfo.platform : Platform.platform; const options = await this._resolveOptions(resolver, task.command.options); const presentationOptions = task.command.presentation; @@ -1398,29 +1429,14 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { } this._terminalCreationQueue = this._terminalCreationQueue.then(() => this._doCreateTerminal(group, launchConfigs!)); - const result: ITerminalInstance = (await this._terminalCreationQueue)!; - result.shellLaunchConfig.task = { lastTask: taskKey, group, label: task._label, id: task._id }; - result.shellLaunchConfig.reconnectionOwner = 'Task'; - const terminalKey = result.instanceId.toString(); - result.onDisposed(() => { - const terminalData = this._terminals[terminalKey]; - if (terminalData) { - delete this._terminals[terminalKey]; - delete this._sameTaskTerminals[terminalData.lastTask]; - this._idleTaskTerminals.delete(terminalData.lastTask); - // Delete the task now as a work around for cases when the onExit isn't fired. - // This can happen if the terminal wasn't shutdown with an "immediate" flag and is expected. - // For correct terminal re-use, the task needs to be deleted immediately. - // Note that this shouldn't be a problem anymore since user initiated terminal kills are now immediate. - const mapKey = task.getMapKey(); - this._removeFromActiveTasks(task); - if (this._busyTasks[mapKey]) { - delete this._busyTasks[mapKey]; - } - } - }); - this._terminals[terminalKey] = { terminal: result, lastTask: taskKey, group }; - return [result, undefined]; + const terminal: ITerminalInstance = (await this._terminalCreationQueue)!; + terminal.shellLaunchConfig.task = { lastTask: taskKey, group, label: task._label, id: task._id }; + terminal.shellLaunchConfig.reconnectionOwner = ReconnectionType; + const terminalKey = terminal.instanceId.toString(); + const terminalData = { terminal: terminal, lastTask: taskKey, group }; + terminal.onDisposed(() => this._deleteTaskAndTerminal(terminal, terminalData)); + this._terminals[terminalKey] = terminalData; + return [terminal, undefined]; } private _buildShellCommandLine(platform: Platform.Platform, shellExecutable: string, shellOptions: IShellConfiguration | undefined, command: CommandString, originalCommand: CommandString | undefined, args: CommandString[]): string { -- cgit v1.2.3 From 171537fd736f214405c1d8d45e03c7677b438d66 Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Thu, 14 Jul 2022 18:14:35 -0700 Subject: fix command not found warning (#155250) fix command not found part of #155163 --- src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts | 3 ++- src/vs/workbench/contrib/tasks/browser/task.contribution.ts | 4 ++-- src/vs/workbench/contrib/tasks/common/taskService.ts | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index b1369374838..4bfc497f05a 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -57,7 +57,7 @@ import { TaskSettingId, TasksSchemaProperties } from 'vs/workbench/contrib/tasks/common/tasks'; -import { ITaskService, ITaskProvider, IProblemMatcherRunOptions, ICustomizationProperties, ITaskFilter, IWorkspaceFolderTaskResult, CustomExecutionSupportedContext, ShellExecutionSupportedContext, ProcessExecutionSupportedContext } from 'vs/workbench/contrib/tasks/common/taskService'; +import { ITaskService, ITaskProvider, IProblemMatcherRunOptions, ICustomizationProperties, ITaskFilter, IWorkspaceFolderTaskResult, CustomExecutionSupportedContext, ShellExecutionSupportedContext, ProcessExecutionSupportedContext, TaskCommandsRegistered } from 'vs/workbench/contrib/tasks/common/taskService'; import { getTemplates as getTaskTemplates } from 'vs/workbench/contrib/tasks/common/taskTemplates'; import * as TaskConfig from '../common/taskConfiguration'; @@ -491,6 +491,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this._openTaskFile(resource, TaskSourceKind.WorkspaceFile); } }); + TaskCommandsRegistered.bindTo(this._contextKeyService).set(true); } private get workspaceFolders(): IWorkspaceFolder[] { diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index 00c85294b7b..3b7aa6162af 100644 --- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -21,7 +21,7 @@ import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatus import { IOutputChannelRegistry, Extensions as OutputExt } from 'vs/workbench/services/output/common/output'; import { ITaskEvent, TaskEventKind, TaskGroup, TaskSettingId, TASKS_CATEGORY, TASK_RUNNING_STATE } from 'vs/workbench/contrib/tasks/common/tasks'; -import { ITaskService, ProcessExecutionSupportedContext, ShellExecutionSupportedContext } from 'vs/workbench/contrib/tasks/common/taskService'; +import { ITaskService, ProcessExecutionSupportedContext, ShellExecutionSupportedContext, TaskCommandsRegistered } from 'vs/workbench/contrib/tasks/common/taskService'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { RunAutomaticTasks, ManageAutomaticTaskRunning } from 'vs/workbench/contrib/tasks/browser/runAutomaticTasks'; @@ -359,7 +359,7 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { KeybindingsRegistry.registerKeybindingRule({ id: 'workbench.action.tasks.build', weight: KeybindingWeight.WorkbenchContrib, - when: undefined, + when: TaskCommandsRegistered, primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyB }); diff --git a/src/vs/workbench/contrib/tasks/common/taskService.ts b/src/vs/workbench/contrib/tasks/common/taskService.ts index 86acc2a6a03..afc0ed385fa 100644 --- a/src/vs/workbench/contrib/tasks/common/taskService.ts +++ b/src/vs/workbench/contrib/tasks/common/taskService.ts @@ -19,6 +19,7 @@ export { ITaskSummary, Task, ITaskTerminateResponse as TaskTerminateResponse }; export const CustomExecutionSupportedContext = new RawContextKey('customExecutionSupported', true, nls.localize('tasks.customExecutionSupported', "Whether CustomExecution tasks are supported. Consider using in the when clause of a \'taskDefinition\' contribution.")); export const ShellExecutionSupportedContext = new RawContextKey('shellExecutionSupported', false, nls.localize('tasks.shellExecutionSupported', "Whether ShellExecution tasks are supported. Consider using in the when clause of a \'taskDefinition\' contribution.")); +export const TaskCommandsRegistered = new RawContextKey('taskCommandsRegistered', false, nls.localize('tasks.taskCommandsRegistered', "Whether the task commands have been registered yet")); export const ProcessExecutionSupportedContext = new RawContextKey('processExecutionSupported', false, nls.localize('tasks.processExecutionSupported', "Whether ProcessExecution tasks are supported. Consider using in the when clause of a \'taskDefinition\' contribution.")); export const ITaskService = createDecorator('taskService'); -- cgit v1.2.3 From 1cd90cceddf3c413673963ab6f154d2ff294b17c Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Thu, 14 Jul 2022 18:43:10 -0700 Subject: make sure execute in terminal is reached (#155254) --- src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index 23bc33a957e..0499c4cd0b5 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -192,6 +192,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { private _terminals: IStringDictionary; private _idleTaskTerminals: LinkedMap; private _sameTaskTerminals: IStringDictionary; + private _terminalForTask: ITerminalInstance | undefined; private _taskSystemInfoResolver: ITaskSystemInfoResolver; private _lastTask: VerifiedTask | undefined; // Should always be set in run @@ -282,8 +283,8 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { this._reconnectToTerminals(terminals); } if (this._tasksToReconnect.includes(task._id)) { - this._lastTask = new VerifiedTask(task, resolver, trigger); - this.rerun(); + this._terminalForTask = terminals.find(t => t.shellLaunchConfig.attachPersistentProcess?.task?.id === task._id); + this.run(task, resolver, trigger); } return undefined; } @@ -305,7 +306,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { } try { - const executeResult = { kind: TaskExecuteKind.Started, task, started: {}, promise: this._executeTask(task, resolver, trigger, new Set()) }; + const executeResult = { kind: TaskExecuteKind.Started, task, started: {}, promise: this._executeTask(task, resolver, trigger, new Set(), undefined) }; executeResult.promise.then(summary => { this._lastTask = this._currentTask; }); @@ -880,7 +881,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { })); watchingProblemMatcher.aboutToStart(); let delayer: Async.Delayer | undefined = undefined; - [terminal, error] = await this._createTerminal(task, resolver, workspaceFolder); + [terminal, error] = this._terminalForTask ? [this._terminalForTask, undefined] : await this._createTerminal(task, resolver, workspaceFolder); if (error) { return Promise.reject(new Error((error).message)); @@ -962,7 +963,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { }); }); } else { - [terminal, error] = await this._createTerminal(task, resolver, workspaceFolder); + [terminal, error] = this._terminalForTask ? [this._terminalForTask, undefined] : await this._createTerminal(task, resolver, workspaceFolder); if (error) { return Promise.reject(new Error((error).message)); -- cgit v1.2.3 From 4398625c327bb99f4505e9ce76e076496a26af29 Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 15 Jul 2022 09:59:09 +0200 Subject: * always show top N snippets and conditionally show action for all surroundable snippets * remove `canExecute` which isn't needed - the "framework" makes sure we only called when it makes sense * tweak styles to my preference and lipstick, try to contain things over loose functions and objects, --- .../snippets/browser/surroundWithSnippet.ts | 203 ++++++++++----------- 1 file changed, 95 insertions(+), 108 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts b/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts index edac26fe643..2d7a798594d 100644 --- a/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts +++ b/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts @@ -14,159 +14,146 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { pickSnippet } from 'vs/workbench/contrib/snippets/browser/snippetPicker'; import { ISnippetsService } from './snippets.contribution'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { IDisposable } from 'vs/base/common/lifecycle'; import { ITextModel } from 'vs/editor/common/model'; -import { CodeAction, CodeActionProvider, CodeActionContext, CodeActionList } from 'vs/editor/common/languages'; +import { CodeAction, CodeActionProvider, CodeActionList } from 'vs/editor/common/languages'; import { CodeActionKind } from 'vs/editor/contrib/codeAction/browser/types'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { Range, IRange } from 'vs/editor/common/core/range'; -import { URI } from 'vs/base/common/uri'; import { Selection } from 'vs/editor/common/core/selection'; import { Snippet } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Position } from 'vs/editor/common/core/position'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { EditorInputCapabilities } from 'vs/workbench/common/editor'; - -const options = { - id: 'editor.action.surroundWithSnippet', - title: { - value: localize('label', 'Surround With Snippet...'), - original: 'Surround With Snippet...' - }, - precondition: ContextKeyExpr.and( - EditorContextKeys.writable, - EditorContextKeys.hasNonEmptySelection - ), - f1: true, -}; - -const MAX_SNIPPETS_ON_CODE_ACTIONS_MENU = 6; - -function makeCodeActionForSnippet(snippet: Snippet, resource: URI, range: IRange): CodeAction { - const title = localize('codeAction', "Surround With Snippet: {0}", snippet.name); - return { - title, - edit: { - edits: [ - { - versionId: undefined, - resource: resource, - textEdit: { - insertAsSnippet: true, - text: snippet.body, - range: range - } - } - ] - } - }; -} - -async function getSurroundableSnippets(accessor: ServicesAccessor, model: ITextModel | null, position: Position | null): Promise { - if (!model) { - return []; - } - const snippetsService = accessor.get(ISnippetsService); +async function getSurroundableSnippets(snippetsService: ISnippetsService, model: ITextModel, position: Position): Promise { - let languageId: string; - if (position) { - const { lineNumber, column } = position; - model.tokenization.tokenizeIfCheap(lineNumber); - languageId = model.getLanguageIdAtPosition(lineNumber, column); - } else { - languageId = model.getLanguageId(); - } + const { lineNumber, column } = position; + model.tokenization.tokenizeIfCheap(lineNumber); + const languageId = model.getLanguageIdAtPosition(lineNumber, column); const allSnippets = await snippetsService.getSnippets(languageId, { includeNoPrefixSnippets: true, includeDisabledSnippets: true }); return allSnippets.filter(snippet => snippet.usesSelection); } -function canExecute(accessor: ServicesAccessor): boolean { - const editorService = accessor.get(IEditorService); +class SurroundWithSnippetEditorAction extends EditorAction2 { - const editor = editorService.activeEditor; - if (!editor || editor.hasCapability(EditorInputCapabilities.Readonly)) { - return false; + static readonly options = { + id: 'editor.action.surroundWithSnippet', + title: { + value: localize('label', 'Surround With Snippet...'), + original: 'Surround With Snippet...' + } + }; + + constructor() { + super({ + ...SurroundWithSnippetEditorAction.options, + precondition: ContextKeyExpr.and( + EditorContextKeys.writable, + EditorContextKeys.hasNonEmptySelection + ), + f1: true, + }); } - const selections = editorService.activeTextEditorControl?.getSelections(); - return !!selections && selections.length > 0; -} -async function surroundWithSnippet(accessor: ServicesAccessor, editor: ICodeEditor) { - const instaService = accessor.get(IInstantiationService); - const clipboardService = accessor.get(IClipboardService); + async runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor) { + if (!editor.hasModel()) { + return; + } - if (!canExecute(accessor)) { - return; - } + const instaService = accessor.get(IInstantiationService); + const snippetsService = accessor.get(ISnippetsService); + const clipboardService = accessor.get(IClipboardService); - const snippets = await getSurroundableSnippets(accessor, editor.getModel(), editor.getPosition()); - if (!snippets.length) { - return; - } + const snippets = await getSurroundableSnippets(snippetsService, editor.getModel(), editor.getPosition()); + if (!snippets.length) { + return; + } - const snippet = await instaService.invokeFunction(pickSnippet, snippets); - if (!snippet) { - return; - } + const snippet = await instaService.invokeFunction(pickSnippet, snippets); + if (!snippet) { + return; + } - let clipboardText: string | undefined; - if (snippet.needsClipboard) { - clipboardText = await clipboardService.readText(); - } + let clipboardText: string | undefined; + if (snippet.needsClipboard) { + clipboardText = await clipboardService.readText(); + } - SnippetController2.get(editor)?.insert(snippet.codeSnippet, { clipboardText }); + SnippetController2.get(editor)?.insert(snippet.codeSnippet, { clipboardText }); + } } -registerAction2(class SurroundWithSnippetEditorAction extends EditorAction2 { - constructor() { - super(options); - } - async runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]) { - await surroundWithSnippet(accessor, editor); - } -}); +class SurroundWithSnippetCodeActionProvider implements CodeActionProvider, IWorkbenchContribution { + + private static readonly _MAX_CODE_ACTIONS = 4; -export class SurroundWithSnippetCodeActionProvider extends Disposable implements CodeActionProvider, IWorkbenchContribution { - private static readonly overflowCodeAction: CodeAction = { + private static readonly _overflowCommandCodeAction: CodeAction = { kind: CodeActionKind.Refactor.value, - title: options.title.value, + title: SurroundWithSnippetEditorAction.options.title.value, command: { - id: options.id, - title: options.title.value, + id: SurroundWithSnippetEditorAction.options.id, + title: SurroundWithSnippetEditorAction.options.title.value, }, }; + private readonly _registration: IDisposable; + constructor( + @ISnippetsService private readonly _snippetService: ISnippetsService, @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, - @IInstantiationService private readonly instaService: IInstantiationService, ) { - super(); - this._register(languageFeaturesService.codeActionProvider.register('*', this)); + this._registration = languageFeaturesService.codeActionProvider.register('*', this); } - async provideCodeActions(model: ITextModel, range: Range | Selection, context: CodeActionContext, token: CancellationToken): Promise { - if (!this.instaService.invokeFunction(canExecute)) { - return { actions: [], dispose: () => { } }; - } + dispose(): void { + this._registration.dispose(); + } - const snippets = await this.instaService.invokeFunction(accessor => getSurroundableSnippets(accessor, model, range.getEndPosition())); + async provideCodeActions(model: ITextModel, range: Range | Selection): Promise { + + const snippets = await getSurroundableSnippets(this._snippetService, model, range.getEndPosition()); if (!snippets.length) { - return { actions: [], dispose: () => { } }; + return undefined; + } + + const actions: CodeAction[] = []; + const hasMore = snippets.length > SurroundWithSnippetCodeActionProvider._MAX_CODE_ACTIONS; + const len = Math.min(snippets.length, SurroundWithSnippetCodeActionProvider._MAX_CODE_ACTIONS); + + for (let i = 0; i < len; i++) { + actions.push(this._makeCodeActionForSnippet(snippets[i], model, range)); + } + if (hasMore) { + actions.push(SurroundWithSnippetCodeActionProvider._overflowCommandCodeAction); } return { - actions: snippets.length <= MAX_SNIPPETS_ON_CODE_ACTIONS_MENU - ? snippets.map(x => makeCodeActionForSnippet(x, model.uri, range)) - : [SurroundWithSnippetCodeActionProvider.overflowCodeAction], - dispose: () => { } + actions, + dispose() { } + }; + } + + private _makeCodeActionForSnippet(snippet: Snippet, model: ITextModel, range: IRange): CodeAction { + return { + title: localize('codeAction', "Surround With: {0}", snippet.name), + kind: CodeActionKind.Refactor.value, + edit: { + edits: [{ + versionId: model.getVersionId(), + resource: model.uri, + textEdit: { + range, + text: snippet.body, + insertAsSnippet: true, + } + }] + } }; } } +registerAction2(SurroundWithSnippetEditorAction); Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(SurroundWithSnippetCodeActionProvider, LifecyclePhase.Restored); -- cgit v1.2.3 From d17e30f8b4c315e72f6c3bb537fd7822a25c8001 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 15 Jul 2022 11:44:46 +0200 Subject: Fix #155157 (#155290) --- .../test/electron-browser/experimentService.test.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts index 2dbfdafd694..7c111e51eda 100644 --- a/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts +++ b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts @@ -6,14 +6,13 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; import { timeout } from 'vs/base/common/async'; -import { Emitter } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; import { OS } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; -import { DidUninstallExtensionEvent, IExtensionIdentifier, IExtensionManagementService, ILocalExtension, InstallExtensionEvent, InstallExtensionResult } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { DidUninstallExtensionEvent, IExtensionIdentifier, ILocalExtension, InstallExtensionEvent, InstallExtensionResult } from 'vs/platform/extensionManagement/common/extensionManagement'; import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { IProductService } from 'vs/platform/product/common/productService'; @@ -26,7 +25,8 @@ import { IURLService } from 'vs/platform/url/common/url'; import { NativeURLService } from 'vs/platform/url/common/urlService'; import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; import { currentSchemaVersion, ExperimentActionType, ExperimentService, ExperimentState, getCurrentActivationRecord, IExperiment } from 'vs/workbench/contrib/experiments/common/experimentService'; -import { IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { IWorkbenchExtensionEnablementService, IWorkbenchExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { ExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagementService'; import { TestExtensionEnablementService } from 'vs/workbench/services/extensionManagement/test/browser/extensionEnablementService.test'; import { IExtensionService, IWillActivateEvent } from 'vs/workbench/services/extensions/common/extensions'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; @@ -84,15 +84,16 @@ suite('Experiment Service', () => { instantiationService.stub(IExtensionService, TestExtensionService); instantiationService.stub(IExtensionService, 'onWillActivateByEvent', activationEvent.event); instantiationService.stub(IUriIdentityService, UriIdentityService); - instantiationService.stub(IExtensionManagementService, ExtensionManagementService); - instantiationService.stub(IExtensionManagementService, 'onInstallExtension', installEvent.event); - instantiationService.stub(IExtensionManagementService, 'onDidInstallExtensions', didInstallEvent.event); - instantiationService.stub(IExtensionManagementService, 'onUninstallExtension', uninstallEvent.event); - instantiationService.stub(IExtensionManagementService, 'onDidUninstallExtension', didUninstallEvent.event); + instantiationService.stub(IWorkbenchExtensionManagementService, ExtensionManagementService); + instantiationService.stub(IWorkbenchExtensionManagementService, 'onInstallExtension', installEvent.event); + instantiationService.stub(IWorkbenchExtensionManagementService, 'onDidInstallExtensions', didInstallEvent.event); + instantiationService.stub(IWorkbenchExtensionManagementService, 'onUninstallExtension', uninstallEvent.event); + instantiationService.stub(IWorkbenchExtensionManagementService, 'onDidUninstallExtension', didUninstallEvent.event); + instantiationService.stub(IWorkbenchExtensionManagementService, 'onDidChangeProfileExtensions', Event.None); instantiationService.stub(IWorkbenchExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); instantiationService.stub(ITelemetryService, NullTelemetryService); instantiationService.stub(IURLService, NativeURLService); - instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [local]); + instantiationService.stubPromise(IWorkbenchExtensionManagementService, 'getInstalled', [local]); testConfigurationService = new TestConfigurationService(); instantiationService.stub(IConfigurationService, testConfigurationService); instantiationService.stub(ILifecycleService, new TestLifecycleService()); -- cgit v1.2.3 From ccba8ca4370694000bb6cf9fb3afe0e3ebd33175 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 15 Jul 2022 13:11:09 +0200 Subject: track usage of snippets to support snippet LRU (#155289) enforce that all snippets have an identifier, mark a snippet as used after completing with it or after inserting one, store the last 100 snippet usages per (user, profile) --- .../contrib/snippets/browser/insertSnippet.ts | 2 + .../snippets/browser/snippetCompletionProvider.ts | 18 +++- .../contrib/snippets/browser/snippetPicker.ts | 2 +- .../snippets/browser/snippets.contribution.ts | 3 + .../contrib/snippets/browser/snippetsFile.ts | 24 +---- .../contrib/snippets/browser/snippetsService.ts | 87 +++++++++++++++-- .../snippets/browser/surroundWithSnippet.ts | 1 + .../snippets/test/browser/snippetFile.test.ts | 21 +++-- .../snippets/test/browser/snippetsRewrite.test.ts | 5 +- .../snippets/test/browser/snippetsService.test.ts | 103 +++++++++++++-------- 10 files changed, 183 insertions(+), 83 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts b/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts index 3a0088a1f48..2d84a9a71a5 100644 --- a/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts +++ b/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts @@ -102,6 +102,7 @@ class InsertSnippetAction extends EditorAction { snippet, '', SnippetSource.User, + `random/${Math.random()}` )); } @@ -143,6 +144,7 @@ class InsertSnippetAction extends EditorAction { clipboardText = await clipboardService.readText(); } SnippetController2.get(editor)?.insert(snippet.codeSnippet, { clipboardText }); + snippetService.updateUsageTimestamp(snippet); } } diff --git a/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts index 3e8ea30e666..7e8c6555e5c 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts @@ -8,7 +8,7 @@ import { compare, compareSubstring } from 'vs/base/common/strings'; import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; -import { CompletionItem, CompletionItemKind, CompletionItemProvider, CompletionList, CompletionItemInsertTextRule, CompletionContext, CompletionTriggerKind, CompletionItemLabel } from 'vs/editor/common/languages'; +import { CompletionItem, CompletionItemKind, CompletionItemProvider, CompletionList, CompletionItemInsertTextRule, CompletionContext, CompletionTriggerKind, CompletionItemLabel, Command } from 'vs/editor/common/languages'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { SnippetParser } from 'vs/editor/contrib/snippet/browser/snippetParser'; import { localize } from 'vs/nls'; @@ -19,6 +19,18 @@ import { StopWatch } from 'vs/base/common/stopwatch'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { getWordAtText } from 'vs/editor/common/core/wordHelper'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; + + +const markSnippetAsUsed = '_snippet.markAsUsed'; + +CommandsRegistry.registerCommand(markSnippetAsUsed, (accessor, ...args) => { + const snippetsService = accessor.get(ISnippetsService); + const [first] = args; + if (first instanceof Snippet) { + snippetsService.updateUsageTimestamp(first); + } +}); export class SnippetCompletion implements CompletionItem { @@ -31,10 +43,11 @@ export class SnippetCompletion implements CompletionItem { kind: CompletionItemKind; insertTextRules: CompletionItemInsertTextRule; extensionId?: ExtensionIdentifier; + command?: Command; constructor( readonly snippet: Snippet, - range: IRange | { insert: IRange; replace: IRange } + range: IRange | { insert: IRange; replace: IRange }, ) { this.label = { label: snippet.prefix, description: snippet.name }; this.detail = localize('detail.snippet', "{0} ({1})", snippet.description || snippet.name, snippet.source); @@ -44,6 +57,7 @@ export class SnippetCompletion implements CompletionItem { this.sortText = `${snippet.snippetSource === SnippetSource.Extension ? 'z' : 'a'}-${snippet.prefix}`; this.kind = CompletionItemKind.Snippet; this.insertTextRules = CompletionItemInsertTextRule.InsertAsSnippet; + this.command = { id: markSnippetAsUsed, title: '', arguments: [snippet] }; } resolve(): this { diff --git a/src/vs/workbench/contrib/snippets/browser/snippetPicker.ts b/src/vs/workbench/contrib/snippets/browser/snippetPicker.ts index 9cb08b37047..0814ea32312 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetPicker.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetPicker.ts @@ -27,7 +27,7 @@ export async function pickSnippet(accessor: ServicesAccessor, languageIdOrSnippe snippets = (await snippetService.getSnippets(languageIdOrSnippets, { includeDisabledSnippets: true, includeNoPrefixSnippets: true })); } - snippets.sort(Snippet.compare); + snippets.sort((a, b) => a.snippetSource - b.snippetSource); const makeSnippetPicks = () => { const result: QuickPickInput[] = []; diff --git a/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts b/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts index 1e1bdd82c69..02b6c4b4238 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts @@ -15,6 +15,7 @@ export const ISnippetsService = createDecorator('snippetServic export interface ISnippetGetOptions { includeDisabledSnippets?: boolean; includeNoPrefixSnippets?: boolean; + noRecencySort?: boolean; } export interface ISnippetsService { @@ -27,6 +28,8 @@ export interface ISnippetsService { updateEnablement(snippet: Snippet, enabled: boolean): void; + updateUsageTimestamp(snippet: Snippet): void; + getSnippets(languageId: string, opt?: ISnippetGetOptions): Promise; getSnippetsSync(languageId: string, opt?: ISnippetGetOptions): Snippet[]; diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts b/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts index 784296b70de..72a3e74f64c 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts @@ -113,7 +113,7 @@ export class Snippet { readonly body: string, readonly source: string, readonly snippetSource: SnippetSource, - readonly snippetIdentifier?: string, + readonly snippetIdentifier: string, readonly extensionId?: ExtensionIdentifier, ) { this.prefixLow = prefix.toLowerCase(); @@ -139,24 +139,6 @@ export class Snippet { get usesSelection(): boolean { return this._bodyInsights.value.usesSelectionVariable; } - - static compare(a: Snippet, b: Snippet): number { - if (a.snippetSource < b.snippetSource) { - return -1; - } else if (a.snippetSource > b.snippetSource) { - return 1; - } else if (a.source < b.source) { - return -1; - } else if (a.source > b.source) { - return 1; - } else if (a.name > b.name) { - return 1; - } else if (a.name < b.name) { - return -1; - } else { - return 0; - } - } } @@ -195,7 +177,7 @@ export class SnippetFile { public defaultScopes: string[] | undefined, private readonly _extension: IExtensionDescription | undefined, private readonly _fileService: IFileService, - private readonly _extensionResourceLoaderService: IExtensionResourceLoaderService + private readonly _extensionResourceLoaderService: IExtensionResourceLoaderService, ) { this.isGlobalSnippets = extname(location.path) === '.code-snippets'; this.isUserSnippets = !this._extension; @@ -330,7 +312,7 @@ export class SnippetFile { body, source, this.source, - this._extension && `${relativePath(this._extension.extensionLocation, this.location)}/${name}`, + this._extension ? `${relativePath(this._extension.extensionLocation, this.location)}/${name}` : `${basename(this.location.path)}/${name}`, this._extension?.identifier, )); } diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts index abc007e863d..da0e5e26960 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts @@ -167,6 +167,42 @@ class SnippetEnablement { } } +class SnippetUsageTimestamps { + + private static _key = 'snippets.usageTimestamps'; + + private readonly _usages: Map; + + constructor( + @IStorageService private readonly _storageService: IStorageService, + ) { + + const raw = _storageService.get(SnippetUsageTimestamps._key, StorageScope.PROFILE, ''); + let data: [string, number][] | undefined; + try { + data = JSON.parse(raw); + } catch { + data = []; + } + + this._usages = Array.isArray(data) ? new Map(data) : new Map(); + } + + getUsageTimestamp(id: string): number | undefined { + return this._usages.get(id); + } + + updateUsageTimestamp(id: string): void { + // map uses insertion order, we want most recent at the end + this._usages.delete(id); + this._usages.set(id, Date.now()); + + // persist last 100 item + const all = [...this._usages].slice(-100); + this._storageService.store(SnippetUsageTimestamps._key, JSON.stringify(all), StorageScope.PROFILE, StorageTarget.USER); + } +} + class SnippetsService implements ISnippetsService { declare readonly _serviceBrand: undefined; @@ -175,6 +211,7 @@ class SnippetsService implements ISnippetsService { private readonly _pendingWork: Promise[] = []; private readonly _files = new ResourceMap(); private readonly _enablement: SnippetEnablement; + private readonly _usageTimestamps: SnippetUsageTimestamps; constructor( @IEnvironmentService private readonly _environmentService: IEnvironmentService, @@ -198,6 +235,7 @@ class SnippetsService implements ISnippetsService { setSnippetSuggestSupport(new SnippetCompletionProvider(this._languageService, this, languageConfigurationService)); this._enablement = instantiationService.createInstance(SnippetEnablement); + this._usageTimestamps = instantiationService.createInstance(SnippetUsageTimestamps); } dispose(): void { @@ -205,13 +243,15 @@ class SnippetsService implements ISnippetsService { } isEnabled(snippet: Snippet): boolean { - return !snippet.snippetIdentifier || !this._enablement.isIgnored(snippet.snippetIdentifier); + return !this._enablement.isIgnored(snippet.snippetIdentifier); } updateEnablement(snippet: Snippet, enabled: boolean): void { - if (snippet.snippetIdentifier) { - this._enablement.updateIgnored(snippet.snippetIdentifier, !enabled); - } + this._enablement.updateIgnored(snippet.snippetIdentifier, !enabled); + } + + updateUsageTimestamp(snippet: Snippet): void { + this._usageTimestamps.updateUsageTimestamp(snippet.snippetIdentifier); } private _joinSnippets(): Promise { @@ -240,7 +280,7 @@ class SnippetsService implements ISnippetsService { } } await Promise.all(promises); - return this._filterSnippets(result, opts); + return this._filterAndSortSnippets(result, opts); } getSnippetsSync(languageId: string, opts?: ISnippetGetOptions): Snippet[] { @@ -253,14 +293,45 @@ class SnippetsService implements ISnippetsService { file.select(languageId, result); } } - return this._filterSnippets(result, opts); + return this._filterAndSortSnippets(result, opts); } - private _filterSnippets(snippets: Snippet[], opts?: ISnippetGetOptions): Snippet[] { - return snippets.filter(snippet => { + private _filterAndSortSnippets(snippets: Snippet[], opts?: ISnippetGetOptions): Snippet[] { + const result = snippets.filter(snippet => { return (snippet.prefix || opts?.includeNoPrefixSnippets) // prefix or no-prefix wanted && (this.isEnabled(snippet) || opts?.includeDisabledSnippets); // enabled or disabled wanted }); + + return result.sort((a, b) => { + let result = 0; + if (!opts?.noRecencySort) { + const val1 = this._usageTimestamps.getUsageTimestamp(a.snippetIdentifier) ?? -1; + const val2 = this._usageTimestamps.getUsageTimestamp(b.snippetIdentifier) ?? -1; + result = val2 - val1; + } + if (result === 0) { + result = this._compareSnippet(a, b); + } + return result; + }); + } + + private _compareSnippet(a: Snippet, b: Snippet): number { + if (a.snippetSource < b.snippetSource) { + return -1; + } else if (a.snippetSource > b.snippetSource) { + return 1; + } else if (a.source < b.source) { + return -1; + } else if (a.source > b.source) { + return 1; + } else if (a.name > b.name) { + return 1; + } else if (a.name < b.name) { + return -1; + } else { + return 0; + } } // --- loading, watching diff --git a/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts b/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts index 2d7a798594d..02805db79eb 100644 --- a/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts +++ b/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts @@ -83,6 +83,7 @@ class SurroundWithSnippetEditorAction extends EditorAction2 { } SnippetController2.get(editor)?.insert(snippet.codeSnippet, { clipboardText }); + snippetService.updateUsageTimestamp(snippet); } } diff --git a/src/vs/workbench/contrib/snippets/test/browser/snippetFile.test.ts b/src/vs/workbench/contrib/snippets/test/browser/snippetFile.test.ts index dffa5ea30ef..b2f87092ad1 100644 --- a/src/vs/workbench/contrib/snippets/test/browser/snippetFile.test.ts +++ b/src/vs/workbench/contrib/snippets/test/browser/snippetFile.test.ts @@ -7,6 +7,7 @@ import * as assert from 'assert'; import { SnippetFile, Snippet, SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; import { URI } from 'vs/base/common/uri'; import { SnippetParser } from 'vs/editor/contrib/snippet/browser/snippetParser'; +import { generateUuid } from 'vs/base/common/uuid'; suite('Snippets', function () { @@ -24,12 +25,12 @@ suite('Snippets', function () { assert.strictEqual(bucket.length, 0); file = new TestSnippetFile(URI.file('somepath/foo.code-snippets'), [ - new Snippet(['foo'], 'FooSnippet1', 'foo', '', 'snippet', 'test', SnippetSource.User), - new Snippet(['foo'], 'FooSnippet2', 'foo', '', 'snippet', 'test', SnippetSource.User), - new Snippet(['bar'], 'BarSnippet1', 'foo', '', 'snippet', 'test', SnippetSource.User), - new Snippet(['bar.comment'], 'BarSnippet2', 'foo', '', 'snippet', 'test', SnippetSource.User), - new Snippet(['bar.strings'], 'BarSnippet2', 'foo', '', 'snippet', 'test', SnippetSource.User), - new Snippet(['bazz', 'bazz'], 'BazzSnippet1', 'foo', '', 'snippet', 'test', SnippetSource.User), + new Snippet(['foo'], 'FooSnippet1', 'foo', '', 'snippet', 'test', SnippetSource.User, generateUuid()), + new Snippet(['foo'], 'FooSnippet2', 'foo', '', 'snippet', 'test', SnippetSource.User, generateUuid()), + new Snippet(['bar'], 'BarSnippet1', 'foo', '', 'snippet', 'test', SnippetSource.User, generateUuid()), + new Snippet(['bar.comment'], 'BarSnippet2', 'foo', '', 'snippet', 'test', SnippetSource.User, generateUuid()), + new Snippet(['bar.strings'], 'BarSnippet2', 'foo', '', 'snippet', 'test', SnippetSource.User, generateUuid()), + new Snippet(['bazz', 'bazz'], 'BazzSnippet1', 'foo', '', 'snippet', 'test', SnippetSource.User, generateUuid()), ]); bucket = []; @@ -56,8 +57,8 @@ suite('Snippets', function () { test('SnippetFile#select - any scope', function () { const file = new TestSnippetFile(URI.file('somepath/foo.code-snippets'), [ - new Snippet([], 'AnySnippet1', 'foo', '', 'snippet', 'test', SnippetSource.User), - new Snippet(['foo'], 'FooSnippet1', 'foo', '', 'snippet', 'test', SnippetSource.User), + new Snippet([], 'AnySnippet1', 'foo', '', 'snippet', 'test', SnippetSource.User, generateUuid()), + new Snippet(['foo'], 'FooSnippet1', 'foo', '', 'snippet', 'test', SnippetSource.User, generateUuid()), ]); const bucket: Snippet[] = []; @@ -69,7 +70,7 @@ suite('Snippets', function () { test('Snippet#needsClipboard', function () { function assertNeedsClipboard(body: string, expected: boolean): void { - const snippet = new Snippet(['foo'], 'FooSnippet1', 'foo', '', body, 'test', SnippetSource.User); + const snippet = new Snippet(['foo'], 'FooSnippet1', 'foo', '', body, 'test', SnippetSource.User, generateUuid()); assert.strictEqual(snippet.needsClipboard, expected); assert.strictEqual(SnippetParser.guessNeedsClipboard(body), expected); @@ -86,7 +87,7 @@ suite('Snippets', function () { test('Snippet#isTrivial', function () { function assertIsTrivial(body: string, expected: boolean): void { - const snippet = new Snippet(['foo'], 'FooSnippet1', 'foo', '', body, 'test', SnippetSource.User); + const snippet = new Snippet(['foo'], 'FooSnippet1', 'foo', '', body, 'test', SnippetSource.User, generateUuid()); assert.strictEqual(snippet.isTrivial, expected); } diff --git a/src/vs/workbench/contrib/snippets/test/browser/snippetsRewrite.test.ts b/src/vs/workbench/contrib/snippets/test/browser/snippetsRewrite.test.ts index 50c826d5868..ac5a579fcfd 100644 --- a/src/vs/workbench/contrib/snippets/test/browser/snippetsRewrite.test.ts +++ b/src/vs/workbench/contrib/snippets/test/browser/snippetsRewrite.test.ts @@ -4,12 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { generateUuid } from 'vs/base/common/uuid'; import { Snippet, SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; suite('SnippetRewrite', function () { function assertRewrite(input: string, expected: string | boolean): void { - const actual = new Snippet(['foo'], 'foo', 'foo', 'foo', input, 'foo', SnippetSource.User); + const actual = new Snippet(['foo'], 'foo', 'foo', 'foo', input, 'foo', SnippetSource.User, generateUuid()); if (typeof expected === 'boolean') { assert.strictEqual(actual.codeSnippet, input); } else { @@ -47,7 +48,7 @@ suite('SnippetRewrite', function () { }); test('lazy bogous variable rewrite', function () { - const snippet = new Snippet(['fooLang'], 'foo', 'prefix', 'desc', 'This is ${bogous} because it is a ${var}', 'source', SnippetSource.Extension); + const snippet = new Snippet(['fooLang'], 'foo', 'prefix', 'desc', 'This is ${bogous} because it is a ${var}', 'source', SnippetSource.Extension, generateUuid()); assert.strictEqual(snippet.body, 'This is ${bogous} because it is a ${var}'); assert.strictEqual(snippet.codeSnippet, 'This is ${1:bogous} because it is a ${2:var}'); assert.strictEqual(snippet.isBogous, true); diff --git a/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts index 6c442beb9f2..81156296016 100644 --- a/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts +++ b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts @@ -15,6 +15,7 @@ import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/te import { EditOperation } from 'vs/editor/common/core/editOperation'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { ILanguageService } from 'vs/editor/common/languages/language'; +import { generateUuid } from 'vs/base/common/uuid'; class SimpleSnippetService implements ISnippetsService { declare readonly _serviceBrand: undefined; @@ -34,6 +35,9 @@ class SimpleSnippetService implements ISnippetsService { updateEnablement(): void { throw new Error(); } + updateUsageTimestamp(snippet: Snippet): void { + throw new Error(); + } } suite('SnippetsService', function () { @@ -59,7 +63,8 @@ suite('SnippetsService', function () { '', 'barCodeSnippet', '', - SnippetSource.User + SnippetSource.User, + generateUuid() ), new Snippet( ['fooLang'], 'bazzTest', @@ -67,7 +72,8 @@ suite('SnippetsService', function () { '', 'bazzCodeSnippet', '', - SnippetSource.User + SnippetSource.User, + generateUuid() )]); }); @@ -128,7 +134,8 @@ suite('SnippetsService', function () { '', 's1', '', - SnippetSource.User + SnippetSource.User, + generateUuid() ), new Snippet( ['fooLang'], 'name', @@ -136,7 +143,8 @@ suite('SnippetsService', function () { '', 's2', '', - SnippetSource.User + SnippetSource.User, + generateUuid() )]); const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); @@ -206,7 +214,8 @@ suite('SnippetsService', function () { '', 'insert me', '', - SnippetSource.User + SnippetSource.User, + generateUuid() )]); const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); @@ -241,7 +250,8 @@ suite('SnippetsService', function () { '', '$0', '', - SnippetSource.User + SnippetSource.User, + generateUuid() )]); const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); @@ -263,7 +273,8 @@ suite('SnippetsService', function () { '', 'second', '', - SnippetSource.Extension + SnippetSource.Extension, + generateUuid() ), new Snippet( ['fooLang'], 'first', @@ -271,7 +282,8 @@ suite('SnippetsService', function () { '', 'first', '', - SnippetSource.User + SnippetSource.User, + generateUuid() )]); const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); @@ -299,7 +311,8 @@ suite('SnippetsService', function () { '', 'second', '', - SnippetSource.User + SnippetSource.User, + generateUuid() )]); const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); @@ -323,7 +336,8 @@ suite('SnippetsService', function () { '', 'second', '', - SnippetSource.User + SnippetSource.User, + generateUuid() )]); const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); @@ -342,7 +356,8 @@ suite('SnippetsService', function () { '', 'second', '', - SnippetSource.User + SnippetSource.User, + generateUuid() )]); const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); @@ -361,7 +376,8 @@ suite('SnippetsService', function () { '', 'second', '', - SnippetSource.User + SnippetSource.User, + generateUuid() )]); const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); @@ -384,7 +400,8 @@ suite('SnippetsService', function () { '', 'second', '', - SnippetSource.User + SnippetSource.User, + generateUuid() )]); const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); @@ -408,7 +425,8 @@ suite('SnippetsService', function () { '', 'second', '', - SnippetSource.User + SnippetSource.User, + generateUuid() )]); const provider = new SnippetCompletionProvider(languageService, snippetService, languageConfigurationService); @@ -427,7 +445,8 @@ suite('SnippetsService', function () { '', 'second', '', - SnippetSource.User + SnippetSource.User, + generateUuid() )]); const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); @@ -446,7 +465,8 @@ suite('SnippetsService', function () { '', '<= #dly"', '', - SnippetSource.User + SnippetSource.User, + generateUuid() ), new Snippet( ['fooLang'], 'noblockwdelay', @@ -454,7 +474,8 @@ suite('SnippetsService', function () { '', 'eleven', '', - SnippetSource.User + SnippetSource.User, + generateUuid() )]); const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); @@ -484,7 +505,8 @@ suite('SnippetsService', function () { '', 'not word snippet', '', - SnippetSource.User + SnippetSource.User, + generateUuid() )]); const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); @@ -526,7 +548,8 @@ suite('SnippetsService', function () { '', '', '', - SnippetSource.User + SnippetSource.User, + generateUuid() )]); const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); @@ -558,7 +581,8 @@ suite('SnippetsService', function () { '', '[PSCustomObject] @{ Key = Value }', '', - SnippetSource.User + SnippetSource.User, + generateUuid() )]); const provider = new SnippetCompletionProvider(languageService, snippetService, languageConfigurationService); @@ -583,7 +607,8 @@ suite('SnippetsService', function () { '', '~\\cite{$CLIPBOARD}', '', - SnippetSource.User + SnippetSource.User, + generateUuid() )]); const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); @@ -602,9 +627,9 @@ suite('SnippetsService', function () { test('still show suggestions in string when disable string suggestion #136611', async function () { snippetService = new SimpleSnippetService([ - new Snippet(['fooLang'], 'aaa', 'aaa', '', 'value', '', SnippetSource.User), - new Snippet(['fooLang'], 'bbb', 'bbb', '', 'value', '', SnippetSource.User), - // new Snippet(['fooLang'], '\'ccc', '\'ccc', '', 'value', '', SnippetSource.User) + new Snippet(['fooLang'], 'aaa', 'aaa', '', 'value', '', SnippetSource.User, generateUuid()), + new Snippet(['fooLang'], 'bbb', 'bbb', '', 'value', '', SnippetSource.User, generateUuid()), + // new Snippet(['fooLang'], '\'ccc', '\'ccc', '', 'value', '', SnippetSource.User, generateUuid()) ]); const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); @@ -624,9 +649,9 @@ suite('SnippetsService', function () { test('still show suggestions in string when disable string suggestion #136611', async function () { snippetService = new SimpleSnippetService([ - new Snippet(['fooLang'], 'aaa', 'aaa', '', 'value', '', SnippetSource.User), - new Snippet(['fooLang'], 'bbb', 'bbb', '', 'value', '', SnippetSource.User), - new Snippet(['fooLang'], '\'ccc', '\'ccc', '', 'value', '', SnippetSource.User) + new Snippet(['fooLang'], 'aaa', 'aaa', '', 'value', '', SnippetSource.User, generateUuid()), + new Snippet(['fooLang'], 'bbb', 'bbb', '', 'value', '', SnippetSource.User, generateUuid()), + new Snippet(['fooLang'], '\'ccc', '\'ccc', '', 'value', '', SnippetSource.User, generateUuid()) ]); const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); @@ -645,9 +670,9 @@ suite('SnippetsService', function () { test('Snippet suggestions are too eager #138707 (word)', async function () { snippetService = new SimpleSnippetService([ - new Snippet(['fooLang'], 'tys', 'tys', '', 'value', '', SnippetSource.User), - new Snippet(['fooLang'], 'hell_or_tell', 'hell_or_tell', '', 'value', '', SnippetSource.User), - new Snippet(['fooLang'], '^y', '^y', '', 'value', '', SnippetSource.User), + new Snippet(['fooLang'], 'tys', 'tys', '', 'value', '', SnippetSource.User, generateUuid()), + new Snippet(['fooLang'], 'hell_or_tell', 'hell_or_tell', '', 'value', '', SnippetSource.User, generateUuid()), + new Snippet(['fooLang'], '^y', '^y', '', 'value', '', SnippetSource.User, generateUuid()), ]); const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); @@ -666,9 +691,9 @@ suite('SnippetsService', function () { test('Snippet suggestions are too eager #138707 (no word)', async function () { snippetService = new SimpleSnippetService([ - new Snippet(['fooLang'], 'tys', 'tys', '', 'value', '', SnippetSource.User), - new Snippet(['fooLang'], 't', 't', '', 'value', '', SnippetSource.User), - new Snippet(['fooLang'], '^y', '^y', '', 'value', '', SnippetSource.User), + new Snippet(['fooLang'], 'tys', 'tys', '', 'value', '', SnippetSource.User, generateUuid()), + new Snippet(['fooLang'], 't', 't', '', 'value', '', SnippetSource.User, generateUuid()), + new Snippet(['fooLang'], '^y', '^y', '', 'value', '', SnippetSource.User, generateUuid()), ]); const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); @@ -687,8 +712,8 @@ suite('SnippetsService', function () { test('Snippet suggestions are too eager #138707 (word/word)', async function () { snippetService = new SimpleSnippetService([ - new Snippet(['fooLang'], 'async arrow function', 'async arrow function', '', 'value', '', SnippetSource.User), - new Snippet(['fooLang'], 'foobarrrrrr', 'foobarrrrrr', '', 'value', '', SnippetSource.User), + new Snippet(['fooLang'], 'async arrow function', 'async arrow function', '', 'value', '', SnippetSource.User, generateUuid()), + new Snippet(['fooLang'], 'foobarrrrrr', 'foobarrrrrr', '', 'value', '', SnippetSource.User, generateUuid()), ]); const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); @@ -707,7 +732,7 @@ suite('SnippetsService', function () { test('Strange and useless autosuggestion #region/#endregion PHP #140039', async function () { snippetService = new SimpleSnippetService([ - new Snippet(['fooLang'], 'reg', '#region', '', 'value', '', SnippetSource.User), + new Snippet(['fooLang'], 'reg', '#region', '', 'value', '', SnippetSource.User, generateUuid()), ]); @@ -725,9 +750,9 @@ suite('SnippetsService', function () { test.skip('Snippets disappear with . key #145960', async function () { snippetService = new SimpleSnippetService([ - new Snippet(['fooLang'], 'div', 'div', '', 'div', '', SnippetSource.User), - new Snippet(['fooLang'], 'div.', 'div.', '', 'div.', '', SnippetSource.User), - new Snippet(['fooLang'], 'div#', 'div#', '', 'div#', '', SnippetSource.User), + new Snippet(['fooLang'], 'div', 'div', '', 'div', '', SnippetSource.User, generateUuid()), + new Snippet(['fooLang'], 'div.', 'div.', '', 'div.', '', SnippetSource.User, generateUuid()), + new Snippet(['fooLang'], 'div#', 'div#', '', 'div#', '', SnippetSource.User, generateUuid()), ]); const provider = new SnippetCompletionProvider(languageService, snippetService, new TestLanguageConfigurationService()); -- cgit v1.2.3 From aeb4a695fc42e2f812b0f70dc98cfe67e33ebe71 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Fri, 15 Jul 2022 14:12:56 +0200 Subject: tests - speed up unit tests (#149712) (#155147) * tests - convert history tracker to in-memory (#149712) * fix warnings from missing service * sqlite slowness * disable flush on write in tests unless disk tests * more runWithFakedTimers * disable flush also in pfs * fix compile --- src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts b/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts index 02805db79eb..e8a9551fe19 100644 --- a/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts +++ b/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts @@ -83,7 +83,7 @@ class SurroundWithSnippetEditorAction extends EditorAction2 { } SnippetController2.get(editor)?.insert(snippet.codeSnippet, { clipboardText }); - snippetService.updateUsageTimestamp(snippet); + snippetsService.updateUsageTimestamp(snippet); } } -- cgit v1.2.3 From 0fd8cd835cccac461864beee81c8df62619eaa57 Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 15 Jul 2022 14:43:47 +0200 Subject: surround with code action needs non-empty selection, uses active end of selection --- src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts b/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts index e8a9551fe19..c582bf54e7d 100644 --- a/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts +++ b/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts @@ -116,7 +116,12 @@ class SurroundWithSnippetCodeActionProvider implements CodeActionProvider, IWork async provideCodeActions(model: ITextModel, range: Range | Selection): Promise { - const snippets = await getSurroundableSnippets(this._snippetService, model, range.getEndPosition()); + if (range.isEmpty()) { + return undefined; + } + + const position = Selection.isISelection(range) ? range.getPosition() : range.getStartPosition(); + const snippets = await getSurroundableSnippets(this._snippetService, model, position); if (!snippets.length) { return undefined; } -- cgit v1.2.3 From b8e6d89295fa0aaddd80b933fb278d6401650720 Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 15 Jul 2022 14:50:47 +0200 Subject: no hidden snippets as code action --- src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts b/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts index c582bf54e7d..7c0842812dd 100644 --- a/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts +++ b/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts @@ -27,13 +27,13 @@ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWo import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Position } from 'vs/editor/common/core/position'; -async function getSurroundableSnippets(snippetsService: ISnippetsService, model: ITextModel, position: Position): Promise { +async function getSurroundableSnippets(snippetsService: ISnippetsService, model: ITextModel, position: Position, includeDisabledSnippets: boolean): Promise { const { lineNumber, column } = position; model.tokenization.tokenizeIfCheap(lineNumber); const languageId = model.getLanguageIdAtPosition(lineNumber, column); - const allSnippets = await snippetsService.getSnippets(languageId, { includeNoPrefixSnippets: true, includeDisabledSnippets: true }); + const allSnippets = await snippetsService.getSnippets(languageId, { includeNoPrefixSnippets: true, includeDisabledSnippets }); return allSnippets.filter(snippet => snippet.usesSelection); } @@ -67,7 +67,7 @@ class SurroundWithSnippetEditorAction extends EditorAction2 { const snippetsService = accessor.get(ISnippetsService); const clipboardService = accessor.get(IClipboardService); - const snippets = await getSurroundableSnippets(snippetsService, editor.getModel(), editor.getPosition()); + const snippets = await getSurroundableSnippets(snippetsService, editor.getModel(), editor.getPosition(), true); if (!snippets.length) { return; } @@ -121,7 +121,7 @@ class SurroundWithSnippetCodeActionProvider implements CodeActionProvider, IWork } const position = Selection.isISelection(range) ? range.getPosition() : range.getStartPosition(); - const snippets = await getSurroundableSnippets(this._snippetService, model, position); + const snippets = await getSurroundableSnippets(this._snippetService, model, position, false); if (!snippets.length) { return undefined; } -- cgit v1.2.3 From 36846c19db60c12021d812c35492ec00ac3bac2b Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 15 Jul 2022 15:38:55 +0200 Subject: add concept of top level snippet and allow to get snippets without language filter --- .../contrib/snippets/browser/insertSnippet.ts | 1 + .../snippets/browser/snippets.contribution.ts | 7 ++- .../contrib/snippets/browser/snippetsFile.ts | 10 ++-- .../contrib/snippets/browser/snippetsService.ts | 39 ++++++++++++--- .../snippets/test/browser/snippetFile.test.ts | 20 ++++---- .../snippets/test/browser/snippetsRewrite.test.ts | 4 +- .../snippets/test/browser/snippetsService.test.ts | 56 +++++++++++++++------- 7 files changed, 95 insertions(+), 42 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts b/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts index 2d84a9a71a5..f52422764ca 100644 --- a/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts +++ b/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts @@ -95,6 +95,7 @@ class InsertSnippetAction extends EditorAction { if (snippet) { return resolve(new Snippet( + false, [], '', '', diff --git a/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts b/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts index 02b6c4b4238..ac1e213fc55 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts @@ -16,6 +16,7 @@ export interface ISnippetGetOptions { includeDisabledSnippets?: boolean; includeNoPrefixSnippets?: boolean; noRecencySort?: boolean; + topLevelSnippets?: boolean; } export interface ISnippetsService { @@ -30,7 +31,7 @@ export interface ISnippetsService { updateUsageTimestamp(snippet: Snippet): void; - getSnippets(languageId: string, opt?: ISnippetGetOptions): Promise; + getSnippets(languageId: string | undefined, opt?: ISnippetGetOptions): Promise; getSnippetsSync(languageId: string, opt?: ISnippetGetOptions): Snippet[]; } @@ -42,6 +43,10 @@ const snippetSchemaProperties: IJSONSchemaMap = { description: nls.localize('snippetSchema.json.prefix', 'The prefix to use when selecting the snippet in intellisense'), type: ['string', 'array'] }, + isTopLevel: { + description: nls.localize('snippetSchema.json.isTopLevel', 'The snippet is only applicable to empty files.'), + type: 'string' + }, body: { markdownDescription: nls.localize('snippetSchema.json.body', 'The snippet content. Use `$1`, `${1:defaultText}` to define cursor positions, use `$0` for the final cursor position. Insert variable values with `${varName}` and `${varName:defaultText}`, e.g. `This is file: $TM_FILENAME`.'), type: ['string', 'array'], diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts b/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts index 72a3e74f64c..b6ce272ef28 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts @@ -8,7 +8,6 @@ import { localize } from 'vs/nls'; import { extname, basename } from 'vs/base/common/path'; import { SnippetParser, Variable, Placeholder, Text } from 'vs/editor/contrib/snippet/browser/snippetParser'; import { KnownSnippetVariableNames } from 'vs/editor/contrib/snippet/browser/snippetVariables'; -import { isFalsyOrWhitespace } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; @@ -106,6 +105,7 @@ export class Snippet { readonly prefixLow: string; constructor( + readonly isTopLevel: boolean, readonly scopes: string[], readonly name: string, readonly prefix: string, @@ -143,8 +143,9 @@ export class Snippet { interface JsonSerializedSnippet { + isTopLevel?: boolean; body: string | string[]; - scope: string; + scope?: string; prefix: string | string[] | undefined; description: string; } @@ -260,7 +261,7 @@ export class SnippetFile { private _parseSnippet(name: string, snippet: JsonSerializedSnippet, bucket: Snippet[]): void { - let { prefix, body, description } = snippet; + let { isTopLevel, prefix, body, description } = snippet; if (!prefix) { prefix = ''; @@ -281,7 +282,7 @@ export class SnippetFile { if (this.defaultScopes) { scopes = this.defaultScopes; } else if (typeof snippet.scope === 'string') { - scopes = snippet.scope.split(',').map(s => s.trim()).filter(s => !isFalsyOrWhitespace(s)); + scopes = snippet.scope.split(',').map(s => s.trim()).filter(Boolean); } else { scopes = []; } @@ -305,6 +306,7 @@ export class SnippetFile { for (const _prefix of Array.isArray(prefix) ? prefix : Iterable.single(prefix)) { bucket.push(new Snippet( + Boolean(isTopLevel), scopes, name, _prefix, diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts index da0e5e26960..918411b564c 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts @@ -31,6 +31,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { insertInto } from 'vs/base/common/arrays'; namespace snippetExt { @@ -265,16 +266,25 @@ class SnippetsService implements ISnippetsService { return this._files.values(); } - async getSnippets(languageId: string, opts?: ISnippetGetOptions): Promise { + async getSnippets(languageId: string | undefined, opts?: ISnippetGetOptions): Promise { await this._joinSnippets(); const result: Snippet[] = []; const promises: Promise[] = []; - if (this._languageService.isRegisteredLanguageId(languageId)) { + if (languageId) { + if (this._languageService.isRegisteredLanguageId(languageId)) { + for (const file of this._files.values()) { + promises.push(file.load() + .then(file => file.select(languageId, result)) + .catch(err => this._logService.error(err, file.location.toString())) + ); + } + } + } else { for (const file of this._files.values()) { promises.push(file.load() - .then(file => file.select(languageId, result)) + .then(file => insertInto(result, result.length, file.data)) .catch(err => this._logService.error(err, file.location.toString())) ); } @@ -297,10 +307,25 @@ class SnippetsService implements ISnippetsService { } private _filterAndSortSnippets(snippets: Snippet[], opts?: ISnippetGetOptions): Snippet[] { - const result = snippets.filter(snippet => { - return (snippet.prefix || opts?.includeNoPrefixSnippets) // prefix or no-prefix wanted - && (this.isEnabled(snippet) || opts?.includeDisabledSnippets); // enabled or disabled wanted - }); + + const result: Snippet[] = []; + + for (const snippet of snippets) { + if (!snippet.prefix && !opts?.includeNoPrefixSnippets) { + // prefix or no-prefix wanted + continue; + } + if (!this.isEnabled(snippet) && !opts?.includeDisabledSnippets) { + // enabled or disabled wanted + continue; + } + if (typeof opts?.topLevelSnippets === 'boolean' && opts.topLevelSnippets !== snippet.isTopLevel) { + // isTopLevel requested but mismatching + continue; + } + result.push(snippet); + } + return result.sort((a, b) => { let result = 0; diff --git a/src/vs/workbench/contrib/snippets/test/browser/snippetFile.test.ts b/src/vs/workbench/contrib/snippets/test/browser/snippetFile.test.ts index b2f87092ad1..e9bb5b9853c 100644 --- a/src/vs/workbench/contrib/snippets/test/browser/snippetFile.test.ts +++ b/src/vs/workbench/contrib/snippets/test/browser/snippetFile.test.ts @@ -25,12 +25,12 @@ suite('Snippets', function () { assert.strictEqual(bucket.length, 0); file = new TestSnippetFile(URI.file('somepath/foo.code-snippets'), [ - new Snippet(['foo'], 'FooSnippet1', 'foo', '', 'snippet', 'test', SnippetSource.User, generateUuid()), - new Snippet(['foo'], 'FooSnippet2', 'foo', '', 'snippet', 'test', SnippetSource.User, generateUuid()), - new Snippet(['bar'], 'BarSnippet1', 'foo', '', 'snippet', 'test', SnippetSource.User, generateUuid()), - new Snippet(['bar.comment'], 'BarSnippet2', 'foo', '', 'snippet', 'test', SnippetSource.User, generateUuid()), - new Snippet(['bar.strings'], 'BarSnippet2', 'foo', '', 'snippet', 'test', SnippetSource.User, generateUuid()), - new Snippet(['bazz', 'bazz'], 'BazzSnippet1', 'foo', '', 'snippet', 'test', SnippetSource.User, generateUuid()), + new Snippet(false, ['foo'], 'FooSnippet1', 'foo', '', 'snippet', 'test', SnippetSource.User, generateUuid()), + new Snippet(false, ['foo'], 'FooSnippet2', 'foo', '', 'snippet', 'test', SnippetSource.User, generateUuid()), + new Snippet(false, ['bar'], 'BarSnippet1', 'foo', '', 'snippet', 'test', SnippetSource.User, generateUuid()), + new Snippet(false, ['bar.comment'], 'BarSnippet2', 'foo', '', 'snippet', 'test', SnippetSource.User, generateUuid()), + new Snippet(false, ['bar.strings'], 'BarSnippet2', 'foo', '', 'snippet', 'test', SnippetSource.User, generateUuid()), + new Snippet(false, ['bazz', 'bazz'], 'BazzSnippet1', 'foo', '', 'snippet', 'test', SnippetSource.User, generateUuid()), ]); bucket = []; @@ -57,8 +57,8 @@ suite('Snippets', function () { test('SnippetFile#select - any scope', function () { const file = new TestSnippetFile(URI.file('somepath/foo.code-snippets'), [ - new Snippet([], 'AnySnippet1', 'foo', '', 'snippet', 'test', SnippetSource.User, generateUuid()), - new Snippet(['foo'], 'FooSnippet1', 'foo', '', 'snippet', 'test', SnippetSource.User, generateUuid()), + new Snippet(false, [], 'AnySnippet1', 'foo', '', 'snippet', 'test', SnippetSource.User, generateUuid()), + new Snippet(false, ['foo'], 'FooSnippet1', 'foo', '', 'snippet', 'test', SnippetSource.User, generateUuid()), ]); const bucket: Snippet[] = []; @@ -70,7 +70,7 @@ suite('Snippets', function () { test('Snippet#needsClipboard', function () { function assertNeedsClipboard(body: string, expected: boolean): void { - const snippet = new Snippet(['foo'], 'FooSnippet1', 'foo', '', body, 'test', SnippetSource.User, generateUuid()); + const snippet = new Snippet(false, ['foo'], 'FooSnippet1', 'foo', '', body, 'test', SnippetSource.User, generateUuid()); assert.strictEqual(snippet.needsClipboard, expected); assert.strictEqual(SnippetParser.guessNeedsClipboard(body), expected); @@ -87,7 +87,7 @@ suite('Snippets', function () { test('Snippet#isTrivial', function () { function assertIsTrivial(body: string, expected: boolean): void { - const snippet = new Snippet(['foo'], 'FooSnippet1', 'foo', '', body, 'test', SnippetSource.User, generateUuid()); + const snippet = new Snippet(false, ['foo'], 'FooSnippet1', 'foo', '', body, 'test', SnippetSource.User, generateUuid()); assert.strictEqual(snippet.isTrivial, expected); } diff --git a/src/vs/workbench/contrib/snippets/test/browser/snippetsRewrite.test.ts b/src/vs/workbench/contrib/snippets/test/browser/snippetsRewrite.test.ts index ac5a579fcfd..54e7e2794f0 100644 --- a/src/vs/workbench/contrib/snippets/test/browser/snippetsRewrite.test.ts +++ b/src/vs/workbench/contrib/snippets/test/browser/snippetsRewrite.test.ts @@ -10,7 +10,7 @@ import { Snippet, SnippetSource } from 'vs/workbench/contrib/snippets/browser/sn suite('SnippetRewrite', function () { function assertRewrite(input: string, expected: string | boolean): void { - const actual = new Snippet(['foo'], 'foo', 'foo', 'foo', input, 'foo', SnippetSource.User, generateUuid()); + const actual = new Snippet(false, ['foo'], 'foo', 'foo', 'foo', input, 'foo', SnippetSource.User, generateUuid()); if (typeof expected === 'boolean') { assert.strictEqual(actual.codeSnippet, input); } else { @@ -48,7 +48,7 @@ suite('SnippetRewrite', function () { }); test('lazy bogous variable rewrite', function () { - const snippet = new Snippet(['fooLang'], 'foo', 'prefix', 'desc', 'This is ${bogous} because it is a ${var}', 'source', SnippetSource.Extension, generateUuid()); + const snippet = new Snippet(false, ['fooLang'], 'foo', 'prefix', 'desc', 'This is ${bogous} because it is a ${var}', 'source', SnippetSource.Extension, generateUuid()); assert.strictEqual(snippet.body, 'This is ${bogous} because it is a ${var}'); assert.strictEqual(snippet.codeSnippet, 'This is ${1:bogous} because it is a ${2:var}'); assert.strictEqual(snippet.isBogous, true); diff --git a/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts index 81156296016..14485ae11a7 100644 --- a/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts +++ b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts @@ -57,6 +57,7 @@ suite('SnippetsService', function () { extensions: ['.fooLang',] })); snippetService = new SimpleSnippetService([new Snippet( + false, ['fooLang'], 'barTest', 'bar', @@ -66,6 +67,7 @@ suite('SnippetsService', function () { SnippetSource.User, generateUuid() ), new Snippet( + false, ['fooLang'], 'bazzTest', 'bazz', @@ -126,8 +128,8 @@ suite('SnippetsService', function () { }); test('snippet completions - with different prefixes', async function () { - snippetService = new SimpleSnippetService([new Snippet( + false, ['fooLang'], 'barTest', 'bar', @@ -137,6 +139,7 @@ suite('SnippetsService', function () { SnippetSource.User, generateUuid() ), new Snippet( + false, ['fooLang'], 'name', 'bar-bar', @@ -208,6 +211,7 @@ suite('SnippetsService', function () { test('Cannot use " Date: Fri, 15 Jul 2022 16:55:14 +0200 Subject: make snippet-contribution a proper contrib-file, remove local registration into contrib-file, move command into their own folder, have a command to populate a file from a top-level snippet --- .../browser/commands/abstractSnippetsActions.ts | 29 +++ .../snippets/browser/commands/configureSnippets.ts | 279 +++++++++++++++++++++ .../snippets/browser/commands/emptyFileSnippets.ts | 114 +++++++++ .../snippets/browser/commands/insertSnippet.ts | 153 +++++++++++ .../browser/commands/surroundWithSnippet.ts | 159 ++++++++++++ .../contrib/snippets/browser/configureSnippets.ts | 278 -------------------- .../contrib/snippets/browser/insertSnippet.ts | 157 ------------ .../snippets/browser/snippetCompletionProvider.ts | 2 +- .../contrib/snippets/browser/snippetPicker.ts | 2 +- .../snippets/browser/snippets.contribution.ts | 63 +++-- .../workbench/contrib/snippets/browser/snippets.ts | 33 +++ .../contrib/snippets/browser/snippetsService.ts | 6 +- .../snippets/browser/surroundWithSnippet.ts | 165 ------------ .../contrib/snippets/browser/tabCompletion.ts | 2 +- .../snippets/test/browser/snippetsService.test.ts | 2 +- 15 files changed, 804 insertions(+), 640 deletions(-) create mode 100644 src/vs/workbench/contrib/snippets/browser/commands/abstractSnippetsActions.ts create mode 100644 src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts create mode 100644 src/vs/workbench/contrib/snippets/browser/commands/emptyFileSnippets.ts create mode 100644 src/vs/workbench/contrib/snippets/browser/commands/insertSnippet.ts create mode 100644 src/vs/workbench/contrib/snippets/browser/commands/surroundWithSnippet.ts delete mode 100644 src/vs/workbench/contrib/snippets/browser/configureSnippets.ts delete mode 100644 src/vs/workbench/contrib/snippets/browser/insertSnippet.ts create mode 100644 src/vs/workbench/contrib/snippets/browser/snippets.ts delete mode 100644 src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/snippets/browser/commands/abstractSnippetsActions.ts b/src/vs/workbench/contrib/snippets/browser/commands/abstractSnippetsActions.ts new file mode 100644 index 00000000000..46f62be9e1c --- /dev/null +++ b/src/vs/workbench/contrib/snippets/browser/commands/abstractSnippetsActions.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { EditorAction2 } from 'vs/editor/browser/editorExtensions'; +import { localize } from 'vs/nls'; +import { Action2, IAction2Options } from 'vs/platform/actions/common/actions'; + +const defaultOptions: Partial = { + category: { + value: localize('snippets', 'Snippets'), + original: 'Snippets' + }, +}; + +export abstract class SnippetsAction extends Action2 { + + constructor(desc: Readonly) { + super({ ...defaultOptions, ...desc }); + } +} + +export abstract class SnippetEditorAction extends EditorAction2 { + + constructor(desc: Readonly) { + super({ ...defaultOptions, ...desc }); + } +} diff --git a/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts b/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts new file mode 100644 index 00000000000..3bc1b0b6b89 --- /dev/null +++ b/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts @@ -0,0 +1,279 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { isValidBasename } from 'vs/base/common/extpath'; +import { extname } from 'vs/base/common/path'; +import { basename, joinPath } from 'vs/base/common/resources'; +import { URI } from 'vs/base/common/uri'; +import { ILanguageService } from 'vs/editor/common/languages/language'; +import * as nls from 'vs/nls'; +import { MenuId } from 'vs/platform/actions/common/actions'; +import { IFileService } from 'vs/platform/files/common/files'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { SnippetsAction } from 'vs/workbench/contrib/snippets/browser/commands/abstractSnippetsActions'; +import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets'; +import { SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; + +namespace ISnippetPick { + export function is(thing: object | undefined): thing is ISnippetPick { + return !!thing && URI.isUri((thing).filepath); + } +} + +interface ISnippetPick extends IQuickPickItem { + filepath: URI; + hint?: true; +} + +async function computePicks(snippetService: ISnippetsService, userDataProfileService: IUserDataProfileService, languageService: ILanguageService) { + + const existing: ISnippetPick[] = []; + const future: ISnippetPick[] = []; + + const seen = new Set(); + + for (const file of await snippetService.getSnippetFiles()) { + + if (file.source === SnippetSource.Extension) { + // skip extension snippets + continue; + } + + if (file.isGlobalSnippets) { + + await file.load(); + + // list scopes for global snippets + const names = new Set(); + outer: for (const snippet of file.data) { + for (const scope of snippet.scopes) { + const name = languageService.getLanguageName(scope); + if (name) { + if (names.size >= 4) { + names.add(`${name}...`); + break outer; + } else { + names.add(name); + } + } + } + } + + existing.push({ + label: basename(file.location), + filepath: file.location, + description: names.size === 0 + ? nls.localize('global.scope', "(global)") + : nls.localize('global.1', "({0})", [...names].join(', ')) + }); + + } else { + // language snippet + const mode = basename(file.location).replace(/\.json$/, ''); + existing.push({ + label: basename(file.location), + description: `(${languageService.getLanguageName(mode)})`, + filepath: file.location + }); + seen.add(mode); + } + } + + const dir = userDataProfileService.currentProfile.snippetsHome; + for (const languageId of languageService.getRegisteredLanguageIds()) { + const label = languageService.getLanguageName(languageId); + if (label && !seen.has(languageId)) { + future.push({ + label: languageId, + description: `(${label})`, + filepath: joinPath(dir, `${languageId}.json`), + hint: true + }); + } + } + + existing.sort((a, b) => { + const a_ext = extname(a.filepath.path); + const b_ext = extname(b.filepath.path); + if (a_ext === b_ext) { + return a.label.localeCompare(b.label); + } else if (a_ext === '.code-snippets') { + return -1; + } else { + return 1; + } + }); + + future.sort((a, b) => { + return a.label.localeCompare(b.label); + }); + + return { existing, future }; +} + +async function createSnippetFile(scope: string, defaultPath: URI, quickInputService: IQuickInputService, fileService: IFileService, textFileService: ITextFileService, opener: IOpenerService) { + + function createSnippetUri(input: string) { + const filename = extname(input) !== '.code-snippets' + ? `${input}.code-snippets` + : input; + return joinPath(defaultPath, filename); + } + + await fileService.createFolder(defaultPath); + + const input = await quickInputService.input({ + placeHolder: nls.localize('name', "Type snippet file name"), + async validateInput(input) { + if (!input) { + return nls.localize('bad_name1', "Invalid file name"); + } + if (!isValidBasename(input)) { + return nls.localize('bad_name2', "'{0}' is not a valid file name", input); + } + if (await fileService.exists(createSnippetUri(input))) { + return nls.localize('bad_name3', "'{0}' already exists", input); + } + return undefined; + } + }); + + if (!input) { + return undefined; + } + + const resource = createSnippetUri(input); + + await textFileService.write(resource, [ + '{', + '\t// Place your ' + scope + ' snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and ', + '\t// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope ', + '\t// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is ', + '\t// used to trigger the snippet and the body will be expanded and inserted. Possible variables are: ', + '\t// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. ', + '\t// Placeholders with the same ids are connected.', + '\t// Example:', + '\t// "Print to console": {', + '\t// \t"scope": "javascript,typescript",', + '\t// \t"prefix": "log",', + '\t// \t"body": [', + '\t// \t\t"console.log(\'$1\');",', + '\t// \t\t"$2"', + '\t// \t],', + '\t// \t"description": "Log output to console"', + '\t// }', + '}' + ].join('\n')); + + await opener.open(resource); + return undefined; +} + +async function createLanguageSnippetFile(pick: ISnippetPick, fileService: IFileService, textFileService: ITextFileService) { + if (await fileService.exists(pick.filepath)) { + return; + } + const contents = [ + '{', + '\t// Place your snippets for ' + pick.label + ' here. Each snippet is defined under a snippet name and has a prefix, body and ', + '\t// description. The prefix is what is used to trigger the snippet and the body will be expanded and inserted. Possible variables are:', + '\t// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. Placeholders with the ', + '\t// same ids are connected.', + '\t// Example:', + '\t// "Print to console": {', + '\t// \t"prefix": "log",', + '\t// \t"body": [', + '\t// \t\t"console.log(\'$1\');",', + '\t// \t\t"$2"', + '\t// \t],', + '\t// \t"description": "Log output to console"', + '\t// }', + '}' + ].join('\n'); + await textFileService.write(pick.filepath, contents); +} + +export class ConfigureSnippets extends SnippetsAction { + + 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 }, + ] + }); + } + + async run(accessor: ServicesAccessor): Promise { + + const snippetService = accessor.get(ISnippetsService); + const quickInputService = accessor.get(IQuickInputService); + const opener = accessor.get(IOpenerService); + const languageService = accessor.get(ILanguageService); + const userDataProfileService = accessor.get(IUserDataProfileService); + const workspaceService = accessor.get(IWorkspaceContextService); + const fileService = accessor.get(IFileService); + const textFileService = accessor.get(ITextFileService); + + const picks = await computePicks(snippetService, userDataProfileService, 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: userDataProfileService.currentProfile.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 (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 + }); + + 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); + } + + } +} diff --git a/src/vs/workbench/contrib/snippets/browser/commands/emptyFileSnippets.ts b/src/vs/workbench/contrib/snippets/browser/commands/emptyFileSnippets.ts new file mode 100644 index 00000000000..85376e61a70 --- /dev/null +++ b/src/vs/workbench/contrib/snippets/browser/commands/emptyFileSnippets.ts @@ -0,0 +1,114 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { groupBy, isFalsyOrEmpty } from 'vs/base/common/arrays'; +import { compare } from 'vs/base/common/strings'; +import { getCodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ILanguageService } from 'vs/editor/common/languages/language'; +import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; +import { localize } from 'vs/nls'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; +import { SnippetsAction } from 'vs/workbench/contrib/snippets/browser/commands/abstractSnippetsActions'; +import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets'; +import { Snippet } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; + +export class SelectSnippetForEmptyFile extends SnippetsAction { + + constructor() { + super({ + id: 'workbench.action.populateFromSnippet', + title: { + value: localize('label', 'Populate File from Snippet'), + original: 'Populate File from Snippet' + }, + f1: true, + }); + } + + async run(accessor: ServicesAccessor): Promise { + const snippetService = accessor.get(ISnippetsService); + const quickInputService = accessor.get(IQuickInputService); + const editorService = accessor.get(IEditorService); + const langService = accessor.get(ILanguageService); + + const editor = getCodeEditor(editorService.activeTextEditorControl); + if (!editor || !editor.hasModel()) { + return; + } + + const snippets = await snippetService.getSnippets(undefined, { topLevelSnippets: true, noRecencySort: true, includeNoPrefixSnippets: true }); + if (snippets.length === 0) { + return; + } + + const selection = await this._pick(quickInputService, langService, snippets); + if (!selection) { + return; + } + + if (editor.hasModel()) { + // apply snippet edit -> replaces everything + SnippetController2.get(editor)?.apply([{ + range: editor.getModel().getFullModelRange(), + template: selection.snippet.body + }]); + + // set language if possible + if (langService.isRegisteredLanguageId(selection.langId)) { + editor.getModel().setMode(selection.langId); + } + } + } + + private async _pick(quickInputService: IQuickInputService, langService: ILanguageService, snippets: Snippet[]) { + + // spread snippet onto each language it supports + type SnippetAndLanguage = { langId: string; snippet: Snippet }; + const all: SnippetAndLanguage[] = []; + for (const snippet of snippets) { + if (isFalsyOrEmpty(snippet.scopes)) { + all.push({ langId: '', snippet }); + } else { + for (const langId of snippet.scopes) { + all.push({ langId, snippet }); + } + } + } + + type SnippetAndLanguagePick = IQuickPickItem & { snippet: SnippetAndLanguage }; + const picks: (SnippetAndLanguagePick | IQuickPickSeparator)[] = []; + + const groups = groupBy(all, (a, b) => compare(a.langId, b.langId)); + + for (const group of groups) { + let first = true; + for (const item of group) { + + if (first) { + picks.push({ + type: 'separator', + label: langService.getLanguageName(item.langId) ?? item.langId + }); + first = false; + } + + picks.push({ + snippet: item, + label: item.snippet.prefix || item.snippet.name, + detail: item.snippet.description + }); + } + } + + const pick = await quickInputService.pick(picks, { + placeHolder: localize('placeholder', 'Select a snippet'), + matchOnDetail: true, + }); + + return pick?.snippet; + } +} diff --git a/src/vs/workbench/contrib/snippets/browser/commands/insertSnippet.ts b/src/vs/workbench/contrib/snippets/browser/commands/insertSnippet.ts new file mode 100644 index 00000000000..aba3f4c5582 --- /dev/null +++ b/src/vs/workbench/contrib/snippets/browser/commands/insertSnippet.ts @@ -0,0 +1,153 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { ILanguageService } from 'vs/editor/common/languages/language'; +import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; +import * as nls from 'vs/nls'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { SnippetEditorAction } from 'vs/workbench/contrib/snippets/browser/commands/abstractSnippetsActions'; +import { pickSnippet } from 'vs/workbench/contrib/snippets/browser/snippetPicker'; +import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets'; +import { Snippet, SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; + +class Args { + + static fromUser(arg: any): Args { + if (!arg || typeof arg !== 'object') { + return Args._empty; + } + let { snippet, name, langId } = arg; + if (typeof snippet !== 'string') { + snippet = undefined; + } + if (typeof name !== 'string') { + name = undefined; + } + if (typeof langId !== 'string') { + langId = undefined; + } + return new Args(snippet, name, langId); + } + + private static readonly _empty = new Args(undefined, undefined, undefined); + + private constructor( + public readonly snippet: string | undefined, + public readonly name: string | undefined, + public readonly langId: string | undefined + ) { } +} + +export class InsertSnippetAction extends SnippetEditorAction { + + constructor() { + super({ + id: 'editor.action.insertSnippet', + title: { + value: nls.localize('snippet.suggestions.label', "Insert Snippet"), + original: 'Insert Snippet' + }, + f1: true, + precondition: EditorContextKeys.writable, + description: { + description: `Insert Snippet`, + args: [{ + name: 'args', + schema: { + 'type': 'object', + 'properties': { + 'snippet': { + 'type': 'string' + }, + 'langId': { + 'type': 'string', + + }, + 'name': { + 'type': 'string' + } + }, + } + }] + } + }); + } + + async runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, arg: any) { + + const languageService = accessor.get(ILanguageService); + const snippetService = accessor.get(ISnippetsService); + + if (!editor.hasModel()) { + return; + } + + const clipboardService = accessor.get(IClipboardService); + const instaService = accessor.get(IInstantiationService); + + const snippet = await new Promise((resolve, reject) => { + + const { lineNumber, column } = editor.getPosition(); + const { snippet, name, langId } = Args.fromUser(arg); + + if (snippet) { + return resolve(new Snippet( + false, + [], + '', + '', + '', + snippet, + '', + SnippetSource.User, + `random/${Math.random()}` + )); + } + + let languageId: string; + if (langId) { + if (!languageService.isRegisteredLanguageId(langId)) { + return resolve(undefined); + } + languageId = langId; + } else { + editor.getModel().tokenization.tokenizeIfCheap(lineNumber); + languageId = editor.getModel().getLanguageIdAtPosition(lineNumber, column); + + // 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 + if (!languageService.getLanguageName(languageId)) { + languageId = editor.getModel().getLanguageId(); + } + } + + if (name) { + // take selected snippet + snippetService.getSnippets(languageId, { includeNoPrefixSnippets: true }) + .then(snippets => snippets.find(snippet => snippet.name === name)) + .then(resolve, reject); + + } else { + // let user pick a snippet + resolve(instaService.invokeFunction(pickSnippet, languageId)); + } + }); + + if (!snippet) { + return; + } + let clipboardText: string | undefined; + if (snippet.needsClipboard) { + clipboardText = await clipboardService.readText(); + } + SnippetController2.get(editor)?.insert(snippet.codeSnippet, { clipboardText }); + snippetService.updateUsageTimestamp(snippet); + } +} diff --git a/src/vs/workbench/contrib/snippets/browser/commands/surroundWithSnippet.ts b/src/vs/workbench/contrib/snippets/browser/commands/surroundWithSnippet.ts new file mode 100644 index 00000000000..7a771724f3a --- /dev/null +++ b/src/vs/workbench/contrib/snippets/browser/commands/surroundWithSnippet.ts @@ -0,0 +1,159 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IDisposable } from 'vs/base/common/lifecycle'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { Position } from 'vs/editor/common/core/position'; +import { IRange, Range } from 'vs/editor/common/core/range'; +import { Selection } from 'vs/editor/common/core/selection'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { CodeAction, CodeActionList, CodeActionProvider } from 'vs/editor/common/languages'; +import { ITextModel } from 'vs/editor/common/model'; +import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; +import { CodeActionKind } from 'vs/editor/contrib/codeAction/browser/types'; +import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; +import { localize } from 'vs/nls'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { SnippetEditorAction } from 'vs/workbench/contrib/snippets/browser/commands/abstractSnippetsActions'; +import { pickSnippet } from 'vs/workbench/contrib/snippets/browser/snippetPicker'; +import { Snippet } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; +import { ISnippetsService } from '../snippets'; + +async function getSurroundableSnippets(snippetsService: ISnippetsService, model: ITextModel, position: Position, includeDisabledSnippets: boolean): Promise { + + const { lineNumber, column } = position; + model.tokenization.tokenizeIfCheap(lineNumber); + const languageId = model.getLanguageIdAtPosition(lineNumber, column); + + const allSnippets = await snippetsService.getSnippets(languageId, { includeNoPrefixSnippets: true, includeDisabledSnippets }); + return allSnippets.filter(snippet => snippet.usesSelection); +} + +export class SurroundWithSnippetEditorAction extends SnippetEditorAction { + + static readonly options = { + id: 'editor.action.surroundWithSnippet', + title: { + value: localize('label', 'Surround With Snippet...'), + original: 'Surround With Snippet...' + } + }; + + constructor() { + super({ + ...SurroundWithSnippetEditorAction.options, + precondition: ContextKeyExpr.and( + EditorContextKeys.writable, + EditorContextKeys.hasNonEmptySelection + ), + f1: true, + }); + } + + async runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor) { + if (!editor.hasModel()) { + return; + } + + const instaService = accessor.get(IInstantiationService); + const snippetsService = accessor.get(ISnippetsService); + const clipboardService = accessor.get(IClipboardService); + + const snippets = await getSurroundableSnippets(snippetsService, editor.getModel(), editor.getPosition(), true); + if (!snippets.length) { + return; + } + + const snippet = await instaService.invokeFunction(pickSnippet, snippets); + if (!snippet) { + return; + } + + let clipboardText: string | undefined; + if (snippet.needsClipboard) { + clipboardText = await clipboardService.readText(); + } + + SnippetController2.get(editor)?.insert(snippet.codeSnippet, { clipboardText }); + snippetsService.updateUsageTimestamp(snippet); + } +} + + +export class SurroundWithSnippetCodeActionProvider implements CodeActionProvider, IWorkbenchContribution { + + private static readonly _MAX_CODE_ACTIONS = 4; + + private static readonly _overflowCommandCodeAction: CodeAction = { + kind: CodeActionKind.Refactor.value, + title: SurroundWithSnippetEditorAction.options.title.value, + command: { + id: SurroundWithSnippetEditorAction.options.id, + title: SurroundWithSnippetEditorAction.options.title.value, + }, + }; + + private readonly _registration: IDisposable; + + constructor( + @ISnippetsService private readonly _snippetService: ISnippetsService, + @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, + ) { + this._registration = languageFeaturesService.codeActionProvider.register('*', this); + } + + dispose(): void { + this._registration.dispose(); + } + + async provideCodeActions(model: ITextModel, range: Range | Selection): Promise { + + if (range.isEmpty()) { + return undefined; + } + + const position = Selection.isISelection(range) ? range.getPosition() : range.getStartPosition(); + const snippets = await getSurroundableSnippets(this._snippetService, model, position, false); + if (!snippets.length) { + return undefined; + } + + const actions: CodeAction[] = []; + const hasMore = snippets.length > SurroundWithSnippetCodeActionProvider._MAX_CODE_ACTIONS; + const len = Math.min(snippets.length, SurroundWithSnippetCodeActionProvider._MAX_CODE_ACTIONS); + + for (let i = 0; i < len; i++) { + actions.push(this._makeCodeActionForSnippet(snippets[i], model, range)); + } + if (hasMore) { + actions.push(SurroundWithSnippetCodeActionProvider._overflowCommandCodeAction); + } + return { + actions, + dispose() { } + }; + } + + private _makeCodeActionForSnippet(snippet: Snippet, model: ITextModel, range: IRange): CodeAction { + return { + title: localize('codeAction', "Surround With: {0}", snippet.name), + kind: CodeActionKind.Refactor.value, + edit: { + edits: [{ + versionId: model.getVersionId(), + resource: model.uri, + textEdit: { + range, + text: snippet.body, + insertAsSnippet: true, + } + }] + } + }; + } +} diff --git a/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts b/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts deleted file mode 100644 index 917e0da44e4..00000000000 --- a/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts +++ /dev/null @@ -1,278 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from 'vs/nls'; -import { ILanguageService } from 'vs/editor/common/languages/language'; -import { extname } from 'vs/base/common/path'; -import { MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/actions'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { URI } from 'vs/base/common/uri'; -import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets.contribution'; -import { IQuickPickItem, IQuickInputService, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; -import { SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IFileService } from 'vs/platform/files/common/files'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { isValidBasename } from 'vs/base/common/extpath'; -import { joinPath, basename } from 'vs/base/common/resources'; -import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; - -namespace ISnippetPick { - export function is(thing: object | undefined): thing is ISnippetPick { - return !!thing && URI.isUri((thing).filepath); - } -} - -interface ISnippetPick extends IQuickPickItem { - filepath: URI; - hint?: true; -} - -async function computePicks(snippetService: ISnippetsService, userDataProfileService: IUserDataProfileService, languageService: ILanguageService) { - - const existing: ISnippetPick[] = []; - const future: ISnippetPick[] = []; - - const seen = new Set(); - - for (const file of await snippetService.getSnippetFiles()) { - - if (file.source === SnippetSource.Extension) { - // skip extension snippets - continue; - } - - if (file.isGlobalSnippets) { - - await file.load(); - - // list scopes for global snippets - const names = new Set(); - outer: for (const snippet of file.data) { - for (const scope of snippet.scopes) { - const name = languageService.getLanguageName(scope); - if (name) { - if (names.size >= 4) { - names.add(`${name}...`); - break outer; - } else { - names.add(name); - } - } - } - } - - existing.push({ - label: basename(file.location), - filepath: file.location, - description: names.size === 0 - ? nls.localize('global.scope', "(global)") - : nls.localize('global.1', "({0})", [...names].join(', ')) - }); - - } else { - // language snippet - const mode = basename(file.location).replace(/\.json$/, ''); - existing.push({ - label: basename(file.location), - description: `(${languageService.getLanguageName(mode)})`, - filepath: file.location - }); - seen.add(mode); - } - } - - const dir = userDataProfileService.currentProfile.snippetsHome; - for (const languageId of languageService.getRegisteredLanguageIds()) { - const label = languageService.getLanguageName(languageId); - if (label && !seen.has(languageId)) { - future.push({ - label: languageId, - description: `(${label})`, - filepath: joinPath(dir, `${languageId}.json`), - hint: true - }); - } - } - - existing.sort((a, b) => { - const a_ext = extname(a.filepath.path); - const b_ext = extname(b.filepath.path); - if (a_ext === b_ext) { - return a.label.localeCompare(b.label); - } else if (a_ext === '.code-snippets') { - return -1; - } else { - return 1; - } - }); - - future.sort((a, b) => { - return a.label.localeCompare(b.label); - }); - - return { existing, future }; -} - -async function createSnippetFile(scope: string, defaultPath: URI, quickInputService: IQuickInputService, fileService: IFileService, textFileService: ITextFileService, opener: IOpenerService) { - - function createSnippetUri(input: string) { - const filename = extname(input) !== '.code-snippets' - ? `${input}.code-snippets` - : input; - return joinPath(defaultPath, filename); - } - - await fileService.createFolder(defaultPath); - - const input = await quickInputService.input({ - placeHolder: nls.localize('name', "Type snippet file name"), - async validateInput(input) { - if (!input) { - return nls.localize('bad_name1', "Invalid file name"); - } - if (!isValidBasename(input)) { - return nls.localize('bad_name2', "'{0}' is not a valid file name", input); - } - if (await fileService.exists(createSnippetUri(input))) { - return nls.localize('bad_name3', "'{0}' already exists", input); - } - return undefined; - } - }); - - if (!input) { - return undefined; - } - - const resource = createSnippetUri(input); - - await textFileService.write(resource, [ - '{', - '\t// Place your ' + scope + ' snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and ', - '\t// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope ', - '\t// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is ', - '\t// used to trigger the snippet and the body will be expanded and inserted. Possible variables are: ', - '\t// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. ', - '\t// Placeholders with the same ids are connected.', - '\t// Example:', - '\t// "Print to console": {', - '\t// \t"scope": "javascript,typescript",', - '\t// \t"prefix": "log",', - '\t// \t"body": [', - '\t// \t\t"console.log(\'$1\');",', - '\t// \t\t"$2"', - '\t// \t],', - '\t// \t"description": "Log output to console"', - '\t// }', - '}' - ].join('\n')); - - await opener.open(resource); - return undefined; -} - -async function createLanguageSnippetFile(pick: ISnippetPick, fileService: IFileService, textFileService: ITextFileService) { - if (await fileService.exists(pick.filepath)) { - return; - } - const contents = [ - '{', - '\t// Place your snippets for ' + pick.label + ' here. Each snippet is defined under a snippet name and has a prefix, body and ', - '\t// description. The prefix is what is used to trigger the snippet and the body will be expanded and inserted. Possible variables are:', - '\t// $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. Placeholders with the ', - '\t// same ids are connected.', - '\t// Example:', - '\t// "Print to console": {', - '\t// \t"prefix": "log",', - '\t// \t"body": [', - '\t// \t\t"console.log(\'$1\');",', - '\t// \t\t"$2"', - '\t// \t],', - '\t// \t"description": "Log output to console"', - '\t// }', - '}' - ].join('\n'); - await textFileService.write(pick.filepath, contents); -} - -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 }, - ] - }); - } - - async run(accessor: ServicesAccessor, ...args: any[]): Promise { - - const snippetService = accessor.get(ISnippetsService); - const quickInputService = accessor.get(IQuickInputService); - const opener = accessor.get(IOpenerService); - const languageService = accessor.get(ILanguageService); - const userDataProfileService = accessor.get(IUserDataProfileService); - const workspaceService = accessor.get(IWorkspaceContextService); - const fileService = accessor.get(IFileService); - const textFileService = accessor.get(ITextFileService); - - const picks = await computePicks(snippetService, userDataProfileService, 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: userDataProfileService.currentProfile.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 (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 - }); - - 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); - } - - } -}); diff --git a/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts b/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts deleted file mode 100644 index f52422764ca..00000000000 --- a/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts +++ /dev/null @@ -1,157 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from 'vs/nls'; -import { registerEditorAction, ServicesAccessor, EditorAction } from 'vs/editor/browser/editorExtensions'; -import { ILanguageService } from 'vs/editor/common/languages/language'; -import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets.contribution'; -import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; -import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { Snippet, SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; -import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { pickSnippet } from 'vs/workbench/contrib/snippets/browser/snippetPicker'; - - -class Args { - - static fromUser(arg: any): Args { - if (!arg || typeof arg !== 'object') { - return Args._empty; - } - let { snippet, name, langId } = arg; - if (typeof snippet !== 'string') { - snippet = undefined; - } - if (typeof name !== 'string') { - name = undefined; - } - if (typeof langId !== 'string') { - langId = undefined; - } - return new Args(snippet, name, langId); - } - - private static readonly _empty = new Args(undefined, undefined, undefined); - - private constructor( - public readonly snippet: string | undefined, - public readonly name: string | undefined, - public readonly langId: string | undefined - ) { } -} - -class InsertSnippetAction extends EditorAction { - - constructor() { - super({ - id: 'editor.action.insertSnippet', - label: nls.localize('snippet.suggestions.label', "Insert Snippet"), - alias: 'Insert Snippet', - precondition: EditorContextKeys.writable, - description: { - description: `Insert Snippet`, - args: [{ - name: 'args', - schema: { - 'type': 'object', - 'properties': { - 'snippet': { - 'type': 'string' - }, - 'langId': { - 'type': 'string', - - }, - 'name': { - 'type': 'string' - } - }, - } - }] - } - }); - } - - async run(accessor: ServicesAccessor, editor: ICodeEditor, arg: any): Promise { - const languageService = accessor.get(ILanguageService); - const snippetService = accessor.get(ISnippetsService); - - if (!editor.hasModel()) { - return; - } - - const clipboardService = accessor.get(IClipboardService); - const instaService = accessor.get(IInstantiationService); - - const snippet = await new Promise((resolve, reject) => { - - const { lineNumber, column } = editor.getPosition(); - const { snippet, name, langId } = Args.fromUser(arg); - - if (snippet) { - return resolve(new Snippet( - false, - [], - '', - '', - '', - snippet, - '', - SnippetSource.User, - `random/${Math.random()}` - )); - } - - let languageId: string; - if (langId) { - if (!languageService.isRegisteredLanguageId(langId)) { - return resolve(undefined); - } - languageId = langId; - } else { - editor.getModel().tokenization.tokenizeIfCheap(lineNumber); - languageId = editor.getModel().getLanguageIdAtPosition(lineNumber, column); - - // 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 - if (!languageService.getLanguageName(languageId)) { - languageId = editor.getModel().getLanguageId(); - } - } - - if (name) { - // take selected snippet - snippetService.getSnippets(languageId, { includeNoPrefixSnippets: true }) - .then(snippets => snippets.find(snippet => snippet.name === name)) - .then(resolve, reject); - - } else { - // let user pick a snippet - resolve(instaService.invokeFunction(pickSnippet, languageId)); - } - }); - - if (!snippet) { - return; - } - let clipboardText: string | undefined; - if (snippet.needsClipboard) { - clipboardText = await clipboardService.readText(); - } - SnippetController2.get(editor)?.insert(snippet.codeSnippet, { clipboardText }); - snippetService.updateUsageTimestamp(snippet); - } -} - -registerEditorAction(InsertSnippetAction); - -// compatibility command to make sure old keybinding are still working -CommandsRegistry.registerCommand('editor.action.showSnippets', accessor => { - return accessor.get(ICommandService).executeCommand('editor.action.insertSnippet'); -}); diff --git a/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts index 7e8c6555e5c..6569bf2ce1f 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts @@ -12,7 +12,7 @@ import { CompletionItem, CompletionItemKind, CompletionItemProvider, CompletionL import { ILanguageService } from 'vs/editor/common/languages/language'; import { SnippetParser } from 'vs/editor/contrib/snippet/browser/snippetParser'; import { localize } from 'vs/nls'; -import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets.contribution'; +import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets'; import { Snippet, SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; import { isPatternInWord } from 'vs/base/common/filters'; import { StopWatch } from 'vs/base/common/stopwatch'; diff --git a/src/vs/workbench/contrib/snippets/browser/snippetPicker.ts b/src/vs/workbench/contrib/snippets/browser/snippetPicker.ts index 0814ea32312..0f72c05ab50 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetPicker.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetPicker.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets.contribution'; +import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets'; import { Snippet, SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; import { IQuickPickItem, IQuickInputService, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; import { Codicon } from 'vs/base/common/codicons'; diff --git a/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts b/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts index ac1e213fc55..39f0c8233c5 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts @@ -4,38 +4,37 @@ *--------------------------------------------------------------------------------------------*/ import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; -import { Registry } from 'vs/platform/registry/common/platform'; -import * as JSONContributionRegistry from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import * as nls from 'vs/nls'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { SnippetFile, Snippet } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; - -export const ISnippetsService = createDecorator('snippetService'); - -export interface ISnippetGetOptions { - includeDisabledSnippets?: boolean; - includeNoPrefixSnippets?: boolean; - noRecencySort?: boolean; - topLevelSnippets?: boolean; -} - -export interface ISnippetsService { - - readonly _serviceBrand: undefined; - - getSnippetFiles(): Promise>; - - isEnabled(snippet: Snippet): boolean; - - updateEnablement(snippet: Snippet, enabled: boolean): void; - - updateUsageTimestamp(snippet: Snippet): void; - - getSnippets(languageId: string | undefined, opt?: ISnippetGetOptions): Promise; - - getSnippetsSync(languageId: string, opt?: ISnippetGetOptions): Snippet[]; -} - +import { registerAction2 } from 'vs/platform/actions/common/actions'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import * as JSONContributionRegistry from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { ConfigureSnippets } from 'vs/workbench/contrib/snippets/browser/commands/configureSnippets'; +import { SelectSnippetForEmptyFile } from 'vs/workbench/contrib/snippets/browser/commands/emptyFileSnippets'; +import { InsertSnippetAction } from 'vs/workbench/contrib/snippets/browser/commands/insertSnippet'; +import { SurroundWithSnippetCodeActionProvider, SurroundWithSnippetEditorAction } from 'vs/workbench/contrib/snippets/browser/commands/surroundWithSnippet'; +import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets'; +import { SnippetsService } from 'vs/workbench/contrib/snippets/browser/snippetsService'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; + +import 'vs/workbench/contrib/snippets/browser/tabCompletion'; + +// service +registerSingleton(ISnippetsService, SnippetsService, true); + +// actions +registerAction2(InsertSnippetAction); +CommandsRegistry.registerCommandAlias('editor.action.showSnippets', 'editor.action.insertSnippet'); +registerAction2(SurroundWithSnippetEditorAction); +registerAction2(ConfigureSnippets); +registerAction2(SelectSnippetForEmptyFile); + +// workbench contribs +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(SurroundWithSnippetCodeActionProvider, LifecyclePhase.Restored); + +// schema const languageScopeSchemaId = 'vscode://schemas/snippets'; const snippetSchemaProperties: IJSONSchemaMap = { @@ -45,7 +44,7 @@ const snippetSchemaProperties: IJSONSchemaMap = { }, isTopLevel: { description: nls.localize('snippetSchema.json.isTopLevel', 'The snippet is only applicable to empty files.'), - type: 'string' + type: 'boolean' }, body: { markdownDescription: nls.localize('snippetSchema.json.body', 'The snippet content. Use `$1`, `${1:defaultText}` to define cursor positions, use `$0` for the final cursor position. Insert variable values with `${varName}` and `${varName:defaultText}`, e.g. `This is file: $TM_FILENAME`.'), diff --git a/src/vs/workbench/contrib/snippets/browser/snippets.ts b/src/vs/workbench/contrib/snippets/browser/snippets.ts new file mode 100644 index 00000000000..fa485ab0f2f --- /dev/null +++ b/src/vs/workbench/contrib/snippets/browser/snippets.ts @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { SnippetFile, Snippet } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; + +export const ISnippetsService = createDecorator('snippetService'); + +export interface ISnippetGetOptions { + includeDisabledSnippets?: boolean; + includeNoPrefixSnippets?: boolean; + noRecencySort?: boolean; + topLevelSnippets?: boolean; +} + +export interface ISnippetsService { + + readonly _serviceBrand: undefined; + + getSnippetFiles(): Promise>; + + isEnabled(snippet: Snippet): boolean; + + updateEnablement(snippet: Snippet, enabled: boolean): void; + + updateUsageTimestamp(snippet: Snippet): void; + + getSnippets(languageId: string | undefined, opt?: ISnippetGetOptions): Promise; + + getSnippetsSync(languageId: string, opt?: ISnippetGetOptions): Snippet[]; +} diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts index 918411b564c..3c5ed85f665 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts @@ -14,11 +14,10 @@ import { setSnippetSuggestSupport } from 'vs/editor/contrib/suggest/browser/sugg import { localize } from 'vs/nls'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { FileChangeType, IFileService } from 'vs/platform/files/common/files'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; import { IWorkspace, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { ISnippetGetOptions, ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets.contribution'; +import { ISnippetGetOptions, ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets'; import { Snippet, SnippetFile, SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { languagesExtPoint } from 'vs/workbench/services/language/common/languageService'; @@ -204,7 +203,7 @@ class SnippetUsageTimestamps { } } -class SnippetsService implements ISnippetsService { +export class SnippetsService implements ISnippetsService { declare readonly _serviceBrand: undefined; @@ -504,7 +503,6 @@ class SnippetsService implements ISnippetsService { } } -registerSingleton(ISnippetsService, SnippetsService, true); export interface ISimpleModel { getLineContent(lineNumber: number): string; diff --git a/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts b/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts deleted file mode 100644 index 7c0842812dd..00000000000 --- a/src/vs/workbench/contrib/snippets/browser/surroundWithSnippet.ts +++ /dev/null @@ -1,165 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorAction2 } from 'vs/editor/browser/editorExtensions'; -import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2'; -import { localize } from 'vs/nls'; -import { registerAction2 } from 'vs/platform/actions/common/actions'; -import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { pickSnippet } from 'vs/workbench/contrib/snippets/browser/snippetPicker'; -import { ISnippetsService } from './snippets.contribution'; -import { IDisposable } from 'vs/base/common/lifecycle'; -import { ITextModel } from 'vs/editor/common/model'; -import { CodeAction, CodeActionProvider, CodeActionList } from 'vs/editor/common/languages'; -import { CodeActionKind } from 'vs/editor/contrib/codeAction/browser/types'; -import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; -import { Range, IRange } from 'vs/editor/common/core/range'; -import { Selection } from 'vs/editor/common/core/selection'; -import { Snippet } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { Position } from 'vs/editor/common/core/position'; - -async function getSurroundableSnippets(snippetsService: ISnippetsService, model: ITextModel, position: Position, includeDisabledSnippets: boolean): Promise { - - const { lineNumber, column } = position; - model.tokenization.tokenizeIfCheap(lineNumber); - const languageId = model.getLanguageIdAtPosition(lineNumber, column); - - const allSnippets = await snippetsService.getSnippets(languageId, { includeNoPrefixSnippets: true, includeDisabledSnippets }); - return allSnippets.filter(snippet => snippet.usesSelection); -} - -class SurroundWithSnippetEditorAction extends EditorAction2 { - - static readonly options = { - id: 'editor.action.surroundWithSnippet', - title: { - value: localize('label', 'Surround With Snippet...'), - original: 'Surround With Snippet...' - } - }; - - constructor() { - super({ - ...SurroundWithSnippetEditorAction.options, - precondition: ContextKeyExpr.and( - EditorContextKeys.writable, - EditorContextKeys.hasNonEmptySelection - ), - f1: true, - }); - } - - async runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor) { - if (!editor.hasModel()) { - return; - } - - const instaService = accessor.get(IInstantiationService); - const snippetsService = accessor.get(ISnippetsService); - const clipboardService = accessor.get(IClipboardService); - - const snippets = await getSurroundableSnippets(snippetsService, editor.getModel(), editor.getPosition(), true); - if (!snippets.length) { - return; - } - - const snippet = await instaService.invokeFunction(pickSnippet, snippets); - if (!snippet) { - return; - } - - let clipboardText: string | undefined; - if (snippet.needsClipboard) { - clipboardText = await clipboardService.readText(); - } - - SnippetController2.get(editor)?.insert(snippet.codeSnippet, { clipboardText }); - snippetsService.updateUsageTimestamp(snippet); - } -} - - -class SurroundWithSnippetCodeActionProvider implements CodeActionProvider, IWorkbenchContribution { - - private static readonly _MAX_CODE_ACTIONS = 4; - - private static readonly _overflowCommandCodeAction: CodeAction = { - kind: CodeActionKind.Refactor.value, - title: SurroundWithSnippetEditorAction.options.title.value, - command: { - id: SurroundWithSnippetEditorAction.options.id, - title: SurroundWithSnippetEditorAction.options.title.value, - }, - }; - - private readonly _registration: IDisposable; - - constructor( - @ISnippetsService private readonly _snippetService: ISnippetsService, - @ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService, - ) { - this._registration = languageFeaturesService.codeActionProvider.register('*', this); - } - - dispose(): void { - this._registration.dispose(); - } - - async provideCodeActions(model: ITextModel, range: Range | Selection): Promise { - - if (range.isEmpty()) { - return undefined; - } - - const position = Selection.isISelection(range) ? range.getPosition() : range.getStartPosition(); - const snippets = await getSurroundableSnippets(this._snippetService, model, position, false); - if (!snippets.length) { - return undefined; - } - - const actions: CodeAction[] = []; - const hasMore = snippets.length > SurroundWithSnippetCodeActionProvider._MAX_CODE_ACTIONS; - const len = Math.min(snippets.length, SurroundWithSnippetCodeActionProvider._MAX_CODE_ACTIONS); - - for (let i = 0; i < len; i++) { - actions.push(this._makeCodeActionForSnippet(snippets[i], model, range)); - } - if (hasMore) { - actions.push(SurroundWithSnippetCodeActionProvider._overflowCommandCodeAction); - } - return { - actions, - dispose() { } - }; - } - - private _makeCodeActionForSnippet(snippet: Snippet, model: ITextModel, range: IRange): CodeAction { - return { - title: localize('codeAction', "Surround With: {0}", snippet.name), - kind: CodeActionKind.Refactor.value, - edit: { - edits: [{ - versionId: model.getVersionId(), - resource: model.uri, - textEdit: { - range, - text: snippet.body, - insertAsSnippet: true, - } - }] - } - }; - } -} - -registerAction2(SurroundWithSnippetEditorAction); -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(SurroundWithSnippetCodeActionProvider, LifecyclePhase.Restored); diff --git a/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts b/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts index 1f4afaa0a94..0abc197abeb 100644 --- a/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts +++ b/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts @@ -6,7 +6,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { RawContextKey, IContextKeyService, ContextKeyExpr, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { ISnippetsService } from './snippets.contribution'; +import { ISnippetsService } from './snippets'; import { getNonWhitespacePrefix } from './snippetsService'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; diff --git a/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts index 14485ae11a7..a414f31246d 100644 --- a/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts +++ b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { SnippetCompletion, SnippetCompletionProvider } from 'vs/workbench/contrib/snippets/browser/snippetCompletionProvider'; import { Position } from 'vs/editor/common/core/position'; import { createModelServices, instantiateTextModel } from 'vs/editor/test/common/testTextModel'; -import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets.contribution'; +import { ISnippetsService } from "vs/workbench/contrib/snippets/browser/snippets"; import { Snippet, SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; import { CompletionContext, CompletionItemLabel, CompletionItemRanges, CompletionTriggerKind } from 'vs/editor/common/languages'; import { DisposableStore } from 'vs/base/common/lifecycle'; -- cgit v1.2.3 From f485d5e98777a59bf50d39c2b958fe27ab77271d Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Fri, 15 Jul 2022 08:08:13 -0700 Subject: set to udf after (#155304) --- src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index 0499c4cd0b5..22c53792466 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -882,6 +882,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { watchingProblemMatcher.aboutToStart(); let delayer: Async.Delayer | undefined = undefined; [terminal, error] = this._terminalForTask ? [this._terminalForTask, undefined] : await this._createTerminal(task, resolver, workspaceFolder); + this._terminalForTask = undefined; if (error) { return Promise.reject(new Error((error).message)); @@ -964,6 +965,7 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { }); } else { [terminal, error] = this._terminalForTask ? [this._terminalForTask, undefined] : await this._createTerminal(task, resolver, workspaceFolder); + this._terminalForTask = undefined; if (error) { return Promise.reject(new Error((error).message)); -- cgit v1.2.3 From 7fd96dbcf5c41c9b09b12cf5cc7153dae67c47da Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 15 Jul 2022 17:24:41 +0200 Subject: tweak label --- .../workbench/contrib/snippets/browser/commands/emptyFileSnippets.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/snippets/browser/commands/emptyFileSnippets.ts b/src/vs/workbench/contrib/snippets/browser/commands/emptyFileSnippets.ts index 85376e61a70..739cb837dfa 100644 --- a/src/vs/workbench/contrib/snippets/browser/commands/emptyFileSnippets.ts +++ b/src/vs/workbench/contrib/snippets/browser/commands/emptyFileSnippets.ts @@ -22,8 +22,8 @@ export class SelectSnippetForEmptyFile extends SnippetsAction { super({ id: 'workbench.action.populateFromSnippet', title: { - value: localize('label', 'Populate File from Snippet'), - original: 'Populate File from Snippet' + value: localize('label', 'Populate from Snippet'), + original: 'Populate from Snippet' }, f1: true, }); -- cgit v1.2.3 From 92329e484eb6bd0c7d35c10ee41f24c9cf3a2e93 Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 15 Jul 2022 18:05:41 +0200 Subject: add "start with snippet" to empty editor text, change things to use formatted text renderer (fixes https://github.com/microsoft/vscode/issues/155293) --- .../codeEditor/browser/untitledTextEditorHint.ts | 106 ++++++++++----------- .../snippets/browser/commands/emptyFileSnippets.ts | 4 +- 2 files changed, 52 insertions(+), 58 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/codeEditor/browser/untitledTextEditorHint.ts b/src/vs/workbench/contrib/codeEditor/browser/untitledTextEditorHint.ts index 14c97806e09..bac3e753519 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/untitledTextEditorHint.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/untitledTextEditorHint.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { localize } from 'vs/nls'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; @@ -17,9 +17,10 @@ import { Schemas } from 'vs/base/common/network'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; -import { EventType as GestureEventType, Gesture } from 'vs/base/browser/touch'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IContentActionHandler, renderFormattedText } from 'vs/base/browser/formattedTextRenderer'; +import { SelectSnippetForEmptyFile } from 'vs/workbench/contrib/snippets/browser/commands/emptyFileSnippets'; const $ = dom.$; @@ -70,7 +71,7 @@ class UntitledTextEditorHintContentWidget implements IContentWidget { private static readonly ID = 'editor.widget.untitledHint'; private domNode: HTMLElement | undefined; - private toDispose: IDisposable[]; + private toDispose: DisposableStore; constructor( private readonly editor: ICodeEditor, @@ -79,9 +80,9 @@ class UntitledTextEditorHintContentWidget implements IContentWidget { private readonly configurationService: IConfigurationService, private readonly keybindingService: IKeybindingService, ) { - this.toDispose = []; - this.toDispose.push(editor.onDidChangeModelContent(() => this.onDidChangeModelContent())); - this.toDispose.push(this.editor.onDidChangeConfiguration((e: ConfigurationChangedEvent) => { + this.toDispose = new DisposableStore(); + this.toDispose.add(editor.onDidChangeModelContent(() => this.onDidChangeModelContent())); + this.toDispose.add(this.editor.onDidChangeConfiguration((e: ConfigurationChangedEvent) => { if (this.domNode && e.hasChanged(EditorOption.fontInfo)) { this.editor.applyFontInfo(this.domNode); } @@ -107,49 +108,43 @@ class UntitledTextEditorHintContentWidget implements IContentWidget { this.domNode = $('.untitled-hint'); this.domNode.style.width = 'max-content'; - const language = $('a.language-mode'); - language.style.cursor = 'pointer'; - language.innerText = localize('selectAlanguage2', "Select a language"); - const languageKeyBinding = this.keybindingService.lookupKeybinding(ChangeLanguageAction.ID); - const languageKeybindingLabel = languageKeyBinding?.getLabel(); - if (languageKeybindingLabel) { - language.title = localize('keyboardBindingTooltip', "{0}", languageKeybindingLabel); - } - this.domNode.appendChild(language); - - const or = $('span'); - or.innerText = localize('or', " or ",); - this.domNode.appendChild(or); - - const editorType = $('a.editor-type'); - editorType.style.cursor = 'pointer'; - editorType.innerText = localize('openADifferentEditor', "open a different editor"); - const selectEditorTypeKeyBinding = this.keybindingService.lookupKeybinding('welcome.showNewFileEntries'); - const selectEditorTypeKeybindingLabel = selectEditorTypeKeyBinding?.getLabel(); - if (selectEditorTypeKeybindingLabel) { - editorType.title = localize('keyboardBindingTooltip', "{0}", selectEditorTypeKeybindingLabel); - } - this.domNode.appendChild(editorType); - - const toGetStarted = $('span'); - toGetStarted.innerText = localize('toGetStarted', " to get started."); - this.domNode.appendChild(toGetStarted); - - this.domNode.appendChild($('br')); - - const startTyping = $('span'); - startTyping.innerText = localize('startTyping', "Start typing to dismiss or "); - this.domNode.appendChild(startTyping); + const hintMsg = localize({ key: 'message', comment: ['Presereve double-square brackets and their order'] }, '[[Select a language]], [[start with a snippet]], or [[open a different editor]] to get started.\nStart typing to dismiss or [[don\'t show]] this again.'); + const hintHandler: IContentActionHandler = { + disposables: this.toDispose, + callback: (index, event) => { + switch (index) { + case '0': + languageOnClickOrTap(event.browserEvent); + break; + case '1': + snippetOnClickOrTab(event.browserEvent); + break; + case '2': + chooseEditorOnClickOrTap(event.browserEvent); + break; + case '3': + dontShowOnClickOrTap(); + break; + } + } + }; - const dontShow = $('a'); - dontShow.style.cursor = 'pointer'; - dontShow.innerText = localize('dontshow', "don't show"); - this.domNode.appendChild(dontShow); + const hintElement = renderFormattedText(hintMsg, { + actionHandler: hintHandler, + renderCodeSegments: false, + }); + this.domNode.append(hintElement); + + // ugly way to associate keybindings... + const keybindingsLookup = [ChangeLanguageAction.ID, SelectSnippetForEmptyFile.Id, 'welcome.showNewFileEntries']; + for (const anchor of hintElement.querySelectorAll('A')) { + (anchor).style.cursor = 'pointer'; + const id = keybindingsLookup.shift(); + const title = id && this.keybindingService.lookupKeybinding(id)?.getLabel(); + (anchor).title = title ?? ''; + } - const thisAgain = $('span'); - thisAgain.innerText = localize('thisAgain', " this again."); - this.domNode.appendChild(thisAgain); - this.toDispose.push(Gesture.addTarget(this.domNode)); + // the actual command handlers... const languageOnClickOrTap = async (e: MouseEvent) => { e.stopPropagation(); // Need to focus editor before so current editor becomes active and the command is properly executed @@ -157,9 +152,12 @@ class UntitledTextEditorHintContentWidget implements IContentWidget { await this.commandService.executeCommand(ChangeLanguageAction.ID, { from: 'hint' }); this.editor.focus(); }; - this.toDispose.push(dom.addDisposableListener(language, 'click', languageOnClickOrTap)); - this.toDispose.push(dom.addDisposableListener(language, GestureEventType.Tap, languageOnClickOrTap)); - this.toDispose.push(Gesture.addTarget(language)); + + const snippetOnClickOrTab = async (e: MouseEvent) => { + e.stopPropagation(); + this.editor.focus(); + this.commandService.executeCommand(SelectSnippetForEmptyFile.Id, { from: 'hint' }); + }; const chooseEditorOnClickOrTap = async (e: MouseEvent) => { e.stopPropagation(); @@ -172,20 +170,14 @@ class UntitledTextEditorHintContentWidget implements IContentWidget { this.editorGroupsService.activeGroup.closeEditor(activeEditorInput, { preserveFocus: true }); } }; - this.toDispose.push(dom.addDisposableListener(editorType, 'click', chooseEditorOnClickOrTap)); - this.toDispose.push(dom.addDisposableListener(editorType, GestureEventType.Tap, chooseEditorOnClickOrTap)); - this.toDispose.push(Gesture.addTarget(editorType)); const dontShowOnClickOrTap = () => { this.configurationService.updateValue(untitledTextEditorHintSetting, 'hidden'); this.dispose(); this.editor.focus(); }; - this.toDispose.push(dom.addDisposableListener(dontShow, 'click', dontShowOnClickOrTap)); - this.toDispose.push(dom.addDisposableListener(dontShow, GestureEventType.Tap, dontShowOnClickOrTap)); - this.toDispose.push(Gesture.addTarget(dontShow)); - this.toDispose.push(dom.addDisposableListener(this.domNode, 'click', () => { + this.toDispose.add(dom.addDisposableListener(this.domNode, 'click', () => { this.editor.focus(); })); diff --git a/src/vs/workbench/contrib/snippets/browser/commands/emptyFileSnippets.ts b/src/vs/workbench/contrib/snippets/browser/commands/emptyFileSnippets.ts index 739cb837dfa..963c92e60cb 100644 --- a/src/vs/workbench/contrib/snippets/browser/commands/emptyFileSnippets.ts +++ b/src/vs/workbench/contrib/snippets/browser/commands/emptyFileSnippets.ts @@ -18,9 +18,11 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic export class SelectSnippetForEmptyFile extends SnippetsAction { + static readonly Id = 'workbench.action.populateFromSnippet'; + constructor() { super({ - id: 'workbench.action.populateFromSnippet', + id: SelectSnippetForEmptyFile.Id, title: { value: localize('label', 'Populate from Snippet'), original: 'Populate from Snippet' -- cgit v1.2.3 From 8630720a0be1b108de818e84576350d9a7e63784 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 15 Jul 2022 18:15:12 +0200 Subject: Fixes #155179 by implementing DeprecatedExtensionMigratorContribution (#155318) * Fixes #155179 by implementing DeprecatedExtensionMigratorContribution * Fixes CI. --- .../deprecatedExtensionMigrator.contribution.ts | 103 +++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 src/vs/workbench/contrib/deprecatedExtensionMigrator/browser/deprecatedExtensionMigrator.contribution.ts (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/deprecatedExtensionMigrator/browser/deprecatedExtensionMigrator.contribution.ts b/src/vs/workbench/contrib/deprecatedExtensionMigrator/browser/deprecatedExtensionMigrator.contribution.ts new file mode 100644 index 00000000000..3abb2f55315 --- /dev/null +++ b/src/vs/workbench/contrib/deprecatedExtensionMigrator/browser/deprecatedExtensionMigrator.contribution.ts @@ -0,0 +1,103 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Action } from 'vs/base/common/actions'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import { isDefined } from 'vs/base/common/types'; +import { localize } from 'vs/nls'; +import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; +import { EnablementState } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; + +class DeprecatedExtensionMigratorContribution { + constructor( + @IConfigurationService private readonly configurationService: IConfigurationService, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @IStorageService private readonly storageService: IStorageService, + @INotificationService private readonly notificationService: INotificationService, + @IOpenerService private readonly openerService: IOpenerService + ) { + this.init().catch(onUnexpectedError); + } + + private async init(): Promise { + const bracketPairColorizerId = 'coenraads.bracket-pair-colorizer'; + + await this.extensionsWorkbenchService.queryLocal(); + const extension = this.extensionsWorkbenchService.installed.find(e => e.identifier.id === bracketPairColorizerId); + if ( + !extension || + ((extension.enablementState !== EnablementState.EnabledGlobally) && + (extension.enablementState !== EnablementState.EnabledWorkspace)) + ) { + return; + } + + const state = await this.getState(); + const disablementLogEntry = state.disablementLog.some(d => d.extensionId === bracketPairColorizerId); + + if (disablementLogEntry) { + return; + } + + state.disablementLog.push({ extensionId: bracketPairColorizerId, disablementDateTime: new Date().getTime() }); + await this.setState(state); + + await this.extensionsWorkbenchService.setEnablement(extension, EnablementState.DisabledGlobally); + + const nativeBracketPairColorizationEnabledKey = 'editor.bracketPairColorization.enabled'; + const bracketPairColorizationEnabled = !!this.configurationService.inspect(nativeBracketPairColorizationEnabledKey).user; + + this.notificationService.notify({ + message: localize('bracketPairColorizer.notification', "The extension 'Bracket pair Colorizer' got disabled because it was deprecated."), + severity: Severity.Info, + actions: { + primary: [ + new Action('', localize('bracketPairColorizer.notification.action.uninstall', "Uninstall Extension"), undefined, undefined, () => { + this.extensionsWorkbenchService.uninstall(extension); + }), + ], + secondary: [ + !bracketPairColorizationEnabled ? new Action('', localize('bracketPairColorizer.notification.action.enableNative', "Enable Native Bracket Pair Colorization"), undefined, undefined, () => { + this.configurationService.updateValue(nativeBracketPairColorizationEnabledKey, true, ConfigurationTarget.USER); + }) : undefined, + new Action('', localize('bracketPairColorizer.notification.action.showMoreInfo', "More Info"), undefined, undefined, () => { + this.openerService.open('https://github.com/microsoft/vscode/issues/155179'); + }), + ].filter(isDefined), + } + }); + } + + private readonly storageKey = 'deprecatedExtensionMigrator.state'; + + private async getState(): Promise { + const jsonStr = await this.storageService.get(this.storageKey, StorageScope.APPLICATION, ''); + if (jsonStr === '') { + return { disablementLog: [] }; + } + return JSON.parse(jsonStr) as State; + } + + private async setState(state: State): Promise { + const json = JSON.stringify(state); + await this.storageService.store(this.storageKey, json, StorageScope.APPLICATION, StorageTarget.USER); + } +} + +interface State { + disablementLog: { + extensionId: string; + disablementDateTime: number; + }[]; +} + +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DeprecatedExtensionMigratorContribution, LifecyclePhase.Restored); -- cgit v1.2.3 From 6779fd96bfbfa297e643bf0c76329a4a63bf341d Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Fri, 15 Jul 2022 09:52:17 -0700 Subject: Add "Go to Last Failed Cell" Button (#154443) * Add go to last failed cell function --- .../notebook/browser/controller/executeActions.ts | 52 +++++++++++++++++++++- .../notebook/browser/media/notebookToolbar.css | 4 ++ .../browser/notebookExecutionStateServiceImpl.ts | 36 ++++++++++++--- .../viewParts/notebookEditorWidgetContextKeys.ts | 13 +++++- .../contrib/notebook/common/notebookContextKeys.ts | 1 + .../common/notebookExecutionStateService.ts | 6 +++ .../notebook/test/browser/testNotebookEditor.ts | 8 +++- 7 files changed, 111 insertions(+), 9 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts index 3e54598b92b..7b190d89c14 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts @@ -15,7 +15,7 @@ import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { EditorsOrder } from 'vs/workbench/common/editor'; import { insertCell } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; import { cellExecutionArgs, CellToolbarOrder, CELL_TITLE_CELL_GROUP_ID, executeNotebookCondition, getContextFromActiveEditor, getContextFromUri, INotebookActionContext, INotebookCellActionContext, INotebookCellToolbarActionContext, INotebookCommandContext, NotebookAction, NotebookCellAction, NotebookMultiCellAction, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT, parseMultiCellExecutionArgs } from 'vs/workbench/contrib/notebook/browser/controller/coreActions'; -import { NOTEBOOK_CELL_EXECUTING, NOTEBOOK_CELL_EXECUTION_STATE, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_TYPE, NOTEBOOK_HAS_RUNNING_CELL, NOTEBOOK_INTERRUPTIBLE_KERNEL, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_KERNEL_SOURCE_COUNT, NOTEBOOK_MISSING_KERNEL_EXTENSION } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; +import { NOTEBOOK_CELL_EXECUTING, NOTEBOOK_CELL_EXECUTION_STATE, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_TYPE, NOTEBOOK_HAS_RUNNING_CELL, NOTEBOOK_INTERRUPTIBLE_KERNEL, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_KERNEL_SOURCE_COUNT, NOTEBOOK_LAST_CELL_FAILED, NOTEBOOK_MISSING_KERNEL_EXTENSION } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { CellEditState, CellFocusMode, EXECUTE_CELL_COMMAND_ID } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons'; import { CellKind, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -35,6 +35,7 @@ const EXECUTE_CELL_AND_BELOW = 'notebook.cell.executeCellAndBelow'; const EXECUTE_CELLS_ABOVE = 'notebook.cell.executeCellsAbove'; const RENDER_ALL_MARKDOWN_CELLS = 'notebook.renderAllMarkdownCells'; const REVEAL_RUNNING_CELL = 'notebook.revealRunningCell'; +const REVEAL_LAST_FAILED_CELL = 'notebook.revealLastFailedCell'; // If this changes, update getCodeCellExecutionContextKeyService to match export const executeCondition = ContextKeyExpr.and( @@ -594,3 +595,52 @@ registerAction2(class RevealRunningCellAction extends NotebookAction { } } }); + +registerAction2(class RevealLastFailedCellAction extends NotebookAction { + constructor() { + super({ + id: REVEAL_LAST_FAILED_CELL, + title: localize('revealLastFailedCell', "Go to Most Recently Failed Cell"), + tooltip: localize('revealLastFailedCell', "Go to Most Recently Failed Cell"), + shortTitle: localize('revealLastFailedCellShort', "Go To"), + precondition: NOTEBOOK_LAST_CELL_FAILED, + menu: [ + { + id: MenuId.EditorTitle, + when: ContextKeyExpr.and( + NOTEBOOK_IS_ACTIVE_EDITOR, + NOTEBOOK_LAST_CELL_FAILED, + NOTEBOOK_HAS_RUNNING_CELL.toNegated(), + ContextKeyExpr.notEquals('config.notebook.globalToolbar', true) + ), + group: 'navigation', + order: 0 + }, + { + id: MenuId.NotebookToolbar, + when: ContextKeyExpr.and( + NOTEBOOK_IS_ACTIVE_EDITOR, + NOTEBOOK_LAST_CELL_FAILED, + NOTEBOOK_HAS_RUNNING_CELL.toNegated(), + ContextKeyExpr.equals('config.notebook.globalToolbar', true) + ), + group: 'navigation/execute', + order: 0 + }, + ], + icon: icons.errorStateIcon, + }); + } + + async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise { + const notebookExecutionStateService = accessor.get(INotebookExecutionStateService); + const notebook = context.notebookEditor.textModel.uri; + const lastFailedCellHandle = notebookExecutionStateService.getLastFailedCellForNotebook(notebook); + if (lastFailedCellHandle !== undefined) { + const lastFailedCell = context.notebookEditor.getCellByHandle(lastFailedCellHandle); + if (lastFailedCell) { + context.notebookEditor.focusNotebookCell(lastFailedCell, 'container'); + } + } + } +}); diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebookToolbar.css b/src/vs/workbench/contrib/notebook/browser/media/notebookToolbar.css index 4b61bafdfd8..f7ddb6665a7 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebookToolbar.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebookToolbar.css @@ -80,3 +80,7 @@ .monaco-workbench .notebookOverlay .notebook-toolbar-container .monaco-action-bar:not(.vertical) .action-item.active { background-color: unset; } + +.monaco-workbench .notebookOverlay .notebook-toolbar-container .monaco-action-bar .action-item .codicon-notebook-state-error { + color: var(--notebook-cell-status-icon-error); +} diff --git a/src/vs/workbench/contrib/notebook/browser/notebookExecutionStateServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookExecutionStateServiceImpl.ts index e32dbce7ba8..0cf3e56c598 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookExecutionStateServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookExecutionStateServiceImpl.ts @@ -13,7 +13,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { CellEditType, CellUri, ICellEditOperation, NotebookCellExecutionState, NotebookCellInternalMetadata, NotebookTextModelWillAddRemoveEvent } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { CellExecutionUpdateType, INotebookExecutionService } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; -import { ICellExecuteUpdate, ICellExecutionComplete, ICellExecutionStateChangedEvent, ICellExecutionStateUpdate, INotebookCellExecution, INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; +import { ICellExecuteUpdate, ICellExecutionComplete, ICellExecutionStateChangedEvent, ICellExecutionStateUpdate, INotebookCellExecution, INotebookExecutionStateService, INotebookFailStateChangedEvent } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; export class NotebookExecutionStateService extends Disposable implements INotebookExecutionStateService { @@ -22,10 +22,14 @@ export class NotebookExecutionStateService extends Disposable implements INotebo private readonly _executions = new ResourceMap>(); private readonly _notebookListeners = new ResourceMap(); private readonly _cellListeners = new ResourceMap(); + private readonly _lastFailedCells = new ResourceMap(); private readonly _onDidChangeCellExecution = this._register(new Emitter()); onDidChangeCellExecution = this._onDidChangeCellExecution.event; + private readonly _onDidChangeLastRunFailState = this._register(new Emitter()); + onDidChangeLastRunFailState = this._onDidChangeLastRunFailState.event; + constructor( @IInstantiationService private readonly _instantiationService: IInstantiationService, @ILogService private readonly _logService: ILogService, @@ -34,6 +38,10 @@ export class NotebookExecutionStateService extends Disposable implements INotebo super(); } + getLastFailedCellForNotebook(notebook: URI): number | undefined { + return this._lastFailedCells.get(notebook); + } + forceCancelNotebookExecutions(notebookUri: URI): void { const notebookExecutions = this._executions.get(notebookUri); if (!notebookExecutions) { @@ -68,7 +76,7 @@ export class NotebookExecutionStateService extends Disposable implements INotebo this._onDidChangeCellExecution.fire(new NotebookExecutionEvent(notebookUri, cellHandle, exe)); } - private _onCellExecutionDidComplete(notebookUri: URI, cellHandle: number, exe: CellExecution): void { + private _onCellExecutionDidComplete(notebookUri: URI, cellHandle: number, exe: CellExecution, lastRunSuccess?: boolean): void { const notebookExecutions = this._executions.get(notebookUri); if (!notebookExecutions) { this._logService.debug(`NotebookExecutionStateService#_onCellExecutionDidComplete - unknown notebook ${notebookUri.toString()}`); @@ -86,6 +94,14 @@ export class NotebookExecutionStateService extends Disposable implements INotebo this._notebookListeners.delete(notebookUri); } + if (lastRunSuccess !== undefined) { + if (lastRunSuccess) { + this._clearLastFailedCell(notebookUri); + } else { + this._setLastFailedCell(notebookUri, cellHandle); + } + } + this._onDidChangeCellExecution.fire(new NotebookExecutionEvent(notebookUri, cellHandle)); } @@ -119,12 +135,22 @@ export class NotebookExecutionStateService extends Disposable implements INotebo const exe: CellExecution = this._instantiationService.createInstance(CellExecution, cellHandle, notebook); const disposable = combinedDisposable( exe.onDidUpdate(() => this._onCellExecutionDidChange(notebookUri, cellHandle, exe)), - exe.onDidComplete(() => this._onCellExecutionDidComplete(notebookUri, cellHandle, exe))); + exe.onDidComplete(lastRunSuccess => this._onCellExecutionDidComplete(notebookUri, cellHandle, exe, lastRunSuccess))); this._cellListeners.set(CellUri.generate(notebookUri, cellHandle), disposable); return exe; } + private _setLastFailedCell(notebook: URI, cellHandle: number) { + this._lastFailedCells.set(notebook, cellHandle); + this._onDidChangeLastRunFailState.fire({ failed: true, notebook }); + } + + private _clearLastFailedCell(notebook: URI) { + this._lastFailedCells.delete(notebook); + this._onDidChangeLastRunFailState.fire({ failed: false, notebook: notebook }); + } + override dispose(): void { super.dispose(); this._executions.forEach(executionMap => { @@ -250,7 +276,7 @@ class CellExecution extends Disposable implements INotebookCellExecution { private readonly _onDidUpdate = this._register(new Emitter()); readonly onDidUpdate = this._onDidUpdate.event; - private readonly _onDidComplete = this._register(new Emitter()); + private readonly _onDidComplete = this._register(new Emitter()); readonly onDidComplete = this._onDidComplete.event; private _state: NotebookCellExecutionState = NotebookCellExecutionState.Unconfirmed; @@ -350,7 +376,7 @@ class CellExecution extends Disposable implements INotebookCellExecution { this._applyExecutionEdits([edit]); } - this._onDidComplete.fire(); + this._onDidComplete.fire(completionData.lastRunSuccess); } private _applyExecutionEdits(edits: ICellEditOperation[]): void { diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts index 3702ac26261..27384f830d4 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts @@ -6,8 +6,8 @@ import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ICellViewModel, INotebookEditorDelegate, KERNEL_EXTENSIONS } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { NOTEBOOK_CELL_TOOLBAR_LOCATION, NOTEBOOK_HAS_OUTPUTS, NOTEBOOK_HAS_RUNNING_CELL, NOTEBOOK_INTERRUPTIBLE_KERNEL, NOTEBOOK_KERNEL, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_KERNEL_SELECTED, NOTEBOOK_KERNEL_SOURCE_COUNT, NOTEBOOK_MISSING_KERNEL_EXTENSION, NOTEBOOK_USE_CONSOLIDATED_OUTPUT_BUTTON, NOTEBOOK_VIEW_TYPE } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; -import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; +import { NOTEBOOK_CELL_TOOLBAR_LOCATION, NOTEBOOK_HAS_OUTPUTS, NOTEBOOK_HAS_RUNNING_CELL, NOTEBOOK_INTERRUPTIBLE_KERNEL, NOTEBOOK_KERNEL, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_KERNEL_SELECTED, NOTEBOOK_KERNEL_SOURCE_COUNT, NOTEBOOK_LAST_CELL_FAILED, NOTEBOOK_MISSING_KERNEL_EXTENSION, NOTEBOOK_USE_CONSOLIDATED_OUTPUT_BUTTON, NOTEBOOK_VIEW_TYPE } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; +import { INotebookExecutionStateService, INotebookFailStateChangedEvent } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -24,6 +24,7 @@ export class NotebookEditorContextKeys { private readonly _viewType!: IContextKey; private readonly _missingKernelExtension: IContextKey; private readonly _cellToolbarLocation: IContextKey<'left' | 'right' | 'hidden'>; + private readonly _lastCellFailed: IContextKey; private readonly _disposables = new DisposableStore(); private readonly _viewModelDisposables = new DisposableStore(); @@ -47,6 +48,7 @@ export class NotebookEditorContextKeys { this._missingKernelExtension = NOTEBOOK_MISSING_KERNEL_EXTENSION.bindTo(contextKeyService); this._notebookKernelSourceCount = NOTEBOOK_KERNEL_SOURCE_COUNT.bindTo(contextKeyService); this._cellToolbarLocation = NOTEBOOK_CELL_TOOLBAR_LOCATION.bindTo(contextKeyService); + this._lastCellFailed = NOTEBOOK_LAST_CELL_FAILED.bindTo(contextKeyService); this._handleDidChangeModel(); this._updateForNotebookOptions(); @@ -58,6 +60,7 @@ export class NotebookEditorContextKeys { this._disposables.add(_editor.notebookOptions.onDidChangeOptions(this._updateForNotebookOptions, this)); this._disposables.add(_extensionService.onDidChangeExtensions(this._updateForInstalledExtension, this)); this._disposables.add(_notebookExecutionStateService.onDidChangeCellExecution(this._updateForCellExecution, this)); + this._disposables.add(_notebookExecutionStateService.onDidChangeLastRunFailState(this._updateForLastRunFailState, this)); } dispose(): void { @@ -132,6 +135,12 @@ export class NotebookEditorContextKeys { } } + private _updateForLastRunFailState(e: INotebookFailStateChangedEvent): void { + if (e.notebook === this._editor.textModel?.uri) { + this._lastCellFailed.set(e.failed); + } + } + private async _updateForInstalledExtension(): Promise { if (!this._editor.hasModel()) { return; diff --git a/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts b/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts index e629ed034d2..fd9692eba0f 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts @@ -24,6 +24,7 @@ export const NOTEBOOK_USE_CONSOLIDATED_OUTPUT_BUTTON = new RawContextKey('notebookBreakpointMargin', false); export const NOTEBOOK_CELL_TOOLBAR_LOCATION = new RawContextKey<'left' | 'right' | 'hidden'>('notebookCellToolbarLocation', 'left'); export const NOTEBOOK_CURSOR_NAVIGATION_MODE = new RawContextKey('notebookCursorNavigationMode', false); +export const NOTEBOOK_LAST_CELL_FAILED = new RawContextKey('notebookLastCellFailed', false); // Cell keys export const NOTEBOOK_VIEW_TYPE = new RawContextKey('notebookType', undefined); diff --git a/src/vs/workbench/contrib/notebook/common/notebookExecutionStateService.ts b/src/vs/workbench/contrib/notebook/common/notebookExecutionStateService.ts index f6317245bb1..d1fbe75907f 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookExecutionStateService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookExecutionStateService.ts @@ -39,6 +39,10 @@ export interface ICellExecutionStateChangedEvent { affectsCell(cell: URI): boolean; affectsNotebook(notebook: URI): boolean; } +export interface INotebookFailStateChangedEvent { + failed: boolean; + notebook: URI; +} export const INotebookExecutionStateService = createDecorator('INotebookExecutionStateService'); @@ -46,11 +50,13 @@ export interface INotebookExecutionStateService { _serviceBrand: undefined; onDidChangeCellExecution: Event; + onDidChangeLastRunFailState: Event; forceCancelNotebookExecutions(notebookUri: URI): void; getCellExecutionStatesForNotebook(notebook: URI): INotebookCellExecution[]; getCellExecution(cellUri: URI): INotebookCellExecution | undefined; createCellExecution(notebook: URI, cellHandle: number): INotebookCellExecution; + getLastFailedCellForNotebook(notebook: URI): number | undefined; } export interface INotebookCellExecution { diff --git a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts index 8570bf9146a..6b16b20461f 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts @@ -44,7 +44,7 @@ import { ViewContext } from 'vs/workbench/contrib/notebook/browser/viewModel/vie import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { CellKind, CellUri, INotebookDiffEditorModel, INotebookEditorModel, INotebookSearchOptions, IOutputDto, IResolvedNotebookEditorModel, NotebookCellExecutionState, NotebookCellMetadata, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { ICellExecuteUpdate, ICellExecutionComplete, ICellExecutionStateChangedEvent, INotebookCellExecution, INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; +import { ICellExecuteUpdate, ICellExecutionComplete, ICellExecutionStateChangedEvent, INotebookCellExecution, INotebookExecutionStateService, INotebookFailStateChangedEvent } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { NotebookOptions } from 'vs/workbench/contrib/notebook/common/notebookOptions'; import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; @@ -405,11 +405,17 @@ class TestCellExecution implements INotebookCellExecution { } class TestNotebookExecutionStateService implements INotebookExecutionStateService { + + getLastFailedCellForNotebook(notebook: URI): number | undefined { + return; + } + _serviceBrand: undefined; private _executions = new ResourceMap(); onDidChangeCellExecution = new Emitter().event; + onDidChangeLastRunFailState = new Emitter().event; forceCancelNotebookExecutions(notebookUri: URI): void { } -- cgit v1.2.3 From 425aaebb45c75568a5f3f483e1bc2610b8411215 Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Fri, 15 Jul 2022 10:25:30 -0700 Subject: Flexible-width Find Menu in Notebook (#154550) Fixes #141516 --- .../contrib/find/notebookFindReplaceWidget.css | 7 +- .../contrib/find/notebookFindReplaceWidget.ts | 78 +++++++++++++++++++++- .../browser/contrib/find/notebookFindWidget.ts | 4 +- 3 files changed, 82 insertions(+), 7 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.css b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.css index d9701c95197..52cfc8adcac 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.css +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.css @@ -9,9 +9,9 @@ position: absolute; top: -45px; right: 18px; - width: 318px; + width: var(--notebook-find-width); max-width: calc(100% - 28px - 28px - 8px); - pointer-events: none; + padding:0 var(--notebook-find-horizontal-padding); transition: top 200ms linear; visibility: hidden; } @@ -158,3 +158,6 @@ .monaco-workbench .simple-fr-replace-part .monaco-inputbox > .ibwrapper > .input { height: 24px; } +.monaco-workbench .simple-fr-find-part-wrapper .monaco-sash { + left: 0 !important; +} diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts index 59f060f6421..f2d0a44853c 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindReplaceWidget.ts @@ -18,7 +18,7 @@ import * as nls from 'vs/nls'; import { ContextScopedReplaceInput, registerAndCreateHistoryNavigationContext } from 'vs/platform/history/browser/contextScopedHistoryWidget'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { editorWidgetBackground, editorWidgetForeground, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; +import { editorWidgetBackground, editorWidgetBorder, editorWidgetForeground, editorWidgetResizeBorder, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { registerIcon, widgetClose } from 'vs/platform/theme/common/iconRegistry'; import { attachProgressBarStyler } from 'vs/platform/theme/common/styler'; import { IColorTheme, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; @@ -35,6 +35,8 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { filterIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; import { NotebookFindFilters } from 'vs/workbench/contrib/notebook/browser/contrib/find/findFilters'; import { isSafari } from 'vs/base/common/platform'; +import { ISashEvent, Orientation, Sash } from 'vs/base/browser/ui/sash/sash'; +import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; const NLS_FIND_INPUT_LABEL = nls.localize('label.find', "Find"); const NLS_FIND_INPUT_PLACEHOLDER = nls.localize('placeholder.find', "Find"); @@ -55,6 +57,8 @@ const NOTEBOOK_FIND_IN_MARKUP_PREVIEW = nls.localize('notebook.find.filter.findI const NOTEBOOK_FIND_IN_CODE_INPUT = nls.localize('notebook.find.filter.findInCodeInput', "Code Cell Source"); const NOTEBOOK_FIND_IN_CODE_OUTPUT = nls.localize('notebook.find.filter.findInCodeOutput', "Cell Output"); +const NOTEBOOK_FIND_WIDGET_INITIAL_WIDTH = 318; +const NOTEBOOK_FIND_WIDGET_INITIAL_HORIZONTAL_PADDING = 4; class NotebookFindFilterActionViewItem extends DropdownMenuActionViewItem { constructor(readonly filters: NotebookFindFilters, action: IAction, actionRunner: IActionRunner, @IContextMenuService contextMenuService: IContextMenuService) { super(action, @@ -256,6 +260,8 @@ export abstract class SimpleFindReplaceWidget extends Widget { protected _replaceBtn!: SimpleButton; protected _replaceAllBtn!: SimpleButton; + private readonly _resizeSash: Sash; + private _resizeOriginalWidth = NOTEBOOK_FIND_WIDGET_INITIAL_WIDTH; private _isVisible: boolean = false; private _isReplaceVisible: boolean = false; @@ -274,7 +280,8 @@ export abstract class SimpleFindReplaceWidget extends Widget { @IMenuService readonly menuService: IMenuService, @IContextMenuService readonly contextMenuService: IContextMenuService, @IInstantiationService readonly instantiationService: IInstantiationService, - protected readonly _state: FindReplaceState = new FindReplaceState() + protected readonly _state: FindReplaceState = new FindReplaceState(), + protected readonly _notebookEditor: INotebookEditor, ) { super(); @@ -339,7 +346,8 @@ export abstract class SimpleFindReplaceWidget extends Widget { this.updateButtons(this.foundMatch); return { content: e.message }; } - } + }, + flexibleWidth: true, } )); @@ -474,6 +482,58 @@ export abstract class SimpleFindReplaceWidget extends Widget { this._innerReplaceDomNode.appendChild(this._replaceBtn.domNode); this._innerReplaceDomNode.appendChild(this._replaceAllBtn.domNode); + + this._resizeSash = this._register(new Sash(this._domNode, { getVerticalSashLeft: () => 0 }, { orientation: Orientation.VERTICAL, size: 2 })); + + this._register(this._resizeSash.onDidStart(() => { + this._resizeOriginalWidth = this._getDomWidth(); + })); + + this._register(this._resizeSash.onDidChange((evt: ISashEvent) => { + let width = this._resizeOriginalWidth + evt.startX - evt.currentX; + if (width < NOTEBOOK_FIND_WIDGET_INITIAL_WIDTH) { + width = NOTEBOOK_FIND_WIDGET_INITIAL_WIDTH; + } + + const maxWidth = this._getMaxWidth(); + if (width > maxWidth) { + width = maxWidth; + } + + this._domNode.style.width = `${width}px`; + + if (this._isReplaceVisible) { + this._replaceInput.width = dom.getTotalWidth(this._findInput.domNode); + } + + this._findInput.inputBox.layout(); + })); + + this._register(this._resizeSash.onDidReset(() => { + // users double click on the sash + // try to emulate what happens with editor findWidget + const currentWidth = this._getDomWidth(); + let width = NOTEBOOK_FIND_WIDGET_INITIAL_WIDTH; + + if (currentWidth <= NOTEBOOK_FIND_WIDGET_INITIAL_WIDTH) { + width = this._getMaxWidth(); + } + + this._domNode.style.width = `${width}px`; + if (this._isReplaceVisible) { + this._replaceInput.width = dom.getTotalWidth(this._findInput.domNode); + } + + this._findInput.inputBox.layout(); + })); + } + + private _getMaxWidth() { + return this._notebookEditor.getLayoutInfo().width - 64; + } + + private _getDomWidth() { + return dom.getTotalWidth(this._domNode) - (NOTEBOOK_FIND_WIDGET_INITIAL_HORIZONTAL_PADDING * 2); } getCellToolbarActions(menu: IMenu): { primary: IAction[]; secondary: IAction[] } { @@ -727,4 +787,16 @@ registerThemingParticipant((theme, collector) => { if (inputActiveOptionBackgroundColor) { collector.addRule(`.simple-fr-find-part .find-filter-button > .monaco-action-bar .action-label.notebook-filters.checked { background-color: ${inputActiveOptionBackgroundColor}; }`); } + + const resizeBorderBackground = theme.getColor(editorWidgetResizeBorder) ?? theme.getColor(editorWidgetBorder); + if (resizeBorderBackground) { + collector.addRule(`.monaco-workbench .simple-fr-find-part-wrapper .monaco-sash { background-color: ${resizeBorderBackground}; }`); + } + + collector.addRule(` + :root { + --notebook-find-width: ${NOTEBOOK_FIND_WIDGET_INITIAL_WIDTH}px; + --notebook-find-horizontal-padding: ${NOTEBOOK_FIND_WIDGET_INITIAL_HORIZONTAL_PADDING}px; + } + `); }); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts index c027d8137d4..9c38190c342 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts @@ -48,7 +48,7 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote private _findModel: FindModel; constructor( - private readonly _notebookEditor: INotebookEditor, + _notebookEditor: INotebookEditor, @IContextViewService contextViewService: IContextViewService, @IContextKeyService contextKeyService: IContextKeyService, @IThemeService themeService: IThemeService, @@ -57,7 +57,7 @@ export class NotebookFindWidget extends SimpleFindReplaceWidget implements INote @IMenuService menuService: IMenuService, @IInstantiationService instantiationService: IInstantiationService, ) { - super(contextViewService, contextKeyService, themeService, configurationService, menuService, contextMenuService, instantiationService, new FindReplaceState()); + super(contextViewService, contextKeyService, themeService, configurationService, menuService, contextMenuService, instantiationService, new FindReplaceState(), _notebookEditor); this._findModel = new FindModel(this._notebookEditor, this._state, this._configurationService); DOM.append(this._notebookEditor.getDomNode(), this.getDomNode()); -- cgit v1.2.3 From b40bbdda5bab954499e4f109f929a731271bc91a Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Fri, 15 Jul 2022 14:49:16 -0400 Subject: Fix #155131 (#155334) * Cleanup expansion context key * Fix #155131 --- .../contrib/files/browser/views/explorerView.ts | 44 +++++++++++----------- 1 file changed, 21 insertions(+), 23 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/files/browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts index 6185a906efa..a9cd6b8a8c9 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -67,23 +67,28 @@ interface IExplorerViewStyles { listDropBackground?: Color; } -// Accepts a single or multiple workspace folders -function hasExpandedRootChild(tree: WorkbenchCompressibleAsyncDataTree, treeInput: ExplorerItem | ExplorerItem[]): boolean { - const inputsToCheck = []; - if (Array.isArray(treeInput)) { - inputsToCheck.push(...treeInput.filter(folder => tree.hasNode(folder) && !tree.isCollapsed(folder))); - } else { - inputsToCheck.push(treeInput); - } - - for (const folder of inputsToCheck) { - for (const [, child] of folder.children.entries()) { - if (tree.hasNode(child) && tree.isCollapsible(child) && !tree.isCollapsed(child)) { - return true; +function hasExpandedRootChild(tree: WorkbenchCompressibleAsyncDataTree, treeInput: ExplorerItem[]): boolean { + for (const folder of treeInput) { + if (tree.hasNode(folder) && !tree.isCollapsed(folder)) { + for (const [, child] of folder.children.entries()) { + if (tree.hasNode(child) && tree.isCollapsible(child) && !tree.isCollapsed(child)) { + return true; + } } } } + return false; +} +/** + * Whether or not any of the nodes in the tree are expanded + */ +function hasExpandedNode(tree: WorkbenchCompressibleAsyncDataTree, treeInput: ExplorerItem[]): boolean { + for (const folder of treeInput) { + if (tree.hasNode(folder) && !tree.isCollapsed(folder)) { + return true; + } + } return false; } @@ -786,15 +791,6 @@ export class ExplorerView extends ViewPane implements IExplorerView { this.tree.domFocus(); } - const treeInput = this.tree.getInput(); - if (Array.isArray(treeInput)) { - treeInput.forEach(folder => { - folder.children.forEach(child => this.tree.hasNode(child) && this.tree.expand(child, true)); - }); - - return; - } - this.tree.expandAll(); } @@ -871,7 +867,9 @@ export class ExplorerView extends ViewPane implements IExplorerView { if (treeInput === undefined) { return; } - this.viewHasSomeCollapsibleRootItem.set(hasExpandedRootChild(this.tree, treeInput)); + const treeInputArray = Array.isArray(treeInput) ? treeInput : Array.from(treeInput.children.values()); + // Has collapsible root when anything is expanded + this.viewHasSomeCollapsibleRootItem.set(hasExpandedNode(this.tree, treeInputArray)); } styleListDropBackground(styles: IExplorerViewStyles): void { -- cgit v1.2.3 From 425a6dec811366e465958b57d0121413a4b538dc Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Fri, 15 Jul 2022 12:17:24 -0700 Subject: Fix notebook perf markers (#155266) Fixes #135834 --- .../contrib/notebook/browser/notebookEditor.ts | 17 +++++----- .../notebook/browser/notebookEditorWidget.ts | 10 +++--- .../contrib/notebook/common/notebookEditorInput.ts | 6 ++-- .../contrib/notebook/common/notebookPerformance.ts | 36 ++++++---------------- 4 files changed, 26 insertions(+), 43 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index 99d1d8de1f0..06e09afc3a7 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -33,7 +33,7 @@ import { NotebooKernelActionViewItem } from 'vs/workbench/contrib/notebook/brows import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { NOTEBOOK_EDITOR_ID } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; -import { clearMarks, getAndClearMarks, mark } from 'vs/workbench/contrib/notebook/common/notebookPerformance'; +import { NotebookPerfMarks } from 'vs/workbench/contrib/notebook/common/notebookPerformance'; import { IEditorDropService } from 'vs/workbench/services/editor/browser/editorDropService'; import { GroupsOrder, IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -172,8 +172,8 @@ export class NotebookEditor extends EditorPane implements IEditorPaneWithSelecti override async setInput(input: NotebookEditorInput, options: INotebookEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken, noRetry?: boolean): Promise { try { - clearMarks(input.resource); - mark(input.resource, 'startTime'); + const perf = new NotebookPerfMarks(); + perf.mark('startTime'); const group = this.group!; this._inputListener.value = input.onDidChangeCapabilities(() => this._onDidChangeInputCapabilities(input)); @@ -203,8 +203,8 @@ export class NotebookEditor extends EditorPane implements IEditorPaneWithSelecti // only now `setInput` and yield/await. this is AFTER the actual widget is ready. This is very important // so that others synchronously receive a notebook editor with the correct widget being set await super.setInput(input, options, context, token); - const model = await input.resolve(); - mark(input.resource, 'inputLoaded'); + const model = await input.resolve(perf); + perf.mark('inputLoaded'); // Check for cancellation if (token.isCancellationRequested) { @@ -230,7 +230,7 @@ export class NotebookEditor extends EditorPane implements IEditorPaneWithSelecti const viewState = options?.viewState ?? this._loadNotebookEditorViewState(input); this._widget.value?.setParentContextKeyService(this._contextKeyService); - await this._widget.value!.setModel(model.notebook, viewState); + await this._widget.value!.setModel(model.notebook, viewState, perf); const isReadOnly = input.hasCapability(EditorInputCapabilities.Readonly); await this._widget.value!.setOptions({ ...options, isReadOnly }); this._widgetDisposableStore.add(this._widget.value!.onDidFocusWidget(() => this._onDidFocusWidget.fire())); @@ -240,7 +240,7 @@ export class NotebookEditor extends EditorPane implements IEditorPaneWithSelecti containsGroup: (group) => this.group?.id === group.id })); - mark(input.resource, 'editorLoaded'); + perf.mark('editorLoaded'); type WorkbenchNotebookOpenClassification = { owner: 'rebornix'; @@ -266,8 +266,7 @@ export class NotebookEditor extends EditorPane implements IEditorPaneWithSelecti editorLoaded: number; }; - const perfMarks = getAndClearMarks(input.resource); - + const perfMarks = perf.value; if (perfMarks) { const startTime = perfMarks['startTime']; const extensionActivated = perfMarks['extensionActivated']; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 6402451333d..b0dad86e9bb 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -77,7 +77,6 @@ import { INotebookExecutionService } from 'vs/workbench/contrib/notebook/common/ import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { NotebookOptions, OutputInnerContainerTopPadding } from 'vs/workbench/contrib/notebook/common/notebookOptions'; -import { mark } from 'vs/workbench/contrib/notebook/common/notebookPerformance'; import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { INotebookRendererMessagingService } from 'vs/workbench/contrib/notebook/common/notebookRendererMessagingService'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; @@ -85,6 +84,7 @@ import { editorGutterModifiedBackground } from 'vs/workbench/contrib/scm/browser import { IWebview } from 'vs/workbench/contrib/webview/browser/webview'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { NotebookPerfMarks } from 'vs/workbench/contrib/notebook/common/notebookPerformance'; const $ = DOM.$; @@ -1080,12 +1080,12 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this.scopedContextKeyService.updateParent(parentContextKeyService); } - async setModel(textModel: NotebookTextModel, viewState: INotebookEditorViewState | undefined): Promise { + async setModel(textModel: NotebookTextModel, viewState: INotebookEditorViewState | undefined, perf?: NotebookPerfMarks): Promise { if (this.viewModel === undefined || !this.viewModel.equal(textModel)) { const oldTopInsertToolbarHeight = this._notebookOptions.computeTopInsertToolbarHeight(this.viewModel?.viewType); const oldBottomToolbarDimensions = this._notebookOptions.computeBottomToolbarDimensions(this.viewModel?.viewType); this._detachModel(); - await this._attachModel(textModel, viewState); + await this._attachModel(textModel, viewState, perf); const newTopInsertToolbarHeight = this._notebookOptions.computeTopInsertToolbarHeight(this.viewModel?.viewType); const newBottomToolbarDimensions = this._notebookOptions.computeBottomToolbarDimensions(this.viewModel?.viewType); @@ -1389,7 +1389,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this._list.attachWebview(this._webview.element); } - private async _attachModel(textModel: NotebookTextModel, viewState: INotebookEditorViewState | undefined) { + private async _attachModel(textModel: NotebookTextModel, viewState: INotebookEditorViewState | undefined, perf?: NotebookPerfMarks) { await this._createWebview(this.getId(), textModel.uri); this.viewModel = this.instantiationService.createInstance(NotebookViewModel, textModel.viewType, textModel, this._viewContext, this.getLayoutInfo(), { isReadOnly: this._readOnly }); this._viewContext.eventDispatcher.emit([new NotebookLayoutChangedEvent({ width: true, fontInfo: true }, this.getLayoutInfo())]); @@ -1472,7 +1472,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD // init rendering await this._warmupWithMarkdownRenderer(this.viewModel, viewState); - mark(textModel.uri, 'customMarkdownLoaded'); + perf?.mark('customMarkdownLoaded'); // model attached this._localCellStateListeners = this.viewModel.viewCells.map(cell => this._bindCellListener(cell)); diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts index b8cbd4985d0..54e0ac40f5e 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorInput.ts @@ -16,7 +16,6 @@ import { IDisposable, IReference } from 'vs/base/common/lifecycle'; import { CellEditType, IResolvedNotebookEditorModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ILabelService } from 'vs/platform/label/common/label'; import { Schemas } from 'vs/base/common/network'; -import { mark } from 'vs/workbench/contrib/notebook/common/notebookPerformance'; import { FileSystemProviderCapabilities, IFileService } from 'vs/platform/files/common/files'; import { AbstractResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { IResourceEditorInput } from 'vs/platform/editor/common/editor'; @@ -24,6 +23,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { VSBuffer } from 'vs/base/common/buffer'; import { IWorkingCopyIdentifier } from 'vs/workbench/services/workingCopy/common/workingCopy'; import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; +import { NotebookPerfMarks } from 'vs/workbench/contrib/notebook/common/notebookPerformance'; export interface NotebookEditorInputOptions { startDirty?: boolean; @@ -231,12 +231,12 @@ export class NotebookEditorInput extends AbstractResourceEditorInput { } } - override async resolve(): Promise { + override async resolve(perf?: NotebookPerfMarks): Promise { if (!await this._notebookService.canResolve(this.viewType)) { return null; } - mark(this.resource, 'extensionActivated'); + perf?.mark('extensionActivated'); // we are now loading the notebook and don't need to listen to // "other" loading anymore diff --git a/src/vs/workbench/contrib/notebook/common/notebookPerformance.ts b/src/vs/workbench/contrib/notebook/common/notebookPerformance.ts index bd59c7bfbf1..c47b6eeeb2c 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookPerformance.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookPerformance.ts @@ -3,39 +3,23 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { URI } from 'vs/base/common/uri'; - export type PerfName = 'startTime' | 'extensionActivated' | 'inputLoaded' | 'webviewCommLoaded' | 'customMarkdownLoaded' | 'editorLoaded'; type PerformanceMark = { [key in PerfName]?: number }; -const perfMarks = new Map(); +export class NotebookPerfMarks { + private _marks: PerformanceMark = {}; + + get value(): PerformanceMark { + return { ...this._marks }; + } -export function mark(resource: URI, name: PerfName): void { - const key = resource.toString(); - if (!perfMarks.has(key)) { - const perfMark: PerformanceMark = {}; - perfMark[name] = Date.now(); - perfMarks.set(key, perfMark); - } else { - if (perfMarks.get(key)![name]) { + mark(name: PerfName): void { + if (this._marks[name]) { console.error(`Skipping overwrite of notebook perf value: ${name}`); return; } - perfMarks.get(key)![name] = Date.now(); - } -} -export function clearMarks(resource: URI): void { - const key = resource.toString(); - - perfMarks.delete(key); -} - -export function getAndClearMarks(resource: URI): PerformanceMark | null { - const key = resource.toString(); - - const perfMark = perfMarks.get(key) || null; - perfMarks.delete(key); - return perfMark; + this._marks[name] = Date.now(); + } } -- cgit v1.2.3 From 598e4befc04318ea63c7fbe75afb29db556ce869 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Fri, 15 Jul 2022 15:52:51 -0700 Subject: Do not require a `group` in contributed command --- .../workbench/contrib/editSessions/browser/editSessions.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts index 34d89d716a2..9be6a5124f7 100644 --- a/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts +++ b/src/vs/workbench/contrib/editSessions/browser/editSessions.contribution.ts @@ -113,7 +113,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo } const commands = new Map((extension.description.contributes?.commands ?? []).map(c => [c.command, c])); for (const contribution of extension.value) { - if (!contribution.command || !contribution.group || !contribution.when) { + if (!contribution.command || !contribution.when) { continue; } const fullCommand = commands.get(contribution.command); -- cgit v1.2.3 From 78397428676e15782e253261358b0398c2a1149e Mon Sep 17 00:00:00 2001 From: Megan Rogge Date: Fri, 15 Jul 2022 18:15:43 -0700 Subject: check if values are true not just undefined (#155348) --- src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts | 7 ++++--- src/vs/workbench/contrib/tasks/browser/task.contribution.ts | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 4bfc497f05a..d32b3e5602a 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -293,7 +293,9 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer })); this._taskRunningState = TASK_RUNNING_STATE.bindTo(_contextKeyService); this._onDidStateChange = this._register(new Emitter()); - this._registerCommands(); + this._registerCommands().then(() => { + TaskCommandsRegistered.bindTo(this._contextKeyService).set(true); + }); this._configurationResolverService.contributeVariable('defaultBuildTask', async (): Promise => { let tasks = await this._getTasksForGroup(TaskGroup.Build); if (tasks.length > 0) { @@ -491,7 +493,6 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this._openTaskFile(resource, TaskSourceKind.WorkspaceFile); } }); - TaskCommandsRegistered.bindTo(this._contextKeyService).set(true); } private get workspaceFolders(): IWorkspaceFolder[] { @@ -2173,7 +2174,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } private get _jsonTasksSupported(): boolean { - return !!ShellExecutionSupportedContext.getValue(this._contextKeyService) && !!ProcessExecutionSupportedContext.getValue(this._contextKeyService); + return ShellExecutionSupportedContext.getValue(this._contextKeyService) === true && ProcessExecutionSupportedContext.getValue(this._contextKeyService) === true && !Platform.isWeb; } private _computeWorkspaceFolderTasks(workspaceFolder: IWorkspaceFolder, runSource: TaskRunSource = TaskRunSource.User): Promise { diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index 3b7aa6162af..a2cc1a8efaf 100644 --- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -40,7 +40,7 @@ import { TaskDefinitionRegistry } from 'vs/workbench/contrib/tasks/common/taskDe import { TerminalMenuBarGroup } from 'vs/workbench/contrib/terminal/browser/terminalMenus'; import { isString } from 'vs/base/common/types'; -const SHOW_TASKS_COMMANDS_CONTEXT = ContextKeyExpr.or(ShellExecutionSupportedContext, ProcessExecutionSupportedContext); +const SHOW_TASKS_COMMANDS_CONTEXT = ContextKeyExpr.and(ShellExecutionSupportedContext, ProcessExecutionSupportedContext, TaskCommandsRegistered); const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchRegistry.registerWorkbenchContribution(RunAutomaticTasks, LifecyclePhase.Eventually); -- cgit v1.2.3 From 1ccfe2bbe804a20a7c657ca42368987fd1adac58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Mon, 18 Jul 2022 10:03:48 +0200 Subject: Revert "check if values are true not just undefined (#155348)" (#155468) This reverts commit 78397428676e15782e253261358b0398c2a1149e. --- src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts | 7 +++---- src/vs/workbench/contrib/tasks/browser/task.contribution.ts | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index d32b3e5602a..4bfc497f05a 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -293,9 +293,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer })); this._taskRunningState = TASK_RUNNING_STATE.bindTo(_contextKeyService); this._onDidStateChange = this._register(new Emitter()); - this._registerCommands().then(() => { - TaskCommandsRegistered.bindTo(this._contextKeyService).set(true); - }); + this._registerCommands(); this._configurationResolverService.contributeVariable('defaultBuildTask', async (): Promise => { let tasks = await this._getTasksForGroup(TaskGroup.Build); if (tasks.length > 0) { @@ -493,6 +491,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this._openTaskFile(resource, TaskSourceKind.WorkspaceFile); } }); + TaskCommandsRegistered.bindTo(this._contextKeyService).set(true); } private get workspaceFolders(): IWorkspaceFolder[] { @@ -2174,7 +2173,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } private get _jsonTasksSupported(): boolean { - return ShellExecutionSupportedContext.getValue(this._contextKeyService) === true && ProcessExecutionSupportedContext.getValue(this._contextKeyService) === true && !Platform.isWeb; + return !!ShellExecutionSupportedContext.getValue(this._contextKeyService) && !!ProcessExecutionSupportedContext.getValue(this._contextKeyService); } private _computeWorkspaceFolderTasks(workspaceFolder: IWorkspaceFolder, runSource: TaskRunSource = TaskRunSource.User): Promise { diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index a2cc1a8efaf..3b7aa6162af 100644 --- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -40,7 +40,7 @@ import { TaskDefinitionRegistry } from 'vs/workbench/contrib/tasks/common/taskDe import { TerminalMenuBarGroup } from 'vs/workbench/contrib/terminal/browser/terminalMenus'; import { isString } from 'vs/base/common/types'; -const SHOW_TASKS_COMMANDS_CONTEXT = ContextKeyExpr.and(ShellExecutionSupportedContext, ProcessExecutionSupportedContext, TaskCommandsRegistered); +const SHOW_TASKS_COMMANDS_CONTEXT = ContextKeyExpr.or(ShellExecutionSupportedContext, ProcessExecutionSupportedContext); const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchRegistry.registerWorkbenchContribution(RunAutomaticTasks, LifecyclePhase.Eventually); -- cgit v1.2.3 From 34507a8db3ab7a5e44fb4ca1ffd405733114c84a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Mon, 18 Jul 2022 16:07:29 +0200 Subject: h: type check attributes object (#155501) --- src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts index 7289cc61637..6b7f4670cb0 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editorGutter.ts @@ -31,7 +31,7 @@ export class EditorGutter extends D super(); this._domNode.className = 'gutter monaco-editor'; const scrollDecoration = this._domNode.appendChild( - h('div.scroll-decoration', { role: 'presentation', ariaHidden: true, style: { width: '100%' } }) + h('div.scroll-decoration', { role: 'presentation', ariaHidden: 'true', style: { width: '100%' } }) .root ); -- cgit v1.2.3 From 4b0d6f3bbcd9630d97a0622e2ed41af29054183b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Mon, 18 Jul 2022 16:25:05 +0200 Subject: Support find widget in lists/trees (#152481) * replace list type filter and tree type label controller with list type navigation and tree find. use proper FindInput widget * make sure vim doesn't break * polish outline use case * :lipstick: * remove unused import --- .../contrib/comments/browser/commentsTreeViewer.ts | 9 ++------- src/vs/workbench/contrib/debug/browser/debugHover.ts | 2 -- .../workbench/contrib/debug/browser/loadedScriptsView.ts | 5 +++-- .../contrib/extensions/browser/extensionsViewer.ts | 6 +----- .../workbench/contrib/list/browser/list.contribution.ts | 12 +++++------- src/vs/workbench/contrib/markers/browser/markersView.ts | 6 ++---- .../notebook/browser/diff/notebookTextDiffEditor.ts | 2 +- .../notebook/browser/diff/notebookTextDiffList.ts | 16 ---------------- .../contrib/notebook/browser/notebookEditorWidget.ts | 2 +- .../contrib/notebook/browser/view/notebookCellList.ts | 16 ---------------- .../contrib/notebook/test/browser/testNotebookEditor.ts | 1 - src/vs/workbench/contrib/outline/browser/outlinePane.ts | 13 +++++++------ .../contrib/preferences/browser/settingsTree.ts | 6 +----- src/vs/workbench/contrib/preferences/browser/tocTree.ts | 7 +------ .../contrib/testing/browser/testingExplorerView.ts | 1 - 15 files changed, 24 insertions(+), 80 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts index 020ef7eb4d1..42ebc26458d 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts @@ -13,8 +13,6 @@ import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels'; import { CommentNode, CommentsModel, ResourceWithCommentThreads } from 'vs/workbench/contrib/comments/common/commentModel'; import { IAsyncDataSource, ITreeNode } from 'vs/base/browser/ui/tree/tree'; import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { WorkbenchAsyncDataTree, IListService, IWorkbenchAsyncDataTreeOptions } from 'vs/platform/list/browser/listService'; @@ -266,8 +264,6 @@ export class CommentsList extends WorkbenchAsyncDataTree { @IThemeService themeService: IThemeService, @IInstantiationService instantiationService: IInstantiationService, @IConfigurationService configurationService: IConfigurationService, - @IKeybindingService keybindingService: IKeybindingService, - @IAccessibilityService accessibilityService: IAccessibilityService ) { const delegate = new CommentsModelVirualDelegate(); const dataSource = new CommentsAsyncDataSource(); @@ -311,12 +307,11 @@ export class CommentsList extends WorkbenchAsyncDataTree { }, overrideStyles: options.overrideStyles }, + instantiationService, contextKeyService, listService, themeService, - configurationService, - keybindingService, - accessibilityService + configurationService ); } } diff --git a/src/vs/workbench/contrib/debug/browser/debugHover.ts b/src/vs/workbench/contrib/debug/browser/debugHover.ts index 21d65a90cb3..6308c2b6c5b 100644 --- a/src/vs/workbench/contrib/debug/browser/debugHover.ts +++ b/src/vs/workbench/contrib/debug/browser/debugHover.ts @@ -116,8 +116,6 @@ export class DebugHoverWidget implements IContentWidget { horizontalScrolling: true, useShadows: false, keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (e: IExpression) => e.name }, - filterOnType: false, - simpleKeyboardNavigation: true, overrideStyles: { listBackground: editorHoverBackground } diff --git a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts index 1dc13b23fb7..5f9b1975001 100644 --- a/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts +++ b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts @@ -39,6 +39,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; +import { TreeFindMode } from 'vs/base/browser/ui/tree/abstractTree'; const NEW_STYLE_COMPRESS = true; @@ -585,8 +586,8 @@ export class LoadedScriptsView extends ViewPane { // feature: expand all nodes when filtering (not when finding) let viewState: IViewState | undefined; - this._register(this.tree.onDidChangeTypeFilterPattern(pattern => { - if (!this.tree.options.filterOnType) { + this._register(this.tree.onDidChangeFindPattern(pattern => { + if (this.tree.findMode === TreeFindMode.Highlight) { return; } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts index e390f694942..8ceb6a8e9ee 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts @@ -14,10 +14,8 @@ import { IListService, WorkbenchAsyncDataTree } from 'vs/platform/list/browser/l import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IAsyncDataSource, ITreeNode } from 'vs/base/browser/ui/tree/tree'; import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { CancellationToken } from 'vs/base/common/cancellation'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import { IColorMapping } from 'vs/platform/theme/common/styler'; @@ -244,8 +242,6 @@ export class ExtensionsTree extends WorkbenchAsyncDataTree('listSupportsKeyboardNavigation', true); -export const WorkbenchListAutomaticKeyboardNavigation = new RawContextKey(WorkbenchListAutomaticKeyboardNavigationKey, true); - export class ListContext implements IWorkbenchContribution { constructor( @IContextKeyService contextKeyService: IContextKeyService ) { - WorkbenchListSupportsKeyboardNavigation.bindTo(contextKeyService); - WorkbenchListAutomaticKeyboardNavigation.bindTo(contextKeyService); + contextKeyService.createKey('listSupportsTypeNavigation', true); + + // @deprecated in favor of listSupportsTypeNavigation + contextKeyService.createKey('listSupportsKeyboardNavigation', true); } } diff --git a/src/vs/workbench/contrib/markers/browser/markersView.ts b/src/vs/workbench/contrib/markers/browser/markersView.ts index bf75c6804ca..1b322657c38 100644 --- a/src/vs/workbench/contrib/markers/browser/markersView.ts +++ b/src/vs/workbench/contrib/markers/browser/markersView.ts @@ -40,7 +40,6 @@ import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/marke import { withUndefinedAsNull } from 'vs/base/common/types'; import { MementoObject, Memento } from 'vs/workbench/common/memento'; import { IIdentityProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { KeyCode } from 'vs/base/common/keyCodes'; import { editorLightBulbForeground, editorLightBulbAutoFixForeground } from 'vs/platform/theme/common/colorRegistry'; import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane'; @@ -922,14 +921,13 @@ class MarkersTree extends WorkbenchObjectTree impleme delegate: IListVirtualDelegate, renderers: ITreeRenderer[], options: IWorkbenchObjectTreeOptions, + @IInstantiationService instantiationService: IInstantiationService, @IContextKeyService contextKeyService: IContextKeyService, @IListService listService: IListService, @IThemeService themeService: IThemeService, @IConfigurationService configurationService: IConfigurationService, - @IKeybindingService keybindingService: IKeybindingService, - @IAccessibilityService accessibilityService: IAccessibilityService ) { - super(user, container, delegate, renderers, options, contextKeyService, listService, themeService, configurationService, keybindingService, accessibilityService); + super(user, container, delegate, renderers, options, instantiationService, contextKeyService, listService, themeService, configurationService); this.visibilityContextKey = MarkersContextKeys.MarkersTreeVisibilityContextKey.bindTo(contextKeyService); } diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts index c0951594b1c..bfaa6a81660 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts @@ -232,7 +232,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD keyboardSupport: false, mouseSupport: true, multipleSelectionSupport: false, - enableKeyboardNavigation: true, + typeNavigationEnabled: true, additionalScrollHeight: 0, // transformOptimization: (isMacintosh && isNative) || getTitleBarStyle(this.configurationService, this.environmentService) === 'native', styleController: (_suffix: string) => { return this._list!; }, diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts index 244f65f50ca..e98ae9b79f1 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffList.ts @@ -445,22 +445,6 @@ export class NotebookTextDiffList extends WorkbenchList { return this._list; }, diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index b62e89274d0..ace2ec61b1b 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -1393,22 +1393,6 @@ export class NotebookCellList extends WorkbenchList implements ID `); } - if (styles.listFilterWidgetBackground) { - content.push(`.monaco-list-type-filter { background-color: ${styles.listFilterWidgetBackground} }`); - } - - if (styles.listFilterWidgetOutline) { - content.push(`.monaco-list-type-filter { border: 1px solid ${styles.listFilterWidgetOutline}; }`); - } - - if (styles.listFilterWidgetNoMatchesOutline) { - content.push(`.monaco-list-type-filter.no-matches { border: 1px solid ${styles.listFilterWidgetNoMatchesOutline}; }`); - } - - if (styles.listMatchesShadow) { - content.push(`.monaco-list-type-filter { box-shadow: 1px 1px 1px ${styles.listMatchesShadow}; }`); - } - const newStyles = content.join('\n'); if (newStyles !== this.styleElement.textContent) { this.styleElement.textContent = newStyles; diff --git a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts index 6b16b20461f..1d667408085 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts @@ -366,7 +366,6 @@ export function createNotebookCellList(instantiationService: TestInstantiationSe { supportDynamicHeights: true, multipleSelectionSupport: true, - enableKeyboardNavigation: true, focusNextPreviousDelegate: { onFocusNext: (applyFocusNext: () => void) => { applyFocusNext(); }, onFocusPrevious: (applyFocusPrevious: () => void) => { applyFocusPrevious(); }, diff --git a/src/vs/workbench/contrib/outline/browser/outlinePane.ts b/src/vs/workbench/contrib/outline/browser/outlinePane.ts index 8b8a7ab8180..d41777b2c54 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePane.ts +++ b/src/vs/workbench/contrib/outline/browser/outlinePane.ts @@ -35,7 +35,7 @@ import { EditorResourceAccessor, IEditorPane } from 'vs/workbench/common/editor' import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; import { ITreeSorter } from 'vs/base/browser/ui/tree/tree'; -import { AbstractTreeViewState, IAbstractTreeViewState } from 'vs/base/browser/ui/tree/abstractTree'; +import { AbstractTreeViewState, IAbstractTreeViewState, TreeFindMode } from 'vs/base/browser/ui/tree/abstractTree'; const _ctxFollowsCursor = new RawContextKey('outlineFollowsCursor', false); const _ctxFilterOnType = new RawContextKey('outlineFiltersOnType', false); @@ -259,7 +259,7 @@ export class OutlinePane extends ViewPane { expandOnlyOnTwistieClick: true, multipleSelectionSupport: false, hideTwistiesOfChildlessElements: true, - filterOnType: this._outlineViewState.filterOnType, + defaultFindMode: this._outlineViewState.filterOnType ? TreeFindMode.Filter : TreeFindMode.Highlight, overrideStyles: { listBackground: this.getBackgroundColor() } } ); @@ -286,6 +286,7 @@ export class OutlinePane extends ViewPane { }; updateTree(); this._editorControlDisposables.add(newOutline.onDidChange(updateTree)); + tree.findMode = this._outlineViewState.filterOnType ? TreeFindMode.Filter : TreeFindMode.Highlight; // feature: apply panel background to tree this._editorControlDisposables.add(this.viewDescriptorService.onDidChangeLocation(({ views }) => { @@ -295,7 +296,7 @@ export class OutlinePane extends ViewPane { })); // feature: filter on type - keep tree and menu in sync - this._editorControlDisposables.add(tree.onDidUpdateOptions(e => this._outlineViewState.filterOnType = Boolean(e.filterOnType))); + this._editorControlDisposables.add(tree.onDidChangeFindMode(mode => this._outlineViewState.filterOnType = mode === TreeFindMode.Filter)); // feature: reveal outline selection in editor // on change -> reveal/select defining range @@ -328,7 +329,7 @@ export class OutlinePane extends ViewPane { this._editorControlDisposables.add(this._outlineViewState.onDidChange((e: { followCursor?: boolean; sortBy?: boolean; filterOnType?: boolean }) => { this._outlineViewState.persist(this._storageService); if (e.filterOnType) { - tree.updateOptions({ filterOnType: this._outlineViewState.filterOnType }); + tree.findMode = this._outlineViewState.filterOnType ? TreeFindMode.Filter : TreeFindMode.Highlight; } if (e.followCursor) { revealActiveElement(); @@ -341,8 +342,8 @@ export class OutlinePane extends ViewPane { // feature: expand all nodes when filtering (not when finding) let viewState: AbstractTreeViewState | undefined; - this._editorControlDisposables.add(tree.onDidChangeTypeFilterPattern(pattern => { - if (!tree.options.filterOnType) { + this._editorControlDisposables.add(tree.onDidChangeFindPattern(pattern => { + if (tree.findMode === TreeFindMode.Highlight) { return; } if (!viewState && pattern) { diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 16dd1bb7a51..39df9facda5 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -54,7 +54,6 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { IList } from 'vs/base/browser/ui/tree/indexTreeModel'; import { IListService, WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { ILogService } from 'vs/platform/log/common/log'; import { settingsMoreActionIcon } from 'vs/workbench/contrib/preferences/browser/preferencesIcons'; import { IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; @@ -2334,8 +2333,6 @@ export class SettingsTree extends WorkbenchObjectTree { @IListService listService: IListService, @IThemeService themeService: IThemeService, @IConfigurationService configurationService: IConfigurationService, - @IKeybindingService keybindingService: IKeybindingService, - @IAccessibilityService accessibilityService: IAccessibilityService, @IInstantiationService instantiationService: IInstantiationService, @ILanguageService languageService: ILanguageService ) { @@ -2356,12 +2353,11 @@ export class SettingsTree extends WorkbenchObjectTree { smoothScrolling: configurationService.getValue('workbench.list.smoothScrolling'), multipleSelectionSupport: false, }, + instantiationService, contextKeyService, listService, themeService, configurationService, - keybindingService, - accessibilityService, ); this.disposables.add(registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { diff --git a/src/vs/workbench/contrib/preferences/browser/tocTree.ts b/src/vs/workbench/contrib/preferences/browser/tocTree.ts index 1451c190687..522b5e13b80 100644 --- a/src/vs/workbench/contrib/preferences/browser/tocTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/tocTree.ts @@ -9,11 +9,9 @@ import { DefaultStyleController, IListAccessibilityProvider } from 'vs/base/brow import { ITreeElement, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; import { Iterable } from 'vs/base/common/iterator'; import { localize } from 'vs/nls'; -import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IListService, IWorkbenchObjectTreeOptions, WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; import { editorBackground, focusBorder, foreground, transparent } from 'vs/platform/theme/common/colorRegistry'; import { attachStyler } from 'vs/platform/theme/common/styler'; @@ -203,8 +201,6 @@ export class TOCTree extends WorkbenchObjectTree { @IListService listService: IListService, @IThemeService themeService: IThemeService, @IConfigurationService configurationService: IConfigurationService, - @IKeybindingService keybindingService: IKeybindingService, - @IAccessibilityService accessibilityService: IAccessibilityService, @IInstantiationService instantiationService: IInstantiationService, ) { // test open mode @@ -231,12 +227,11 @@ export class TOCTree extends WorkbenchObjectTree { new TOCTreeDelegate(), [new TOCRenderer()], options, + instantiationService, contextKeyService, listService, themeService, configurationService, - keybindingService, - accessibilityService, ); this.disposables.add(attachStyler(themeService, { diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts index 66ffbef2595..2281441286a 100644 --- a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts +++ b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts @@ -485,7 +485,6 @@ export class TestingExplorerViewModel extends Disposable { instantiationService.createInstance(ErrorRenderer), ], { - simpleKeyboardNavigation: true, identityProvider: instantiationService.createInstance(IdentityProvider), hideTwistiesOfChildlessElements: false, sorter: instantiationService.createInstance(TreeSorter, this), -- cgit v1.2.3 From a567b593d52685075199f266c91e9bddc17a8589 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 18 Jul 2022 18:44:07 +0200 Subject: introduce `--merge` to bring up merge editor (for #5770) (#155039) * introduce `--merge` to bring up merge editor (for #5770) * wait on proper editor when merging * sqlite slowness * disable flush on write in tests unless disk tests * more runWithFakedTimers * disable flush also in pfs * introduce `IResourceMergeEditorInput` * cleanup * align with merge editor names * stronger check * adopt `ResourceSet` * no need to coalesce * improve `matches` method --- .../browser/mergeEditor.contribution.ts | 6 +- .../mergeEditor/browser/mergeEditorInput.ts | 37 +++++++++--- .../mergeEditor/browser/view/mergeEditor.ts | 68 +++++++++++++++++++++- .../remote/electron-sandbox/remote.contribution.ts | 4 +- .../tags/electron-sandbox/workspaceTagsService.ts | 8 ++- .../telemetry/browser/telemetry.contribution.ts | 5 +- 6 files changed, 114 insertions(+), 14 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts index cba7f643cec..c06a6e0447c 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditor.contribution.ts @@ -13,7 +13,7 @@ import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/ed import { CompareInput1WithBaseCommand, CompareInput2WithBaseCommand, GoToNextConflict, GoToPreviousConflict, OpenBaseFile, OpenMergeEditor, OpenResultResource, SetColumnLayout, SetMixedLayout, ToggleActiveConflictInput1, ToggleActiveConflictInput2 } from 'vs/workbench/contrib/mergeEditor/browser/commands/commands'; import { MergeEditorCopyContentsToJSON, MergeEditorOpenContents } from 'vs/workbench/contrib/mergeEditor/browser/commands/devCommands'; import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; -import { MergeEditor, MergeEditorOpenHandlerContribution } from 'vs/workbench/contrib/mergeEditor/browser/view/mergeEditor'; +import { MergeEditor, MergeEditorResolverContribution, MergeEditorOpenHandlerContribution } from 'vs/workbench/contrib/mergeEditor/browser/view/mergeEditor'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { MergeEditorSerializer } from './mergeEditorSerializer'; @@ -55,3 +55,7 @@ registerAction2(CompareInput2WithBaseCommand); Registry .as(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(MergeEditorOpenHandlerContribution, LifecyclePhase.Restored); + +Registry + .as(WorkbenchExtensions.Workbench) + .registerWorkbenchContribution(MergeEditorResolverContribution, LifecyclePhase.Starting); diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts index 864214ae210..7f5a2febdb7 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts @@ -13,7 +13,7 @@ import { ConfirmResult, IDialogService } from 'vs/platform/dialogs/common/dialog import { IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; -import { IEditorIdentifier, IUntypedEditorInput } from 'vs/workbench/common/editor'; +import { IEditorIdentifier, IResourceMergeEditorInput, isResourceMergeEditorInput, IUntypedEditorInput } from 'vs/workbench/common/editor'; import { EditorInput, IEditorCloseHandler } from 'vs/workbench/common/editor/editorInput'; import { AbstractTextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput'; import { EditorWorkerServiceDiffComputer } from 'vs/workbench/contrib/mergeEditor/browser/model/diffComputer'; @@ -134,14 +134,37 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements return this._model; } + override toUntyped(): IResourceMergeEditorInput { + return { + input1: { resource: this.input1.uri, label: this.input1.title, description: this.input1.description }, + input2: { resource: this.input2.uri, label: this.input2.title, description: this.input2.description }, + base: { resource: this.base }, + result: { resource: this.result }, + options: { + override: this.typeId + } + }; + } + override matches(otherInput: EditorInput | IUntypedEditorInput): boolean { - if (!(otherInput instanceof MergeEditorInput)) { - return false; + if (this === otherInput) { + return true; } - return isEqual(this.base, otherInput.base) - && isEqual(this.input1.uri, otherInput.input1.uri) - && isEqual(this.input2.uri, otherInput.input2.uri) - && isEqual(this.result, otherInput.result); + if (otherInput instanceof MergeEditorInput) { + return isEqual(this.base, otherInput.base) + && isEqual(this.input1.uri, otherInput.input1.uri) + && isEqual(this.input2.uri, otherInput.input2.uri) + && isEqual(this.result, otherInput.result); + } + if (isResourceMergeEditorInput(otherInput)) { + return this.editorId === otherInput.options?.override + && isEqual(this.base, otherInput.base.resource) + && isEqual(this.input1.uri, otherInput.input1.resource) + && isEqual(this.input2.uri, otherInput.input2.resource) + && isEqual(this.result, otherInput.result.resource); + } + + return false; } // ---- FileEditorInput diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts index 74e3ee14e88..d98aa657980 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/mergeEditor.ts @@ -36,7 +36,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { FloatingClickWidget } from 'vs/workbench/browser/codeeditor'; import { AbstractTextEditor } from 'vs/workbench/browser/parts/editor/textEditor'; -import { IEditorOpenContext } from 'vs/workbench/common/editor'; +import { DEFAULT_EDITOR_ASSOCIATION, EditorInputWithOptions, IEditorOpenContext, IResourceMergeEditorInput } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { applyTextEditorOptions } from 'vs/workbench/common/editor/editorOptions'; import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; @@ -47,6 +47,7 @@ import { MergeEditorViewModel } from 'vs/workbench/contrib/mergeEditor/browser/v import { ctxBaseResourceScheme, ctxIsMergeEditor, ctxMergeEditorLayout, MergeEditorLayoutTypes } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor'; import { settingsSashBorder } from 'vs/workbench/contrib/preferences/common/settingsEditorColorRegistry'; import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import './colors'; import { InputCodeEditorView } from './editors/inputCodeEditorView'; @@ -501,6 +502,71 @@ export class MergeEditorOpenHandlerContribution extends Disposable { } } +export class MergeEditorResolverContribution extends Disposable { + + constructor( + @IEditorResolverService editorResolverService: IEditorResolverService, + @IInstantiationService instantiationService: IInstantiationService, + ) { + super(); + + this._register(editorResolverService.registerEditor( + `*`, + { + id: MergeEditorInput.ID, + label: localize('editor.mergeEditor.label', "Merge Editor"), + detail: DEFAULT_EDITOR_ASSOCIATION.providerDisplayName, + priority: RegisteredEditorPriority.option + }, + {}, + (editor) => { + return { + editor: instantiationService.createInstance( + MergeEditorInput, + editor.resource, + { + uri: editor.resource, + title: '', + description: '', + detail: '' + }, + { + uri: editor.resource, + title: '', + description: '', + detail: '' + }, + editor.resource + ) + }; + }, + undefined, + undefined, + (mergeEditor: IResourceMergeEditorInput): EditorInputWithOptions => { + return { + editor: instantiationService.createInstance( + MergeEditorInput, + mergeEditor.base.resource, + { + uri: mergeEditor.input1.resource, + title: localize('input1Title', "First Version"), + description: '', + detail: '' + }, + { + uri: mergeEditor.input2.resource, + title: localize('input2Title', "Second Version"), + description: '', + detail: '' + }, + mergeEditor.result.resource + ) + }; + } + )); + } +} + type IMergeEditorViewState = ICodeEditorViewState & { readonly input1State?: ICodeEditorViewState; readonly input2State?: ICodeEditorViewState; diff --git a/src/vs/workbench/contrib/remote/electron-sandbox/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-sandbox/remote.contribution.ts index 37c91b0b6b8..4e10ac0bea3 100644 --- a/src/vs/workbench/contrib/remote/electron-sandbox/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-sandbox/remote.contribution.ts @@ -117,8 +117,8 @@ class RemoteEmptyWorkbenchPresentation extends Disposable implements IWorkbenchC return shouldShowExplorer(); } - const { remoteAuthority, filesToDiff, filesToOpenOrCreate, filesToWait } = environmentService; - if (remoteAuthority && contextService.getWorkbenchState() === WorkbenchState.EMPTY && !filesToDiff?.length && !filesToOpenOrCreate?.length && !filesToWait) { + const { remoteAuthority, filesToDiff, filesToMerge, filesToOpenOrCreate, filesToWait } = environmentService; + if (remoteAuthority && contextService.getWorkbenchState() === WorkbenchState.EMPTY && !filesToDiff?.length && !filesToMerge?.length && !filesToOpenOrCreate?.length && !filesToWait) { remoteAuthorityResolverService.resolveAuthority(remoteAuthority).then(() => { if (shouldShowExplorer()) { commandService.executeCommand('workbench.view.explorer'); diff --git a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts b/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts index dd0ef223db9..d50317952c0 100644 --- a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts +++ b/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts @@ -305,6 +305,7 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { "WorkspaceTags" : { "workbench.filesToOpenOrCreate" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workbench.filesToDiff" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workbench.filesToMerge" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, "workspace.roots" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.empty" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, @@ -572,9 +573,10 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { tags['workspace.id'] = await this.getTelemetryWorkspaceId(workspace, state); - const { filesToOpenOrCreate, filesToDiff } = this.environmentService; + const { filesToOpenOrCreate, filesToDiff, filesToMerge } = this.environmentService; tags['workbench.filesToOpenOrCreate'] = filesToOpenOrCreate && filesToOpenOrCreate.length || 0; tags['workbench.filesToDiff'] = filesToDiff && filesToDiff.length || 0; + tags['workbench.filesToMerge'] = filesToMerge && filesToMerge.length || 0; const isEmpty = state === WorkbenchState.EMPTY; tags['workspace.roots'] = isEmpty ? 0 : workspace.folders.length; @@ -813,11 +815,13 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { } private findFolder(): URI | undefined { - const { filesToOpenOrCreate, filesToDiff } = this.environmentService; + const { filesToOpenOrCreate, filesToDiff, filesToMerge } = this.environmentService; if (filesToOpenOrCreate && filesToOpenOrCreate.length) { return this.parentURI(filesToOpenOrCreate[0].fileUri); } else if (filesToDiff && filesToDiff.length) { return this.parentURI(filesToDiff[0].fileUri); + } else if (filesToMerge && filesToMerge.length) { + return this.parentURI(filesToMerge[3].fileUri) /* [3] is the resulting merge file */; } return undefined; } diff --git a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts index dfcf34f405d..674741ed4d0 100644 --- a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts +++ b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts @@ -63,7 +63,7 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr ) { super(); - const { filesToOpenOrCreate, filesToDiff } = environmentService; + const { filesToOpenOrCreate, filesToDiff, filesToMerge } = environmentService; const activeViewlet = paneCompositeService.getActivePaneComposite(ViewContainerLocation.Sidebar); type WindowSizeFragment = { @@ -80,6 +80,7 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr windowSize: WindowSizeFragment; 'workbench.filesToOpenOrCreate': { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; 'workbench.filesToDiff': { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; + 'workbench.filesToMerge': { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; customKeybindingsCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; theme: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; language: { classification: 'SystemMetaData'; purpose: 'BusinessInsight' }; @@ -95,6 +96,7 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr emptyWorkbench: boolean; 'workbench.filesToOpenOrCreate': number; 'workbench.filesToDiff': number; + 'workbench.filesToMerge': number; customKeybindingsCount: number; theme: string; language: string; @@ -110,6 +112,7 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr emptyWorkbench: contextService.getWorkbenchState() === WorkbenchState.EMPTY, 'workbench.filesToOpenOrCreate': filesToOpenOrCreate && filesToOpenOrCreate.length || 0, 'workbench.filesToDiff': filesToDiff && filesToDiff.length || 0, + 'workbench.filesToMerge': filesToMerge && filesToMerge.length || 0, customKeybindingsCount: keybindingsService.customKeybindingsCount(), theme: themeService.getColorTheme().id, language, -- cgit v1.2.3 From 8846ac54887deb74afdb262cbdaecdd12b824f98 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 18 Jul 2022 10:37:48 -0700 Subject: Check if execution values are true, delay registered ctx key Another attempt at #155348 which got reverted Fixes #155336 Part of #155227 --- src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts | 7 ++++--- src/vs/workbench/contrib/tasks/browser/task.contribution.ts | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 4bfc497f05a..c123a941880 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -293,7 +293,9 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer })); this._taskRunningState = TASK_RUNNING_STATE.bindTo(_contextKeyService); this._onDidStateChange = this._register(new Emitter()); - this._registerCommands(); + this._registerCommands().then(() => { + TaskCommandsRegistered.bindTo(this._contextKeyService).set(true); + }); this._configurationResolverService.contributeVariable('defaultBuildTask', async (): Promise => { let tasks = await this._getTasksForGroup(TaskGroup.Build); if (tasks.length > 0) { @@ -491,7 +493,6 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this._openTaskFile(resource, TaskSourceKind.WorkspaceFile); } }); - TaskCommandsRegistered.bindTo(this._contextKeyService).set(true); } private get workspaceFolders(): IWorkspaceFolder[] { @@ -2173,7 +2174,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } private get _jsonTasksSupported(): boolean { - return !!ShellExecutionSupportedContext.getValue(this._contextKeyService) && !!ProcessExecutionSupportedContext.getValue(this._contextKeyService); + return ShellExecutionSupportedContext.getValue(this._contextKeyService) === true && ProcessExecutionSupportedContext.getValue(this._contextKeyService) === true; } private _computeWorkspaceFolderTasks(workspaceFolder: IWorkspaceFolder, runSource: TaskRunSource = TaskRunSource.User): Promise { diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index 3b7aa6162af..9b5df9014e5 100644 --- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -40,7 +40,7 @@ import { TaskDefinitionRegistry } from 'vs/workbench/contrib/tasks/common/taskDe import { TerminalMenuBarGroup } from 'vs/workbench/contrib/terminal/browser/terminalMenus'; import { isString } from 'vs/base/common/types'; -const SHOW_TASKS_COMMANDS_CONTEXT = ContextKeyExpr.or(ShellExecutionSupportedContext, ProcessExecutionSupportedContext); +const SHOW_TASKS_COMMANDS_CONTEXT = ContextKeyExpr.and(ShellExecutionSupportedContext, ProcessExecutionSupportedContext); const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchRegistry.registerWorkbenchContribution(RunAutomaticTasks, LifecyclePhase.Eventually); -- cgit v1.2.3 From 75f4d5200d69fa4488eb3dcd33032ea0c90ea617 Mon Sep 17 00:00:00 2001 From: SteVen Batten <6561887+sbatten@users.noreply.github.com> Date: Mon, 18 Jul 2022 11:14:31 -0700 Subject: update comments for telemetry events (#155525) --- src/vs/workbench/contrib/experiments/common/experimentService.ts | 3 ++- src/vs/workbench/contrib/tags/electron-sandbox/workspaceTags.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/experiments/common/experimentService.ts b/src/vs/workbench/contrib/experiments/common/experimentService.ts index 235217dbc64..297b08a29af 100644 --- a/src/vs/workbench/contrib/experiments/common/experimentService.ts +++ b/src/vs/workbench/contrib/experiments/common/experimentService.ts @@ -307,7 +307,8 @@ export class ExperimentService extends Disposable implements IExperimentService return Promise.all(promises).then(() => { type ExperimentsClassification = { owner: 'sbatten'; - experiments: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; + comment: 'Information about the experiments in this session'; + experiments: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The list of experiments in this session' }; }; this.telemetryService.publicLog2<{ experiments: string[] }, ExperimentsClassification>('experiments', { experiments: this._experiments.map(e => e.id) }); }); diff --git a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTags.ts b/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTags.ts index ab86a14c069..48bb97c05e7 100644 --- a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTags.ts +++ b/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTags.ts @@ -67,7 +67,7 @@ export class WorkspaceTags implements IWorkbenchContribution { value = 'Unknown'; } - this.telemetryService.publicLog2<{ edition: string }, { owner: 'sbatten'; edition: { classification: 'SystemMetaData'; purpose: 'BusinessInsight' } }>('windowsEdition', { edition: value }); + this.telemetryService.publicLog2<{ edition: string }, { owner: 'sbatten'; comment: 'Information about the Windows edition.'; edition: { classification: 'SystemMetaData'; purpose: 'BusinessInsight'; comment: 'The Windows edition.' } }>('windowsEdition', { edition: value }); } private async getWorkspaceInformation(): Promise { -- cgit v1.2.3 From 8c3edc065c6f746e3325584aa66516352cf2ccae Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Mon, 18 Jul 2022 11:41:14 -0700 Subject: Update classification property comments (#155526) --- .../workbench/contrib/notebook/browser/notebookEditor.ts | 16 ++++++++-------- .../contrib/notebook/browser/notebookEditorWidget.ts | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts index 06e09afc3a7..084590f8a0c 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditor.ts @@ -245,14 +245,14 @@ export class NotebookEditor extends EditorPane implements IEditorPaneWithSelecti type WorkbenchNotebookOpenClassification = { owner: 'rebornix'; comment: 'The notebook file open metrics. Used to get a better understanding of the performance of notebook file opening'; - scheme: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; - ext: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; - viewType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; - extensionActivated: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; - inputLoaded: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; - webviewCommLoaded: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; - customMarkdownLoaded: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; - editorLoaded: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; + scheme: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'File system provider scheme for the notebook resource' }; + ext: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'File extension for the notebook resource' }; + viewType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The view type of the notebook editor' }; + extensionActivated: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extension activation time for the resource opening' }; + inputLoaded: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Editor Input loading time for the resource opening' }; + webviewCommLoaded: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Webview initialization time for the resource opening' }; + customMarkdownLoaded: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Custom markdown loading time for the resource opening' }; + editorLoaded: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Overall editor loading time for the resource opening' }; }; type WorkbenchNotebookOpenEvent = { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 9b36800e188..a3f275365dd 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -1102,9 +1102,9 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD type WorkbenchNotebookOpenClassification = { owner: 'rebornix'; comment: 'Identify the notebook editor view type'; - scheme: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; - ext: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; - viewType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; + scheme: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'File system provider scheme for the resource' }; + ext: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'File extension for the resource' }; + viewType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'View type of the notebook editor' }; }; type WorkbenchNotebookOpenEvent = { -- cgit v1.2.3 From 36f146381b492063f1ec0606e192aa2b580dcb34 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 19 Jul 2022 09:42:11 +0200 Subject: editors - add capability to control centered layout (fix #154738) (#155565) --- src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts index 7f5a2febdb7..9a4286f26b6 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/mergeEditorInput.ts @@ -13,7 +13,7 @@ import { ConfirmResult, IDialogService } from 'vs/platform/dialogs/common/dialog import { IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; -import { IEditorIdentifier, IResourceMergeEditorInput, isResourceMergeEditorInput, IUntypedEditorInput } from 'vs/workbench/common/editor'; +import { EditorInputCapabilities, IEditorIdentifier, IResourceMergeEditorInput, isResourceMergeEditorInput, IUntypedEditorInput } from 'vs/workbench/common/editor'; import { EditorInput, IEditorCloseHandler } from 'vs/workbench/common/editor/editorInput'; import { AbstractTextResourceEditorInput } from 'vs/workbench/common/editor/textResourceEditorInput'; import { EditorWorkerServiceDiffComputer } from 'vs/workbench/contrib/mergeEditor/browser/model/diffComputer'; @@ -84,6 +84,10 @@ export class MergeEditorInput extends AbstractTextResourceEditorInput implements return MergeEditorInput.ID; } + override get capabilities(): EditorInputCapabilities { + return super.capabilities | EditorInputCapabilities.MultipleEditors; + } + override getName(): string { return localize('name', "Merging: {0}", super.getName()); } -- cgit v1.2.3 From a8444a04712ff1bf9b83d1670f2e78733c327f3b Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 19 Jul 2022 11:28:51 +0200 Subject: telemetry - document keys (#155563) --- .../telemetry/browser/telemetry.contribution.ts | 50 ++++++++++++---------- 1 file changed, 28 insertions(+), 22 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts index 674741ed4d0..40346309f5e 100644 --- a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts +++ b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts @@ -36,11 +36,11 @@ type TelemetryData = { }; type FileTelemetryDataFragment = { - mimeType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; - ext: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; - path: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; - reason?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; - allowlistedjson?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; + mimeType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The language type of the file (for example XML).' }; + ext: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The file extension of the file (for example xml).' }; + path: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The path of the file as a hash.' }; + reason?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The reason why a file is read or written. Allows to e.g. distinguish auto save from normal save.' }; + allowlistedjson?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The name of the file but only if it matches some well known file names such as package.json or tsconfig.json.' }; }; export class TelemetryContribution extends Disposable implements IWorkbenchContribution { @@ -67,27 +67,29 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr const activeViewlet = paneCompositeService.getActivePaneComposite(ViewContainerLocation.Sidebar); type WindowSizeFragment = { - innerHeight: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; - innerWidth: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; - outerHeight: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; - outerWidth: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; + innerHeight: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The height of the current window.' }; + innerWidth: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The width of the current window.' }; + outerHeight: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The height of the current window with all decoration removed.' }; + outerWidth: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The width of the current window with all decoration removed.' }; + comment: 'The size of the window.'; }; type WorkspaceLoadClassification = { owner: 'bpasero'; - userAgent: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; - emptyWorkbench: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; + userAgent: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The user agent as reported by `navigator.userAgent` by Electron or the web browser.' }; + emptyWorkbench: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Whether a folder or workspace is opened or not.' }; windowSize: WindowSizeFragment; - 'workbench.filesToOpenOrCreate': { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; - 'workbench.filesToDiff': { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; - 'workbench.filesToMerge': { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; + 'workbench.filesToOpenOrCreate': { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Number of files that should open or be created.' }; + 'workbench.filesToDiff': { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Number of files that should be compared.' }; + 'workbench.filesToMerge': { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'Number of files that should be merged.' }; customKeybindingsCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; - theme: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; - language: { classification: 'SystemMetaData'; purpose: 'BusinessInsight' }; - pinnedViewlets: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; - restoredViewlet?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; - restoredEditors: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; - startupKind: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true }; + theme: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The current theme of the window.' }; + language: { classification: 'SystemMetaData'; purpose: 'BusinessInsight'; comment: 'The display language of the window.' }; + pinnedViewlets: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifiers of views that are pinned.' }; + restoredViewlet?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier of the view that is restored.' }; + restoredEditors: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'The number of editors that restored.' }; + startupKind: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; isMeasurement: true; comment: 'How the window was opened, e.g via reload or not.' }; + comment: 'Metadata around the workspace that is being loaded into a window.'; }; type WorkspaceLoadEvent = { @@ -141,13 +143,15 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr if (settingsType) { type SettingsReadClassification = { owner: 'bpasero'; - settingsType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; + settingsType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The type of the settings file that was read.' }; + comment: 'Track when a settings file was read, for example from an editor.'; }; this.telemetryService.publicLog2<{ settingsType: string }, SettingsReadClassification>('settingsRead', { settingsType }); // Do not log read to user settings.json and .vscode folder as a fileGet event as it ruins our JSON usage data } else { type FileGetClassification = { owner: 'bpasero'; + comment: 'Track when a file was read, for example from an editor.'; } & FileTelemetryDataFragment; this.telemetryService.publicLog2('fileGet', this.getTelemetryData(e.model.resource, e.reason)); @@ -159,12 +163,14 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr if (settingsType) { type SettingsWrittenClassification = { owner: 'bpasero'; - settingsType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight' }; + settingsType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The type of the settings file that was written to.' }; + comment: 'Track when a settings file was written to, for example from an editor.'; }; this.telemetryService.publicLog2<{ settingsType: string }, SettingsWrittenClassification>('settingsWritten', { settingsType }); // Do not log write to user settings.json and .vscode folder as a filePUT event as it ruins our JSON usage data } else { type FilePutClassfication = { owner: 'bpasero'; + comment: 'Track when a file was written to, for example from an editor.'; } & FileTelemetryDataFragment; this.telemetryService.publicLog2('filePUT', this.getTelemetryData(e.model.resource, e.reason)); } -- cgit v1.2.3 From 4b95a2d3edef09299f0f1626b08d8049a91f85dd Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 19 Jul 2022 14:23:05 +0200 Subject: merge editor - fix regression with editor resolving (#155596) --- src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts | 3 ++- src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts | 3 ++- src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts index 2a0d776fb18..ca56d104c54 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/commands/commands.ts @@ -9,6 +9,7 @@ import { localize } from 'vs/nls'; import { ILocalizedString } from 'vs/platform/action/common/action'; import { Action2, MenuId } from 'vs/platform/actions/common/actions'; import { ICommandService } from 'vs/platform/commands/common/commands'; +import { EditorResolution } from 'vs/platform/editor/common/editor'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { API_OPEN_DIFF_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; @@ -35,7 +36,7 @@ export class OpenMergeEditor extends Action2 { validatedArgs.input2, validatedArgs.output, ); - accessor.get(IEditorService).openEditor(input, { preserveFocus: true }); + accessor.get(IEditorService).openEditor(input, { preserveFocus: true, override: EditorResolution.DISABLED }); } } diff --git a/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts b/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts index 6728dc93b1a..6fdc90a8b40 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/commands/devCommands.ts @@ -19,6 +19,7 @@ import { IWorkbenchFileService } from 'vs/workbench/services/files/common/files' import { URI } from 'vs/base/common/uri'; import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; import { ctxIsMergeEditor } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor'; +import { EditorResolution } from 'vs/platform/editor/common/editor'; interface MergeEditorContents { languageId: string; @@ -160,6 +161,6 @@ export class MergeEditorOpenContents extends Action2 { { uri: input2Uri, title: 'Input 2', description: 'Input 2', detail: '(from JSON)' }, resultUri, ); - editorService.openEditor(input); + editorService.openEditor(input, { override: EditorResolution.DISABLED }); } } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts index 9f113cfabab..32242173816 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSync.ts @@ -60,6 +60,7 @@ import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/use import { MergeEditorInput } from 'vs/workbench/contrib/mergeEditor/browser/mergeEditorInput'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { ctxIsMergeEditor } from 'vs/workbench/contrib/mergeEditor/common/mergeEditor'; +import { EditorResolution } from 'vs/platform/editor/common/editor'; const CONTEXT_CONFLICTS_SOURCES = new RawContextKey('conflictsSources', ''); @@ -736,7 +737,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo { title: localize('Theirs', 'Theirs'), description: remoteResourceName, detail: undefined, uri: conflict.remoteResource }, conflict.previewResource, ); - await this.editorService.openEditor(input); + await this.editorService.openEditor(input, { override: EditorResolution.DISABLED }); } } -- cgit v1.2.3 From dc755925909f87f6fc44c2d29f3334192eba505c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Tue, 19 Jul 2022 14:25:52 +0200 Subject: fix type issues in h() (#155600) - improve regex with named capture groups - drop $ in favor of inline id - add tests Co-authored-by: Henning Dieterichs Co-authored-by: Henning Dieterichs --- .../mergeEditor/browser/view/editors/codeEditorView.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts index 321d1594dd5..d1988a2cfd9 100644 --- a/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts +++ b/src/vs/workbench/contrib/mergeEditor/browser/view/editors/codeEditorView.ts @@ -24,14 +24,14 @@ export abstract class CodeEditorView extends Disposable { readonly model = this._viewModel.map(m => /** @description model */ m?.model); protected readonly htmlElements = h('div.code-view', [ - h('div.title', { $: 'header' }, [ - h('span.title', { $: 'title' }), - h('span.description', { $: 'description' }), - h('span.detail', { $: 'detail' }), + h('div.title@header', [ + h('span.title@title'), + h('span.description@description'), + h('span.detail@detail'), ]), h('div.container', [ - h('div.gutter', { $: 'gutterDiv' }), - h('div', { $: 'editor' }), + h('div.gutter@gutterDiv'), + h('div@editor'), ]), ]); -- cgit v1.2.3 From b840cff00d477c278796dcdf0d1680d187dbad3c Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Tue, 19 Jul 2022 14:33:03 +0200 Subject: editors - fix `--wait` support (fix #155595) (#155599) --- src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts b/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts index d50317952c0..30d07dcc5b3 100644 --- a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts +++ b/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts @@ -820,7 +820,7 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { return this.parentURI(filesToOpenOrCreate[0].fileUri); } else if (filesToDiff && filesToDiff.length) { return this.parentURI(filesToDiff[0].fileUri); - } else if (filesToMerge && filesToMerge.length) { + } else if (filesToMerge && filesToMerge.length === 4) { return this.parentURI(filesToMerge[3].fileUri) /* [3] is the resulting merge file */; } return undefined; -- cgit v1.2.3 From 0945ef6e358d2bfc3406b43c664014c825f539d2 Mon Sep 17 00:00:00 2001 From: Alexandru Dima Date: Tue, 19 Jul 2022 14:49:19 +0200 Subject: Add comments to properties of telemetry events (#155584) --- src/vs/workbench/contrib/remote/browser/remote.ts | 38 +++++++++++----------- .../contrib/remote/common/remote.contribution.ts | 10 +++--- 2 files changed, 24 insertions(+), 24 deletions(-) (limited to 'src/vs/workbench/contrib') diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index e18a8624103..e5ebb7468a5 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -779,10 +779,10 @@ export class RemoteAgentConnectionStatusListener extends Disposable implements I type ReconnectReloadClassification = { owner: 'alexdima'; comment: 'The reload button in the builtin permanent reconnection failure dialog was pressed'; - remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - reconnectionToken: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - millisSinceLastIncomingData: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - attempt: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; + remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of the resolver.' }; + reconnectionToken: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The identifier of the connection.' }; + millisSinceLastIncomingData: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Elapsed time (in ms) since data was last received.' }; + attempt: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The reconnection attempt counter.' }; }; type ReconnectReloadEvent = { remoteName: string | undefined; @@ -825,8 +825,8 @@ export class RemoteAgentConnectionStatusListener extends Disposable implements I type RemoteConnectionLostClassification = { owner: 'alexdima'; comment: 'The remote connection state is now `ConnectionLost`'; - remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - reconnectionToken: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; + remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of the resolver.' }; + reconnectionToken: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The identifier of the connection.' }; }; type RemoteConnectionLostEvent = { remoteName: string | undefined; @@ -861,10 +861,10 @@ export class RemoteAgentConnectionStatusListener extends Disposable implements I type RemoteReconnectionRunningClassification = { owner: 'alexdima'; comment: 'The remote connection state is now `ReconnectionRunning`'; - remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - reconnectionToken: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - millisSinceLastIncomingData: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - attempt: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; + remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of the resolver.' }; + reconnectionToken: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The identifier of the connection.' }; + millisSinceLastIncomingData: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Elapsed time (in ms) since data was last received.' }; + attempt: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The reconnection attempt counter.' }; }; type RemoteReconnectionRunningEvent = { remoteName: string | undefined; @@ -902,11 +902,11 @@ export class RemoteAgentConnectionStatusListener extends Disposable implements I type RemoteReconnectionPermanentFailureClassification = { owner: 'alexdima'; comment: 'The remote connection state is now `ReconnectionPermanentFailure`'; - remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - reconnectionToken: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - millisSinceLastIncomingData: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - attempt: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - handled: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; + remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of the resolver.' }; + reconnectionToken: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The identifier of the connection.' }; + millisSinceLastIncomingData: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Elapsed time (in ms) since data was last received.' }; + attempt: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The reconnection attempt counter.' }; + handled: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The error was handled by the resolver.' }; }; type RemoteReconnectionPermanentFailureEvent = { remoteName: string | undefined; @@ -947,10 +947,10 @@ export class RemoteAgentConnectionStatusListener extends Disposable implements I type RemoteConnectionGainClassification = { owner: 'alexdima'; comment: 'The remote connection state is now `ConnectionGain`'; - remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - reconnectionToken: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - millisSinceLastIncomingData: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - attempt: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; + remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of the resolver.' }; + reconnectionToken: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The identifier of the connection.' }; + millisSinceLastIncomingData: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Elapsed time (in ms) since data was last received.' }; + attempt: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The reconnection attempt counter.' }; }; type RemoteConnectionGainEvent = { remoteName: string | undefined; diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index 0b2e94b01bc..1bf27f3153c 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -190,9 +190,9 @@ class InitialRemoteConnectionHealthContribution implements IWorkbenchContributio type RemoteConnectionSuccessClassification = { owner: 'alexdima'; comment: 'The initial connection succeeded'; - web: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; + web: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Is web ui.' }; connectionTimeMs: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Time, in ms, until connected'; isMeasurement: true }; - remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; + remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of the resolver.' }; }; type RemoteConnectionSuccessEvent = { web: boolean; @@ -212,10 +212,10 @@ class InitialRemoteConnectionHealthContribution implements IWorkbenchContributio type RemoteConnectionFailureClassification = { owner: 'alexdima'; comment: 'The initial connection failed'; - web: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; - remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; + web: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Is web ui.' }; + remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The name of the resolver.' }; connectionTimeMs: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Time, in ms, until connection failure'; isMeasurement: true }; - message: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth' }; + message: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Error message' }; }; type RemoteConnectionFailureEvent = { web: boolean; -- cgit v1.2.3