diff options
Diffstat (limited to 'src/vs/workbench/contrib/snippets/browser/snippetsService.ts')
-rw-r--r-- | src/vs/workbench/contrib/snippets/browser/snippetsService.ts | 128 |
1 files changed, 111 insertions, 17 deletions
diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsService.ts b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts index abc007e863d..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'; @@ -31,6 +30,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 { @@ -167,7 +167,43 @@ class SnippetEnablement { } } -class SnippetsService implements ISnippetsService { +class SnippetUsageTimestamps { + + private static _key = 'snippets.usageTimestamps'; + + private readonly _usages: Map<string, number>; + + 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); + } +} + +export class SnippetsService implements ISnippetsService { declare readonly _serviceBrand: undefined; @@ -175,6 +211,7 @@ class SnippetsService implements ISnippetsService { private readonly _pendingWork: Promise<any>[] = []; private readonly _files = new ResourceMap<SnippetFile>(); 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<any> { @@ -225,22 +265,31 @@ class SnippetsService implements ISnippetsService { return this._files.values(); } - async getSnippets(languageId: string, opts?: ISnippetGetOptions): Promise<Snippet[]> { + async getSnippets(languageId: string | undefined, opts?: ISnippetGetOptions): Promise<Snippet[]> { await this._joinSnippets(); const result: Snippet[] = []; const promises: Promise<any>[] = []; - 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())) ); } } await Promise.all(promises); - return this._filterSnippets(result, opts); + return this._filterAndSortSnippets(result, opts); } getSnippetsSync(languageId: string, opts?: ISnippetGetOptions): Snippet[] { @@ -253,16 +302,62 @@ 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 => { - return (snippet.prefix || opts?.includeNoPrefixSnippets) // prefix or no-prefix wanted - && (this.isEnabled(snippet) || opts?.includeDisabledSnippets); // enabled or disabled wanted + private _filterAndSortSnippets(snippets: Snippet[], opts?: ISnippetGetOptions): Snippet[] { + + 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; + 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 private _initExtensionSnippets(): void { @@ -408,7 +503,6 @@ class SnippetsService implements ISnippetsService { } } -registerSingleton(ISnippetsService, SnippetsService, true); export interface ISimpleModel { getLineContent(lineNumber: number): string; |