Provide emmet abbreviations via inline completions (#147387)

This PR changes Emmet to use inline completions when suggesting completions.
It also provides a boolean setting so that users can opt out.
This PR does not change the "Wrap with Abbreviation" behaviour in Emmet.

Co-authored-by: Raymond Zhao <raymondzhao@microsoft.com>
This commit is contained in:
Henning Dieterichs
2022-04-19 22:33:29 +02:00
committed by GitHub
parent 800eec5645
commit 94de38e7dd
4 changed files with 73 additions and 13 deletions

View File

@@ -16,6 +16,9 @@
"type": "git",
"url": "https://github.com/microsoft/vscode.git"
},
"enabledApiProposals": [
"inlineCompletionsNew"
],
"activationEvents": [
"onCommand:emmet.expandAbbreviation",
"onCommand:editor.emmet.action.wrapWithAbbreviation",
@@ -127,6 +130,11 @@
"default": false,
"markdownDescription": "%emmetTriggerExpansionOnTab%"
},
"emmet.useInlineCompletions": {
"type": "boolean",
"default": true,
"markdownDescription": "%emmetUseInlineCompletions%"
},
"emmet.preferences": {
"type": "object",
"default": {},

View File

@@ -59,5 +59,6 @@
"emmetPreferencesOutputInlineBreak": "The number of sibling inline elements needed for line breaks to be placed between those elements. If `0`, inline elements are always expanded onto a single line.",
"emmetPreferencesOutputReverseAttributes": "If `true`, reverses attribute merging directions when resolving snippets.",
"emmetPreferencesOutputSelfClosingStyle": "Style of self-closing tags: html (`<br>`), xml (`<br/>`) or xhtml (`<br />`).",
"emmetPreferencesCssColorShort": "If `true`, color values like `#f` will be expanded to `#fff` instead of `#ffffff`."
"emmetPreferencesCssColorShort": "If `true`, color values like `#f` will be expanded to `#fff` instead of `#ffffff`.",
"emmetUseInlineCompletions": "If `true`, Emmet will use inline completions to suggest expansions."
}

View File

@@ -23,7 +23,7 @@ import { addFileToParseCache, clearParseCache, removeFileFromParseCache } from '
export function activateEmmetExtension(context: vscode.ExtensionContext) {
migrateEmmetExtensionsPath();
registerCompletionProviders(context);
refreshCompletionProviders(context);
updateEmmetExtensionsPath();
context.subscriptions.push(vscode.commands.registerCommand('editor.emmet.action.wrapWithAbbreviation', (args) => {
@@ -122,8 +122,8 @@ export function activateEmmetExtension(context: vscode.ExtensionContext) {
}));
context.subscriptions.push(vscode.workspace.onDidChangeConfiguration((e) => {
if (e.affectsConfiguration('emmet.includeLanguages')) {
registerCompletionProviders(context);
if (e.affectsConfiguration('emmet.includeLanguages') || e.affectsConfiguration('emmet.useInlineCompletions')) {
refreshCompletionProviders(context);
}
if (e.affectsConfiguration('emmet.extensionsPath')) {
updateEmmetExtensionsPath();
@@ -159,11 +159,42 @@ export function activateEmmetExtension(context: vscode.ExtensionContext) {
*/
const languageMappingForCompletionProviders: Map<string, string> = new Map<string, string>();
const completionProvidersMapping: Map<string, vscode.Disposable> = new Map<string, vscode.Disposable>();
const completionProviderDisposables: vscode.Disposable[] = [];
function registerCompletionProviders(context: vscode.ExtensionContext) {
let completionProvider = new DefaultCompletionItemProvider();
let includedLanguages = getMappingForIncludedLanguages();
function refreshCompletionProviders(_: vscode.ExtensionContext) {
clearCompletionProviderInfo();
const completionProvider = new DefaultCompletionItemProvider();
const inlineCompletionProvider: vscode.InlineCompletionItemProviderNew = {
async provideInlineCompletionItems(document: vscode.TextDocument, position: vscode.Position, _: vscode.InlineCompletionContextNew, token: vscode.CancellationToken) {
const items = await completionProvider.provideCompletionItems(document, position, token, { triggerCharacter: undefined, triggerKind: vscode.CompletionTriggerKind.Invoke });
if (!items) {
return undefined;
}
const item = items.items[0];
if (!item) {
return undefined;
}
const range = item.range as vscode.Range;
if (document.getText(range) !== item.label) {
// We only want to show an inline completion if we are really sure the user meant emmet.
// If the user types `d`, we don't want to suggest `<div></div>`.
return undefined;
}
return [
{
insertText: item.insertText as any,
filterText: item.label as any,
range
}
];
}
};
const useInlineCompletionProvider = vscode.workspace.getConfiguration('emmet').get<boolean>('useInlineCompletions');
const includedLanguages = getMappingForIncludedLanguages();
Object.keys(includedLanguages).forEach(language => {
if (languageMappingForCompletionProviders.has(language) && languageMappingForCompletionProviders.get(language) === includedLanguages[language]) {
return;
@@ -178,8 +209,13 @@ function registerCompletionProviders(context: vscode.ExtensionContext) {
completionProvidersMapping.delete(language);
}
const provider = vscode.languages.registerCompletionItemProvider({ language, scheme: '*' }, completionProvider, ...LANGUAGE_MODES[includedLanguages[language]]);
context.subscriptions.push(provider);
let provider;
if (useInlineCompletionProvider) {
provider = vscode.languages.registerInlineCompletionItemProviderNew({ language, scheme: '*' }, inlineCompletionProvider);
} else {
provider = vscode.languages.registerCompletionItemProvider({ language, scheme: '*' }, completionProvider, ...LANGUAGE_MODES[includedLanguages[language]]);
}
completionProviderDisposables.push(provider);
languageMappingForCompletionProviders.set(language, includedLanguages[language]);
completionProvidersMapping.set(language, provider);
@@ -187,8 +223,13 @@ function registerCompletionProviders(context: vscode.ExtensionContext) {
Object.keys(LANGUAGE_MODES).forEach(language => {
if (!languageMappingForCompletionProviders.has(language)) {
const provider = vscode.languages.registerCompletionItemProvider({ language, scheme: '*' }, completionProvider, ...LANGUAGE_MODES[language]);
context.subscriptions.push(provider);
let provider;
if (useInlineCompletionProvider) {
provider = vscode.languages.registerInlineCompletionItemProviderNew({ language, scheme: '*' }, inlineCompletionProvider);
} else {
provider = vscode.languages.registerCompletionItemProvider({ language, scheme: '*' }, completionProvider, ...LANGUAGE_MODES[language]);
}
completionProviderDisposables.push(provider);
languageMappingForCompletionProviders.set(language, language);
completionProvidersMapping.set(language, provider);
@@ -196,7 +237,16 @@ function registerCompletionProviders(context: vscode.ExtensionContext) {
});
}
export function deactivate() {
function clearCompletionProviderInfo() {
languageMappingForCompletionProviders.clear();
completionProvidersMapping.clear();
let disposable: vscode.Disposable | undefined;
while (disposable = completionProviderDisposables.pop()) {
disposable.dispose();
}
}
export function deactivate() {
clearCompletionProviderInfo();
clearParseCache();
}

View File

@@ -9,6 +9,7 @@
],
"include": [
"src/**/*",
"../../src/vscode-dts/vscode.d.ts"
"../../src/vscode-dts/vscode.d.ts",
"../../src/vscode-dts/vscode.proposed.inlineCompletionsNew.d.ts",
]
}