diff --git a/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts b/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts index 771c8891856bc..115c37ad91e28 100644 --- a/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts +++ b/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts @@ -20,6 +20,7 @@ import { IWorkspaceContextService } from '../../../../../platform/workspace/comm import { SnippetsAction } from './abstractSnippetsActions.js'; import { ISnippetsService } from '../snippets.js'; import { SnippetSource } from '../snippetsFile.js'; +import { IEditorService } from '../../../../services/editor/common/editorService.js'; import { ITextFileService } from '../../../../services/textfile/common/textfiles.js'; import { IUserDataProfileService } from '../../../../services/userDataProfile/common/userDataProfile.js'; @@ -34,7 +35,7 @@ interface ISnippetPick extends IQuickPickItem { hint?: true; } -async function computePicks(snippetService: ISnippetsService, userDataProfileService: IUserDataProfileService, languageService: ILanguageService, labelService: ILabelService) { +async function computePicks(snippetService: ISnippetsService, userDataProfileService: IUserDataProfileService, languageService: ILanguageService, labelService: ILabelService, activeLanguageId: string | undefined) { const existing: ISnippetPick[] = []; const future: ISnippetPick[] = []; @@ -138,9 +139,33 @@ async function computePicks(snippetService: ISnippetsService, userDataProfileSer return a.label.localeCompare(b.label); }); + promoteActiveLanguage(existing, future, activeLanguageId); + return { existing, future }; } +// Move the active editor's language to the front of each group and mark it as detected. +export function promoteActiveLanguage(existing: ISnippetPick[], future: ISnippetPick[], activeLanguageId: string | undefined): void { + if (!activeLanguageId) { + return; + } + const detectedHint = nls.localize('detected', "This file type was detected from the current file."); + promote(existing, existing.findIndex(pick => basename(pick.filepath) === `${activeLanguageId}.json`), detectedHint); + promote(future, future.findIndex(pick => pick.label === activeLanguageId), detectedHint); +} + +function promote(picks: ISnippetPick[], index: number, hint: string): void { + if (index < 0) { + return; + } + const pick = picks[index]; + pick.tooltip = hint; + pick.description = pick.description ? `${pick.description} *` : '*'; + if (index > 0) { + picks.unshift(picks.splice(index, 1)[0]); + } +} + async function createSnippetFile(scope: string, defaultPath: URI, quickInputService: IQuickInputService, fileService: IFileService, textFileService: ITextFileService, opener: IOpenerService) { function createSnippetUri(input: string) { @@ -265,13 +290,14 @@ export class ConfigureSnippetsAction extends SnippetsAction { const quickInputService = accessor.get(IQuickInputService); const opener = accessor.get(IOpenerService); const languageService = accessor.get(ILanguageService); + const editorService = accessor.get(IEditorService); const userDataProfileService = accessor.get(IUserDataProfileService); const workspaceService = accessor.get(IWorkspaceContextService); const fileService = accessor.get(IFileService); const textFileService = accessor.get(ITextFileService); const labelService = accessor.get(ILabelService); - const picks = await computePicks(snippetService, userDataProfileService, languageService, labelService); + const picks = await computePicks(snippetService, userDataProfileService, languageService, labelService, editorService.activeTextEditorLanguageId); const existing: QuickPickInput[] = picks.existing; type SnippetPick = IQuickPickItem & { uri: URI } & { scope: string }; diff --git a/src/vs/workbench/contrib/snippets/test/browser/configureSnippets.test.ts b/src/vs/workbench/contrib/snippets/test/browser/configureSnippets.test.ts new file mode 100644 index 0000000000000..c33fe4fa9a833 --- /dev/null +++ b/src/vs/workbench/contrib/snippets/test/browser/configureSnippets.test.ts @@ -0,0 +1,63 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import assert from 'assert'; +import { URI } from '../../../../../base/common/uri.js'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { promoteActiveLanguage } from '../../browser/commands/configureSnippets.js'; + +suite('Configure Snippets', () => { + + ensureNoDisposablesAreLeakedInTestSuite(); + + type TestPick = { label: string; filepath: URI }; + + function file(name: string): TestPick { + return { label: name, filepath: URI.file(`/snippets/${name}`) }; + } + + function lang(id: string): TestPick { + return { label: id, filepath: URI.file(`/snippets/${id}.json`) }; + } + + function reorder(existing: TestPick[], future: TestPick[], activeLanguageId: string | undefined) { + promoteActiveLanguage(existing, future, activeLanguageId); + return { existing: existing.map(p => p.label), future: future.map(p => p.label) }; + } + + test('promotes the active language in both groups', () => { + // existing file for python, and a new-language entry for python -> both jump to front + const existing = [file('global.code-snippets'), file('javascript.json'), file('python.json')]; + const future = [lang('csharp'), lang('go'), lang('python')]; + + assert.deepStrictEqual(reorder(existing, future, 'python'), { + existing: ['python.json', 'global.code-snippets', 'javascript.json'], + future: ['python', 'csharp', 'go'] + }); + }); + + test('no-op cases: no active language, already first, or not present', () => { + const existing = () => [file('javascript.json'), file('python.json')]; + const future = () => [lang('go'), lang('python')]; + + // undefined active language leaves order untouched + assert.deepStrictEqual(reorder(existing(), future(), undefined), { + existing: ['javascript.json', 'python.json'], + future: ['go', 'python'] + }); + + // already first in the existing group -> untouched + assert.deepStrictEqual(reorder(existing(), future(), 'javascript'), { + existing: ['javascript.json', 'python.json'], + future: ['go', 'python'] // no future entry for javascript, so future untouched too + }); + + // language absent from both groups -> untouched + assert.deepStrictEqual(reorder(existing(), future(), 'rust'), { + existing: ['javascript.json', 'python.json'], + future: ['go', 'python'] + }); + }); +});