add global snippets and create action to configure action, #13182

This commit is contained in:
Johannes Rieken
2018-01-04 15:44:01 +01:00
parent a88b6e1bd3
commit d643caec85
4 changed files with 212 additions and 84 deletions
+7
View File
@@ -314,6 +314,13 @@ export class ShallowCancelThenPromise<T> extends TPromise<T> {
}
}
/**
* Replacement for `WinJS.Promise.timeout`.
*/
export function timeout(n: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, n));
}
/**
* Returns a new promise that joins the provided promise. Upon completion of
* the provided promise the provided function will always be called. This
@@ -0,0 +1,203 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nls from 'vs/nls';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { fileExists, writeFile, readdir } from 'vs/base/node/pfs';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IQuickOpenService, IPickOpenEntry, ISeparator } from 'vs/platform/quickOpen/common/quickOpen';
import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows';
import { join, basename, dirname } from 'path';
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
import { TPromise } from 'vs/base/common/winjs.base';
import { endsWith } from 'vs/base/common/strings';
import { timeout } from 'vs/base/common/async';
const id = 'workbench.action.openSnippets';
class LanguagePick implements IPickOpenEntry {
static list(modeService: IModeService, envService: IEnvironmentService): LanguagePick[] {
const modes = modeService.getRegisteredModes();
const result: LanguagePick[] = [];
for (const mode of modes) {
const langLabel = modeService.getLanguageName(mode);
if (langLabel) {
result.push(new LanguagePick(langLabel, mode, join(envService.appSettingsHome, 'snippets', `${mode}.json`)));
}
}
if (result.length > 0) {
result[0].separator = { label: nls.localize('group.lang', "Language Snippets") };
}
return result.sort(LanguagePick.compare);
}
label: string;
filepath: string;
langName: string;
separator?: ISeparator;
constructor(langLabel: string, langName: string, filepath: string) {
this.label = langLabel;
this.langName = langName;
this.filepath = filepath;
}
async create(): Promise<this> {
const contents = [
'{',
'/*',
'\t// Place your snippets for ' + this.langName + ' 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 writeFile(this.filepath, contents);
return this;
}
static compare(a: LanguagePick, b: LanguagePick): number {
return a.label.localeCompare(b.label);
}
}
class SnippetFilePick implements IPickOpenEntry {
static list(envService: IEnvironmentService): Thenable<SnippetFilePick[]> {
const dir = join(envService.appSettingsHome, 'snippets');
return readdir(dir).then(entries => {
const result: SnippetFilePick[] = [];
for (const filename of entries) {
if (endsWith(filename, '.code-snippets')) {
result.push(new SnippetFilePick(join(dir, filename)));
}
}
if (result.length > 0) {
result[0].separator = { border: true, label: nls.localize('group.global', "Global Snippets") };
}
return result;
});
}
label: string;
filepath: string;
separator?: ISeparator;
constructor(filepath: string) {
this.label = basename(filepath);
this.filepath = filepath;
}
}
class NewSnippetFilePick implements IPickOpenEntry {
readonly label: string = nls.localize('create', "Create Global Snippets File...");
constructor(
private _envService: IEnvironmentService,
private _windowService: IWindowService
) {
//
}
async create(): Promise<string> {
const defaultPath = join(this._envService.appSettingsHome, 'snippets');
const path = await this._windowService.showSaveDialog({
defaultPath,
filters: [{ name: 'Code Snippets', extensions: ['code-snippets'] }]
});
if (!path || dirname(path) !== defaultPath) {
return undefined;
}
await writeFile(path, [
'{',
'/*',
'\t// Each snippet is defined under a snippet name and has a scope, prefix, body and ',
'\t// description. The scope defines in watch languages the snippet is applicable. 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'));
return path;
}
}
type SnippetPick = LanguagePick | SnippetFilePick | NewSnippetFilePick;
CommandsRegistry.registerCommand(id, async accessor => {
const quickOpenService = accessor.get(IQuickOpenService);
const windowsService = accessor.get(IWindowsService);
const windowService = accessor.get(IWindowService);
const modeService = accessor.get(IModeService);
const envService = accessor.get(IEnvironmentService);
function openFile(filePath: string): TPromise<void> {
return windowsService.openWindow([filePath], { forceReuseWindow: true });
}
const picks = <SnippetPick[]>[
...LanguagePick.list(modeService, envService),
...await SnippetFilePick.list(envService),
new NewSnippetFilePick(envService, windowService),
];
const pick = await quickOpenService.pick(picks, {
placeHolder: nls.localize('openSnippet.pickLanguage', "Select Language or Global snippet")
});
if (pick instanceof LanguagePick) {
if (!await fileExists(pick.filepath)) {
await pick.create();
}
return openFile(pick.filepath);
} else if (pick instanceof SnippetFilePick) {
// simply open the file
return openFile(pick.filepath);
} else if (pick instanceof NewSnippetFilePick) {
await timeout(500); // quick pick will stay open otherwise
const path = await pick.create();
if (path) {
return openFile(path);
}
}
});
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: {
id,
title: { value: nls.localize('openSnippet.label', "Open User Snippets"), original: 'Preferences: Open User Snippets' },
category: nls.localize('preferences', "Preferences")
}
});
@@ -3,22 +3,13 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { fileExists, writeFile } from 'vs/base/node/pfs';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IQuickOpenService, IPickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen';
import { IWindowsService } from 'vs/platform/windows/common/windows';
import { join } from 'path';
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
import { Registry } from 'vs/platform/registry/common/platform';
import * as errors from 'vs/base/common/errors';
import * as JSONContributionRegistry from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import * as nls from 'vs/nls';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { LanguageId } from 'vs/editor/common/modes';
import { TPromise } from 'vs/base/common/winjs.base';
import { SnippetParser, Variable, Placeholder, Text } from 'vs/editor/contrib/snippet/snippetParser';
import { KnownSnippetVariableNames } from 'vs/editor/contrib/snippet/snippetVariables';
@@ -134,80 +125,6 @@ export class Snippet {
}
}
{
const id = 'workbench.action.openSnippets';
CommandsRegistry.registerCommand(id, accessor => {
const modeService = accessor.get(IModeService);
const quickOpenService = accessor.get(IQuickOpenService);
const environmentService = accessor.get(IEnvironmentService);
const windowsService = accessor.get(IWindowsService);
function openFile(filePath: string): TPromise<void> {
return windowsService.openWindow([filePath], { forceReuseWindow: true });
}
const modeIds = modeService.getRegisteredModes();
let picks: IPickOpenEntry[] = [];
modeIds.forEach((modeId) => {
const name = modeService.getLanguageName(modeId);
if (name) {
picks.push({ label: name, id: modeId });
}
});
picks = picks.sort((e1, e2) =>
e1.label.localeCompare(e2.label)
);
return quickOpenService.pick(picks, { placeHolder: nls.localize('openSnippet.pickLanguage', "Select Language for Snippet") }).then((language) => {
if (language) {
const snippetPath = join(environmentService.appSettingsHome, 'snippets', language.id + '.json');
return fileExists(snippetPath).then((success) => {
if (success) {
return openFile(snippetPath);
}
const defaultContent = [
'{',
'/*',
'\t// Place your snippets for ' + language.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');
return writeFile(snippetPath, defaultContent).then(() => {
return openFile(snippetPath);
}, (err) => {
errors.onUnexpectedError(nls.localize('openSnippet.errorOnCreate', 'Unable to create {0}', snippetPath));
});
});
}
return TPromise.as(null);
});
});
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: {
id,
title: { value: nls.localize('openSnippet.label', "Open User Snippets"), original: 'Preferences: Open User Snippets' },
category: nls.localize('preferences', "Preferences")
}
});
}
const languageScopeSchemaId = 'vscode://schemas/snippets';
const languageScopeSchema: IJSONSchema = {
id: languageScopeSchemaId,
+1
View File
@@ -98,6 +98,7 @@ import 'vs/workbench/parts/execution/electron-browser/execution.contribution';
import 'vs/workbench/parts/snippets/electron-browser/snippets.contribution';
import 'vs/workbench/parts/snippets/electron-browser/snippetsService';
import 'vs/workbench/parts/snippets/electron-browser/insertSnippet';
import 'vs/workbench/parts/snippets/electron-browser/configureSnippets';
import 'vs/workbench/parts/snippets/electron-browser/tabCompletion';
import 'vs/workbench/parts/themes/electron-browser/themes.contribution';