Update to VS Code 1.52.1

This commit is contained in:
Asher
2021-02-09 16:08:37 +00:00
1351 changed files with 56560 additions and 38990 deletions

View File

@@ -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 {
@@ -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);
@@ -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);
}
});
@@ -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, { includeDisabledSnippets: true, includeNoPrefixSnippets: 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 || snippet.name,
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.eyeClosed.classNames,
tooltip: nls.localize('disableSnippet', 'Hide from IntelliSense')
}];
} else {
pick.description = nls.localize('isDisabled', "(hidden from IntelliSense)");
pick.buttons = [{
iconClass: Codicon.eye.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);

View File

@@ -13,15 +13,24 @@ import { SnippetFile, Snippet } from 'vs/workbench/contrib/snippets/browser/snip
export const ISnippetsService = createDecorator<ISnippetsService>('snippetService');
export interface ISnippetGetOptions {
includeDisabledSnippets?: boolean;
includeNoPrefixSnippets?: boolean;
}
export interface ISnippetsService {
readonly _serviceBrand: undefined;
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, opt?: ISnippetGetOptions): Promise<Snippet[]>;
getSnippetsSync(languageId: LanguageId, opt?: ISnippetGetOptions): Snippet[];
}
const languageScopeSchemaId = 'vscode://schemas/snippets';
@@ -56,7 +65,7 @@ const languageScopeSchema: IJSONSchema = {
description: nls.localize('snippetSchema.json', 'User snippet configuration'),
additionalProperties: {
type: 'object',
required: ['prefix', 'body'],
required: ['body'],
properties: snippetSchemaProperties,
additionalProperties: false
}
@@ -76,7 +85,7 @@ const globalSchema: IJSONSchema = {
description: nls.localize('snippetSchema.json', 'User snippet configuration'),
additionalProperties: {
type: 'object',
required: ['prefix', 'body'],
required: ['body'],
properties: {
...snippetSchemaProperties,
scope: {

View File

@@ -15,6 +15,9 @@ 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';
import { isObject } from 'vs/base/common/types';
import { Iterable } from 'vs/base/common/iterator';
class SnippetBodyInsights {
@@ -86,9 +89,9 @@ export class Snippet {
readonly body: string,
readonly source: string,
readonly snippetSource: SnippetSource,
readonly snippetIdentifier?: string
) {
//
this.prefixLow = prefix ? prefix.toLowerCase() : prefix;
this.prefixLow = prefix.toLowerCase();
this._bodyInsights = new IdleValue(() => new SnippetBodyInsights(this.body));
}
@@ -121,14 +124,14 @@ export class Snippet {
interface JsonSerializedSnippet {
body: string;
body: string | string[];
scope: string;
prefix: string | string[];
prefix: string | string[] | undefined;
description: string;
}
function isJsonSerializedSnippet(thing: any): thing is JsonSerializedSnippet {
return Boolean((<JsonSerializedSnippet>thing).body) && Boolean((<JsonSerializedSnippet>thing).prefix);
return isObject(thing) && Boolean((<JsonSerializedSnippet>thing).body);
}
interface JsonSerializedSnippets {
@@ -242,18 +245,21 @@ export class SnippetFile {
let { prefix, body, description } = snippet;
if (!prefix) {
prefix = '';
}
if (Array.isArray(body)) {
body = body.join('\n');
}
if (typeof body !== 'string') {
return;
}
if (Array.isArray(description)) {
description = description.join('\n');
}
if ((typeof prefix !== 'string' && !Array.isArray(prefix)) || typeof body !== 'string') {
return;
}
let scopes: string[];
if (this.defaultScopes) {
scopes = this.defaultScopes;
@@ -280,17 +286,17 @@ export class SnippetFile {
}
}
let prefixes = Array.isArray(prefix) ? prefix : [prefix];
prefixes.forEach(p => {
for (const _prefix of Array.isArray(prefix) ? prefix : Iterable.single(prefix)) {
bucket.push(new Snippet(
scopes,
name,
p,
_prefix,
description,
body,
source,
this.source
this.source,
this._extension && `${relativePath(this._extension.extensionLocation, this.location)}/${name}`
));
});
}
}
}

View File

@@ -19,12 +19,16 @@ 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 { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets.contribution';
import { ISnippetGetOptions, ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets.contribution';
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/mode/common/workbenchModeService';
import { SnippetCompletionProvider } from './snippetCompletionProvider';
import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader';
import { ResourceMap } from 'vs/base/common/map';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { isStringArray } from 'vs/base/common/types';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
namespace snippetExt {
@@ -123,13 +127,52 @@ 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,
) {
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, StorageTarget.USER);
}
}
}
class SnippetsService implements ISnippetsService {
readonly _serviceBrand: undefined;
declare readonly _serviceBrand: undefined;
private readonly _disposables = new DisposableStore();
private readonly _pendingWork: Promise<any>[] = [];
private readonly _files = new Map<string, SnippetFile>();
private readonly _files = new ResourceMap<SnippetFile>();
private readonly _enablement: SnippetEnablement;
constructor(
@IEnvironmentService private readonly _environmentService: IEnvironmentService,
@@ -139,6 +182,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();
@@ -147,12 +191,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;
@@ -164,26 +220,27 @@ class SnippetsService implements ISnippetsService {
return this._files.values();
}
getSnippets(languageId: LanguageId): Promise<Snippet[]> {
return this._joinSnippets().then(() => {
const result: Snippet[] = [];
const promises: Promise<any>[] = [];
async getSnippets(languageId: LanguageId, opts?: ISnippetGetOptions): Promise<Snippet[]> {
await this._joinSnippets();
const languageIdentifier = this._modeService.getLanguageIdentifier(languageId);
if (languageIdentifier) {
const langName = languageIdentifier.language;
for (const file of this._files.values()) {
promises.push(file.load()
.then(file => file.select(langName, result))
.catch(err => this._logService.error(err, file.location.toString()))
);
}
const result: Snippet[] = [];
const promises: Promise<any>[] = [];
const languageIdentifier = this._modeService.getLanguageIdentifier(languageId);
if (languageIdentifier) {
const langName = languageIdentifier.language;
for (const file of this._files.values()) {
promises.push(file.load()
.then(file => file.select(langName, result))
.catch(err => this._logService.error(err, file.location.toString()))
);
}
return Promise.all(promises).then(() => result);
});
}
await Promise.all(promises);
return this._filterSnippets(result, opts);
}
getSnippetsSync(languageId: LanguageId): Snippet[] {
getSnippetsSync(languageId: LanguageId, opts?: ISnippetGetOptions): Snippet[] {
const result: Snippet[] = [];
const languageIdentifier = this._modeService.getLanguageIdentifier(languageId);
if (languageIdentifier) {
@@ -191,11 +248,18 @@ class SnippetsService implements ISnippetsService {
for (const file of this._files.values()) {
// kick off loading (which is a noop in case it's already loaded)
// and optimistically collect snippets
file.load().catch(err => { /*ignore*/ });
file.load().catch(_err => { /*ignore*/ });
file.select(langName, result);
}
}
return result;
return this._filterSnippets(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
});
}
// --- loading, watching
@@ -203,7 +267,7 @@ class SnippetsService implements ISnippetsService {
private _initExtensionSnippets(): void {
snippetExt.point.setHandler(extensions => {
for (let [key, value] of this._files) {
for (const [key, value] of this._files) {
if (value.source === SnippetSource.Extension) {
this._files.delete(key);
}
@@ -216,8 +280,7 @@ class SnippetsService implements ISnippetsService {
continue;
}
const resource = validContribution.location.toString();
const file = this._files.get(resource);
const file = this._files.get(validContribution.location);
if (file) {
if (file.defaultScopes) {
file.defaultScopes.push(validContribution.language);
@@ -226,7 +289,7 @@ class SnippetsService implements ISnippetsService {
}
} else {
const file = new SnippetFile(SnippetSource.Extension, validContribution.location, validContribution.language ? [validContribution.language] : undefined, extension.description, this._fileService, this._extensionResourceLoaderService);
this._files.set(file.location.toString(), file);
this._files.set(file.location, file);
if (this._environmentService.isExtensionDevelopment) {
file.load().then(file => {
@@ -267,28 +330,28 @@ class SnippetsService implements ISnippetsService {
updateWorkspaceSnippets();
}
private _initWorkspaceFolderSnippets(workspace: IWorkspace, bucket: DisposableStore): Promise<any> {
let promises = workspace.folders.map(folder => {
private async _initWorkspaceFolderSnippets(workspace: IWorkspace, bucket: DisposableStore): Promise<any> {
const promises = workspace.folders.map(async folder => {
const snippetFolder = folder.toResource('.vscode');
return this._fileService.exists(snippetFolder).then(value => {
if (value) {
this._initFolderSnippets(SnippetSource.Workspace, snippetFolder, bucket);
} else {
// watch
bucket.add(this._fileService.onDidFilesChange(e => {
if (e.contains(snippetFolder, FileChangeType.ADDED)) {
this._initFolderSnippets(SnippetSource.Workspace, snippetFolder, bucket);
}
}));
}
});
const value = await this._fileService.exists(snippetFolder);
if (value) {
this._initFolderSnippets(SnippetSource.Workspace, snippetFolder, bucket);
} else {
// watch
bucket.add(this._fileService.onDidFilesChange(e => {
if (e.contains(snippetFolder, FileChangeType.ADDED)) {
this._initFolderSnippets(SnippetSource.Workspace, snippetFolder, bucket);
}
}));
}
});
return Promise.all(promises);
await Promise.all(promises);
}
private _initUserSnippets(): Promise<any> {
private async _initUserSnippets(): Promise<any> {
const userSnippetsFolder = this._environmentService.snippetsHome;
return this._fileService.createFolder(userSnippetsFolder).then(() => this._initFolderSnippets(SnippetSource.User, userSnippetsFolder, this._disposables));
await this._fileService.createFolder(userSnippetsFolder);
return await this._initFolderSnippets(SnippetSource.User, userSnippetsFolder, this._disposables);
}
private _initFolderSnippets(source: SnippetSource, folder: URI, bucket: DisposableStore): Promise<any> {
@@ -315,15 +378,14 @@ class SnippetsService implements ISnippetsService {
private _addSnippetFile(uri: URI, source: SnippetSource): IDisposable {
const ext = resources.extname(uri);
const key = uri.toString();
if (source === SnippetSource.User && ext === '.json') {
const langName = resources.basename(uri).replace(/\.json/, '');
this._files.set(key, new SnippetFile(source, uri, [langName], undefined, this._fileService, this._extensionResourceLoaderService));
this._files.set(uri, new SnippetFile(source, uri, [langName], undefined, this._fileService, this._extensionResourceLoaderService));
} else if (ext === '.code-snippets') {
this._files.set(key, new SnippetFile(source, uri, undefined, undefined, this._fileService, this._extensionResourceLoaderService));
this._files.set(uri, new SnippetFile(source, uri, undefined, undefined, this._fileService, this._extensionResourceLoaderService));
}
return {
dispose: () => this._files.delete(key)
dispose: () => this._files.delete(uri)
};
}
}

View File

@@ -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());
}
@@ -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 () {