Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to ignore snippets (from IntelliSense) #109916

Merged
merged 4 commits into from
Nov 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 82 additions & 40 deletions src/vs/workbench/contrib/snippets/browser/insertSnippet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,9 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { Snippet, SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile';
import { IQuickPickItem, IQuickInputService, QuickPickInput } from 'vs/platform/quickinput/common/quickInput';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { Codicon } from 'vs/base/common/codicons';
import { Event } from 'vs/base/common/event';

interface ISnippetPick extends IQuickPickItem {
snippet: Snippet;
}

class Args {

Expand Down Expand Up @@ -91,7 +90,7 @@ class InsertSnippetAction extends EditorAction {
const clipboardService = accessor.get(IClipboardService);
const quickInputService = accessor.get(IQuickInputService);

const snippet = await new Promise<Snippet | undefined>(async (resolve, reject) => {
const snippet = await new Promise<Snippet | undefined>(async (resolve) => {

const { lineNumber, column } = editor.getPosition();
let { snippet, name, langId } = Args.fromUser(arg);
Expand Down Expand Up @@ -129,44 +128,13 @@ class InsertSnippetAction extends EditorAction {

if (name) {
// take selected snippet
(await snippetService.getSnippets(languageId)).every(snippet => {
if (snippet.name !== name) {
return true;
}
resolve(snippet);
return false;
});
const snippet = (await snippetService.getSnippets(languageId)).find(snippet => snippet.name === name);
resolve(snippet);

} else {
// let user pick a snippet
const snippets = (await snippetService.getSnippets(languageId)).sort(Snippet.compare);
const picks: QuickPickInput<ISnippetPick>[] = [];
let prevSnippet: Snippet | undefined;
for (const snippet of snippets) {
const pick: ISnippetPick = {
label: snippet.prefix,
detail: snippet.description,
snippet
};
if (!prevSnippet || prevSnippet.snippetSource !== snippet.snippetSource) {
let label = '';
switch (snippet.snippetSource) {
case SnippetSource.User:
label = nls.localize('sep.userSnippet', "User Snippets");
break;
case SnippetSource.Extension:
label = nls.localize('sep.extSnippet', "Extension Snippets");
break;
case SnippetSource.Workspace:
label = nls.localize('sep.workspaceSnippet', "Workspace Snippets");
break;
}
picks.push({ type: 'separator', label });

}
picks.push(pick);
prevSnippet = snippet;
}
return quickInputService.pick(picks, { matchOnDetail: true }).then(pick => resolve(pick && pick.snippet), reject);
const snippet = await this._pickSnippet(snippetService, quickInputService, languageId);
resolve(snippet);
}
});

Expand All @@ -179,6 +147,80 @@ class InsertSnippetAction extends EditorAction {
}
SnippetController2.get(editor).insert(snippet.codeSnippet, { clipboardText });
}

private async _pickSnippet(snippetService: ISnippetsService, quickInputService: IQuickInputService, languageId: LanguageId): Promise<Snippet | undefined> {

interface ISnippetPick extends IQuickPickItem {
snippet: Snippet;
}

const snippets = (await snippetService.getSnippets(languageId, true)).sort(Snippet.compare);

const makeSnippetPicks = () => {
const result: QuickPickInput<ISnippetPick>[] = [];
let prevSnippet: Snippet | undefined;
for (const snippet of snippets) {
const pick: ISnippetPick = {
label: snippet.prefix,
detail: snippet.description,
snippet
};
if (!prevSnippet || prevSnippet.snippetSource !== snippet.snippetSource) {
let label = '';
switch (snippet.snippetSource) {
case SnippetSource.User:
label = nls.localize('sep.userSnippet', "User Snippets");
break;
case SnippetSource.Extension:
label = nls.localize('sep.extSnippet', "Extension Snippets");
break;
case SnippetSource.Workspace:
label = nls.localize('sep.workspaceSnippet', "Workspace Snippets");
break;
}
result.push({ type: 'separator', label });
}

if (snippet.snippetSource === SnippetSource.Extension) {
const isEnabled = snippetService.isEnabled(snippet);
if (isEnabled) {
pick.buttons = [{
iconClass: Codicon.eye.classNames,
tooltip: nls.localize('disableSnippet', 'Hide from IntelliSense')
}];
} else {
pick.description = nls.localize('isDisabled', "(hidden from IntelliSense)");
pick.buttons = [{
iconClass: Codicon.eyeClosed.classNames,
tooltip: nls.localize('enable.snippet', 'Show in IntelliSense')
}];
}
}

result.push(pick);
prevSnippet = snippet;
}
return result;
};

const picker = quickInputService.createQuickPick<ISnippetPick>();
picker.placeholder = nls.localize('pick.placeholder', "Select a snippet");
picker.matchOnDescription = true;
picker.ignoreFocusOut = false;
picker.onDidTriggerItemButton(ctx => {
const isEnabled = snippetService.isEnabled(ctx.item.snippet);
snippetService.updateEnablement(ctx.item.snippet, !isEnabled);
picker.items = makeSnippetPicks();
});
picker.items = makeSnippetPicks();
picker.show();

// wait for an item to be picked or the picker to become hidden
await Promise.race([Event.toPromise(picker.onDidAccept), Event.toPromise(picker.onDidHide)]);
const result = picker.selectedItems[0]?.snippet;
picker.dispose();
return result;
}
}

registerEditorAction(InsertSnippetAction);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@ export interface ISnippetsService {

getSnippetFiles(): Promise<Iterable<SnippetFile>>;

getSnippets(languageId: LanguageId): Promise<Snippet[]>;
isEnabled(snippet: Snippet): boolean;

getSnippetsSync(languageId: LanguageId): Snippet[];
updateEnablement(snippet: Snippet, enabled: boolean): void;

getSnippets(languageId: LanguageId, includeDisabledSnippets?: boolean): Promise<Snippet[]>;

getSnippetsSync(languageId: LanguageId, includeDisabledSnippets?: boolean): Snippet[];
}

const languageScopeSchemaId = 'vscode://schemas/snippets';
Expand Down
5 changes: 4 additions & 1 deletion src/vs/workbench/contrib/snippets/browser/snippetsFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { IFileService } from 'vs/platform/files/common/files';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { IdleValue } from 'vs/base/common/async';
import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader';
import { relativePath } from 'vs/base/common/resources';

class SnippetBodyInsights {

Expand Down Expand Up @@ -86,6 +87,7 @@ export class Snippet {
readonly body: string,
readonly source: string,
readonly snippetSource: SnippetSource,
readonly snippetIdentifier?: string
) {
//
this.prefixLow = prefix ? prefix.toLowerCase() : prefix;
Expand Down Expand Up @@ -289,7 +291,8 @@ export class SnippetFile {
description,
body,
source,
this.source
this.source,
this._extension && `${relativePath(this._extension.extensionLocation, this.location)}/${name}`
));
});
}
Expand Down
67 changes: 63 additions & 4 deletions src/vs/workbench/contrib/snippets/browser/snippetsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ import { languagesExtPoint } from 'vs/workbench/services/mode/common/workbenchMo
import { SnippetCompletionProvider } from './snippetCompletionProvider';
import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader';
import { ResourceMap } from 'vs/base/common/map';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { isStringArray } from 'vs/base/common/types';
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';

namespace snippetExt {

Expand Down Expand Up @@ -124,13 +128,55 @@ function watch(service: IFileService, resource: URI, callback: () => any): IDisp
);
}

class SnippetEnablement {

private static _key = 'snippets.ignoredSnippets';

private readonly _ignored: Set<string>;

constructor(
@IStorageService private readonly _storageService: IStorageService,
@IStorageKeysSyncRegistryService storageKeysSyncService: IStorageKeysSyncRegistryService,
) {

storageKeysSyncService.registerStorageKey({ key: SnippetEnablement._key, version: 1 });

const raw = _storageService.get(SnippetEnablement._key, StorageScope.GLOBAL, '');
let data: string[] | undefined;
try {
data = JSON.parse(raw);
} catch { }

this._ignored = isStringArray(data) ? new Set(data) : new Set();
}

isIgnored(id: string): boolean {
return this._ignored.has(id);
}

updateIgnored(id: string, value: boolean): void {
let changed = false;
if (this._ignored.has(id) && !value) {
this._ignored.delete(id);
changed = true;
} else if (!this._ignored.has(id) && value) {
this._ignored.add(id);
changed = true;
}
if (changed) {
this._storageService.store(SnippetEnablement._key, JSON.stringify(Array.from(this._ignored)), StorageScope.GLOBAL);
}
}
}

class SnippetsService implements ISnippetsService {

declare readonly _serviceBrand: undefined;

private readonly _disposables = new DisposableStore();
private readonly _pendingWork: Promise<any>[] = [];
private readonly _files = new ResourceMap<SnippetFile>();
private readonly _enablement: SnippetEnablement;

constructor(
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
Expand All @@ -140,6 +186,7 @@ class SnippetsService implements ISnippetsService {
@IFileService private readonly _fileService: IFileService,
@IExtensionResourceLoaderService private readonly _extensionResourceLoaderService: IExtensionResourceLoaderService,
@ILifecycleService lifecycleService: ILifecycleService,
@IInstantiationService instantiationService: IInstantiationService,
) {
this._pendingWork.push(Promise.resolve(lifecycleService.when(LifecyclePhase.Restored).then(() => {
this._initExtensionSnippets();
Expand All @@ -148,12 +195,24 @@ class SnippetsService implements ISnippetsService {
})));

setSnippetSuggestSupport(new SnippetCompletionProvider(this._modeService, this));

this._enablement = instantiationService.createInstance(SnippetEnablement);
}

dispose(): void {
this._disposables.dispose();
}

isEnabled(snippet: Snippet): boolean {
return !snippet.snippetIdentifier || !this._enablement.isIgnored(snippet.snippetIdentifier);
}

updateEnablement(snippet: Snippet, enabled: boolean): void {
if (snippet.snippetIdentifier) {
this._enablement.updateIgnored(snippet.snippetIdentifier, !enabled);
}
}

private _joinSnippets(): Promise<any> {
const promises = this._pendingWork.slice(0);
this._pendingWork.length = 0;
Expand All @@ -165,7 +224,7 @@ class SnippetsService implements ISnippetsService {
return this._files.values();
}

async getSnippets(languageId: LanguageId): Promise<Snippet[]> {
async getSnippets(languageId: LanguageId, includeIgnored?: boolean): Promise<Snippet[]> {
await this._joinSnippets();

const result: Snippet[] = [];
Expand All @@ -182,10 +241,10 @@ class SnippetsService implements ISnippetsService {
}
}
await Promise.all(promises);
return result;
return includeIgnored ? result : result.filter(this.isEnabled, this);
}

getSnippetsSync(languageId: LanguageId): Snippet[] {
getSnippetsSync(languageId: LanguageId, includeIgnored?: boolean): Snippet[] {
const result: Snippet[] = [];
const languageIdentifier = this._modeService.getLanguageIdentifier(languageId);
if (languageIdentifier) {
Expand All @@ -197,7 +256,7 @@ class SnippetsService implements ISnippetsService {
file.select(langName, result);
}
}
return result;
return includeIgnored ? result : result.filter(this.isEnabled, this);
}

// --- loading, watching
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ import { CompletionContext, CompletionTriggerKind } from 'vs/editor/common/modes

class SimpleSnippetService implements ISnippetsService {
declare readonly _serviceBrand: undefined;
constructor(readonly snippets: Snippet[]) {
}
constructor(readonly snippets: Snippet[]) { }
getSnippets() {
return Promise.resolve(this.getSnippetsSync());
}
Expand All @@ -27,6 +26,12 @@ class SimpleSnippetService implements ISnippetsService {
getSnippetFiles(): any {
throw new Error();
}
isEnabled(): boolean {
throw new Error();
}
updateEnablement(): void {
throw new Error();
}
}

suite('SnippetsService', function () {
Expand Down