mirror of
https://github.com/Microsoft/vscode
synced 2024-10-04 10:27:46 +00:00
joh/familiar sparrow (#155613)
* rename to `isFileTemplate` * add code snippet provider for file templates, fix setting model mode https://github.com/microsoft/vscode/issues/145929
This commit is contained in:
parent
34f1bc679d
commit
a260dc7b3e
|
@ -20,7 +20,7 @@ import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
|
|||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IContentActionHandler, renderFormattedText } from 'vs/base/browser/formattedTextRenderer';
|
||||
import { SelectSnippetForEmptyFile } from 'vs/workbench/contrib/snippets/browser/commands/emptyFileSnippets';
|
||||
import { ApplyFileSnippetAction } from 'vs/workbench/contrib/snippets/browser/commands/fileTemplateSnippets';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
|
@ -136,7 +136,7 @@ class UntitledTextEditorHintContentWidget implements IContentWidget {
|
|||
this.domNode.append(hintElement);
|
||||
|
||||
// ugly way to associate keybindings...
|
||||
const keybindingsLookup = [ChangeLanguageAction.ID, SelectSnippetForEmptyFile.Id, 'welcome.showNewFileEntries'];
|
||||
const keybindingsLookup = [ChangeLanguageAction.ID, ApplyFileSnippetAction.Id, 'welcome.showNewFileEntries'];
|
||||
for (const anchor of hintElement.querySelectorAll('A')) {
|
||||
(<HTMLAnchorElement>anchor).style.cursor = 'pointer';
|
||||
const id = keybindingsLookup.shift();
|
||||
|
@ -156,7 +156,7 @@ class UntitledTextEditorHintContentWidget implements IContentWidget {
|
|||
const snippetOnClickOrTab = async (e: MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
this.editor.focus();
|
||||
this.commandService.executeCommand(SelectSnippetForEmptyFile.Id, { from: 'hint' });
|
||||
this.commandService.executeCommand(ApplyFileSnippetAction.Id, { from: 'hint' });
|
||||
};
|
||||
|
||||
const chooseEditorOnClickOrTap = async (e: MouseEvent) => {
|
||||
|
|
|
@ -7,6 +7,7 @@ import { groupBy, isFalsyOrEmpty } from 'vs/base/common/arrays';
|
|||
import { compare } from 'vs/base/common/strings';
|
||||
import { getCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { ILanguageService } from 'vs/editor/common/languages/language';
|
||||
import { IModelService } from 'vs/editor/common/services/model';
|
||||
import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2';
|
||||
import { localize } from 'vs/nls';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
@ -16,16 +17,16 @@ import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets
|
|||
import { Snippet } from 'vs/workbench/contrib/snippets/browser/snippetsFile';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
|
||||
export class SelectSnippetForEmptyFile extends SnippetsAction {
|
||||
export class ApplyFileSnippetAction extends SnippetsAction {
|
||||
|
||||
static readonly Id = 'workbench.action.populateFromSnippet';
|
||||
static readonly Id = 'workbench.action.populateFileFromSnippet';
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: SelectSnippetForEmptyFile.Id,
|
||||
id: ApplyFileSnippetAction.Id,
|
||||
title: {
|
||||
value: localize('label', 'Populate from Snippet'),
|
||||
original: 'Populate from Snippet'
|
||||
value: localize('label', 'Populate File from Snippet'),
|
||||
original: 'Populate File from Snippet'
|
||||
},
|
||||
f1: true,
|
||||
});
|
||||
|
@ -36,13 +37,14 @@ export class SelectSnippetForEmptyFile extends SnippetsAction {
|
|||
const quickInputService = accessor.get(IQuickInputService);
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const langService = accessor.get(ILanguageService);
|
||||
const modelService = accessor.get(IModelService);
|
||||
|
||||
const editor = getCodeEditor(editorService.activeTextEditorControl);
|
||||
if (!editor || !editor.hasModel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const snippets = await snippetService.getSnippets(undefined, { topLevelSnippets: true, noRecencySort: true, includeNoPrefixSnippets: true });
|
||||
const snippets = await snippetService.getSnippets(undefined, { fileTemplateSnippets: true, noRecencySort: true, includeNoPrefixSnippets: true });
|
||||
if (snippets.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
@ -60,9 +62,7 @@ export class SelectSnippetForEmptyFile extends SnippetsAction {
|
|||
}]);
|
||||
|
||||
// set language if possible
|
||||
if (langService.isRegisteredLanguageId(selection.langId)) {
|
||||
editor.getModel().setMode(selection.langId);
|
||||
}
|
||||
modelService.setMode(editor.getModel(), langService.createById(selection.langId));
|
||||
}
|
||||
}
|
||||
|
|
@ -3,28 +3,21 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { CodeAction, CodeActionList, CodeActionProvider } from 'vs/editor/common/languages';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
|
||||
import { CodeActionKind } from 'vs/editor/contrib/codeAction/browser/types';
|
||||
import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { SnippetEditorAction } from 'vs/workbench/contrib/snippets/browser/commands/abstractSnippetsActions';
|
||||
import { pickSnippet } from 'vs/workbench/contrib/snippets/browser/snippetPicker';
|
||||
import { Snippet } from 'vs/workbench/contrib/snippets/browser/snippetsFile';
|
||||
import { ISnippetsService } from '../snippets';
|
||||
|
||||
async function getSurroundableSnippets(snippetsService: ISnippetsService, model: ITextModel, position: Position, includeDisabledSnippets: boolean): Promise<Snippet[]> {
|
||||
export async function getSurroundableSnippets(snippetsService: ISnippetsService, model: ITextModel, position: Position, includeDisabledSnippets: boolean): Promise<Snippet[]> {
|
||||
|
||||
const { lineNumber, column } = position;
|
||||
model.tokenization.tokenizeIfCheap(lineNumber);
|
||||
|
@ -83,77 +76,3 @@ export class SurroundWithSnippetEditorAction extends SnippetEditorAction {
|
|||
snippetsService.updateUsageTimestamp(snippet);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class SurroundWithSnippetCodeActionProvider implements CodeActionProvider, IWorkbenchContribution {
|
||||
|
||||
private static readonly _MAX_CODE_ACTIONS = 4;
|
||||
|
||||
private static readonly _overflowCommandCodeAction: CodeAction = {
|
||||
kind: CodeActionKind.Refactor.value,
|
||||
title: SurroundWithSnippetEditorAction.options.title.value,
|
||||
command: {
|
||||
id: SurroundWithSnippetEditorAction.options.id,
|
||||
title: SurroundWithSnippetEditorAction.options.title.value,
|
||||
},
|
||||
};
|
||||
|
||||
private readonly _registration: IDisposable;
|
||||
|
||||
constructor(
|
||||
@ISnippetsService private readonly _snippetService: ISnippetsService,
|
||||
@ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService,
|
||||
) {
|
||||
this._registration = languageFeaturesService.codeActionProvider.register('*', this);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._registration.dispose();
|
||||
}
|
||||
|
||||
async provideCodeActions(model: ITextModel, range: Range | Selection): Promise<CodeActionList | undefined> {
|
||||
|
||||
if (range.isEmpty()) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const position = Selection.isISelection(range) ? range.getPosition() : range.getStartPosition();
|
||||
const snippets = await getSurroundableSnippets(this._snippetService, model, position, false);
|
||||
if (!snippets.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const actions: CodeAction[] = [];
|
||||
const hasMore = snippets.length > SurroundWithSnippetCodeActionProvider._MAX_CODE_ACTIONS;
|
||||
const len = Math.min(snippets.length, SurroundWithSnippetCodeActionProvider._MAX_CODE_ACTIONS);
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
actions.push(this._makeCodeActionForSnippet(snippets[i], model, range));
|
||||
}
|
||||
if (hasMore) {
|
||||
actions.push(SurroundWithSnippetCodeActionProvider._overflowCommandCodeAction);
|
||||
}
|
||||
return {
|
||||
actions,
|
||||
dispose() { }
|
||||
};
|
||||
}
|
||||
|
||||
private _makeCodeActionForSnippet(snippet: Snippet, model: ITextModel, range: IRange): CodeAction {
|
||||
return {
|
||||
title: localize('codeAction', "Surround With: {0}", snippet.name),
|
||||
kind: CodeActionKind.Refactor.value,
|
||||
edit: {
|
||||
edits: [{
|
||||
versionId: model.getVersionId(),
|
||||
resource: model.uri,
|
||||
textEdit: {
|
||||
range,
|
||||
text: snippet.body,
|
||||
insertAsSnippet: true,
|
||||
}
|
||||
}]
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,139 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { CodeAction, CodeActionList, CodeActionProvider, WorkspaceEdit } from 'vs/editor/common/languages';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
|
||||
import { CodeActionKind } from 'vs/editor/contrib/codeAction/browser/types';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { ApplyFileSnippetAction } from 'vs/workbench/contrib/snippets/browser/commands/fileTemplateSnippets';
|
||||
import { getSurroundableSnippets, SurroundWithSnippetEditorAction } from 'vs/workbench/contrib/snippets/browser/commands/surroundWithSnippet';
|
||||
import { Snippet } from 'vs/workbench/contrib/snippets/browser/snippetsFile';
|
||||
import { ISnippetsService } from './snippets';
|
||||
|
||||
class SurroundWithSnippetCodeActionProvider implements CodeActionProvider {
|
||||
|
||||
private static readonly _MAX_CODE_ACTIONS = 4;
|
||||
|
||||
private static readonly _overflowCommandCodeAction: CodeAction = {
|
||||
kind: CodeActionKind.Refactor.value,
|
||||
title: SurroundWithSnippetEditorAction.options.title.value,
|
||||
command: {
|
||||
id: SurroundWithSnippetEditorAction.options.id,
|
||||
title: SurroundWithSnippetEditorAction.options.title.value,
|
||||
},
|
||||
};
|
||||
|
||||
constructor(@ISnippetsService private readonly _snippetService: ISnippetsService) { }
|
||||
|
||||
async provideCodeActions(model: ITextModel, range: Range | Selection): Promise<CodeActionList | undefined> {
|
||||
|
||||
if (range.isEmpty()) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const position = Selection.isISelection(range) ? range.getPosition() : range.getStartPosition();
|
||||
const snippets = await getSurroundableSnippets(this._snippetService, model, position, false);
|
||||
if (!snippets.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const actions: CodeAction[] = [];
|
||||
for (const snippet of snippets) {
|
||||
if (actions.length >= SurroundWithSnippetCodeActionProvider._MAX_CODE_ACTIONS) {
|
||||
actions.push(SurroundWithSnippetCodeActionProvider._overflowCommandCodeAction);
|
||||
break;
|
||||
}
|
||||
actions.push({
|
||||
title: localize('codeAction', "Surround With: {0}", snippet.name),
|
||||
kind: CodeActionKind.Refactor.value,
|
||||
edit: asWorkspaceEdit(model, range, snippet)
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
actions,
|
||||
dispose() { }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class FileTemplateCodeActionProvider implements CodeActionProvider {
|
||||
|
||||
private static readonly _MAX_CODE_ACTIONS = 4;
|
||||
|
||||
private static readonly _overflowCommandCodeAction: CodeAction = {
|
||||
title: localize('overflow.start.title', 'Start with Snippet'),
|
||||
kind: CodeActionKind.Refactor.value,
|
||||
command: {
|
||||
id: ApplyFileSnippetAction.Id,
|
||||
title: ''
|
||||
}
|
||||
};
|
||||
|
||||
readonly providedCodeActionKinds?: readonly string[] = [CodeActionKind.Refactor.value];
|
||||
|
||||
constructor(@ISnippetsService private readonly _snippetService: ISnippetsService) { }
|
||||
|
||||
async provideCodeActions(model: ITextModel) {
|
||||
if (model.getValueLength() !== 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const snippets = await this._snippetService.getSnippets(model.getLanguageId(), { fileTemplateSnippets: true, includeNoPrefixSnippets: true });
|
||||
const actions: CodeAction[] = [];
|
||||
for (const snippet of snippets) {
|
||||
if (actions.length >= FileTemplateCodeActionProvider._MAX_CODE_ACTIONS) {
|
||||
actions.push(FileTemplateCodeActionProvider._overflowCommandCodeAction);
|
||||
break;
|
||||
}
|
||||
actions.push({
|
||||
title: localize('title', 'Start with: {0}', snippet.name),
|
||||
kind: CodeActionKind.Refactor.value,
|
||||
edit: asWorkspaceEdit(model, model.getFullModelRange(), snippet)
|
||||
});
|
||||
}
|
||||
return {
|
||||
actions,
|
||||
dispose() { }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function asWorkspaceEdit(model: ITextModel, range: IRange, snippet: Snippet): WorkspaceEdit {
|
||||
return {
|
||||
edits: [{
|
||||
versionId: model.getVersionId(),
|
||||
resource: model.uri,
|
||||
textEdit: {
|
||||
range,
|
||||
text: snippet.body,
|
||||
insertAsSnippet: true,
|
||||
}
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
export class SnippetCodeActions implements IWorkbenchContribution {
|
||||
|
||||
private readonly _store = new DisposableStore();
|
||||
|
||||
constructor(
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@ILanguageFeaturesService languageFeaturesService: ILanguageFeaturesService,
|
||||
) {
|
||||
this._store.add(languageFeaturesService.codeActionProvider.register('*', instantiationService.createInstance(SurroundWithSnippetCodeActionProvider)));
|
||||
this._store.add(languageFeaturesService.codeActionProvider.register('*', instantiationService.createInstance(FileTemplateCodeActionProvider)));
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._store.dispose();
|
||||
}
|
||||
}
|
|
@ -12,9 +12,10 @@ import * as JSONContributionRegistry from 'vs/platform/jsonschemas/common/jsonCo
|
|||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
|
||||
import { ConfigureSnippets } from 'vs/workbench/contrib/snippets/browser/commands/configureSnippets';
|
||||
import { SelectSnippetForEmptyFile } from 'vs/workbench/contrib/snippets/browser/commands/emptyFileSnippets';
|
||||
import { ApplyFileSnippetAction } from 'vs/workbench/contrib/snippets/browser/commands/fileTemplateSnippets';
|
||||
import { InsertSnippetAction } from 'vs/workbench/contrib/snippets/browser/commands/insertSnippet';
|
||||
import { SurroundWithSnippetCodeActionProvider, SurroundWithSnippetEditorAction } from 'vs/workbench/contrib/snippets/browser/commands/surroundWithSnippet';
|
||||
import { SurroundWithSnippetEditorAction } from 'vs/workbench/contrib/snippets/browser/commands/surroundWithSnippet';
|
||||
import { SnippetCodeActions } from 'vs/workbench/contrib/snippets/browser/snippetCodeActionProvider';
|
||||
import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets';
|
||||
import { SnippetsService } from 'vs/workbench/contrib/snippets/browser/snippetsService';
|
||||
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
|
@ -29,10 +30,11 @@ registerAction2(InsertSnippetAction);
|
|||
CommandsRegistry.registerCommandAlias('editor.action.showSnippets', 'editor.action.insertSnippet');
|
||||
registerAction2(SurroundWithSnippetEditorAction);
|
||||
registerAction2(ConfigureSnippets);
|
||||
registerAction2(SelectSnippetForEmptyFile);
|
||||
registerAction2(ApplyFileSnippetAction);
|
||||
|
||||
// workbench contribs
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(SurroundWithSnippetCodeActionProvider, LifecyclePhase.Restored);
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
|
||||
.registerWorkbenchContribution(SnippetCodeActions, LifecyclePhase.Restored);
|
||||
|
||||
// schema
|
||||
const languageScopeSchemaId = 'vscode://schemas/snippets';
|
||||
|
@ -42,8 +44,8 @@ const snippetSchemaProperties: IJSONSchemaMap = {
|
|||
description: nls.localize('snippetSchema.json.prefix', 'The prefix to use when selecting the snippet in intellisense'),
|
||||
type: ['string', 'array']
|
||||
},
|
||||
isTopLevel: {
|
||||
description: nls.localize('snippetSchema.json.isTopLevel', 'The snippet is only applicable to empty files.'),
|
||||
isFileTemplate: {
|
||||
description: nls.localize('snippetSchema.json.isFileTemplate', 'The snippet is meant to populate or replace a whole file'),
|
||||
type: 'boolean'
|
||||
},
|
||||
body: {
|
||||
|
|
|
@ -12,7 +12,7 @@ export interface ISnippetGetOptions {
|
|||
includeDisabledSnippets?: boolean;
|
||||
includeNoPrefixSnippets?: boolean;
|
||||
noRecencySort?: boolean;
|
||||
topLevelSnippets?: boolean;
|
||||
fileTemplateSnippets?: boolean;
|
||||
}
|
||||
|
||||
export interface ISnippetsService {
|
||||
|
|
|
@ -105,7 +105,7 @@ export class Snippet {
|
|||
readonly prefixLow: string;
|
||||
|
||||
constructor(
|
||||
readonly isTopLevel: boolean,
|
||||
readonly isFileTemplate: boolean,
|
||||
readonly scopes: string[],
|
||||
readonly name: string,
|
||||
readonly prefix: string,
|
||||
|
@ -143,7 +143,7 @@ export class Snippet {
|
|||
|
||||
|
||||
interface JsonSerializedSnippet {
|
||||
isTopLevel?: boolean;
|
||||
isFileTemplate?: boolean;
|
||||
body: string | string[];
|
||||
scope?: string;
|
||||
prefix: string | string[] | undefined;
|
||||
|
@ -261,7 +261,7 @@ export class SnippetFile {
|
|||
|
||||
private _parseSnippet(name: string, snippet: JsonSerializedSnippet, bucket: Snippet[]): void {
|
||||
|
||||
let { isTopLevel, prefix, body, description } = snippet;
|
||||
let { isFileTemplate, prefix, body, description } = snippet;
|
||||
|
||||
if (!prefix) {
|
||||
prefix = '';
|
||||
|
@ -306,7 +306,7 @@ export class SnippetFile {
|
|||
|
||||
for (const _prefix of Array.isArray(prefix) ? prefix : Iterable.single(prefix)) {
|
||||
bucket.push(new Snippet(
|
||||
Boolean(isTopLevel),
|
||||
Boolean(isFileTemplate),
|
||||
scopes,
|
||||
name,
|
||||
_prefix,
|
||||
|
|
|
@ -318,7 +318,7 @@ export class SnippetsService implements ISnippetsService {
|
|||
// enabled or disabled wanted
|
||||
continue;
|
||||
}
|
||||
if (typeof opts?.topLevelSnippets === 'boolean' && opts.topLevelSnippets !== snippet.isTopLevel) {
|
||||
if (typeof opts?.fileTemplateSnippets === 'boolean' && opts.fileTemplateSnippets !== snippet.isFileTemplate) {
|
||||
// isTopLevel requested but mismatching
|
||||
continue;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue