Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/emmet/src/defaultCompletionProvider.ts
4772 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import * as vscode from 'vscode';
7
import { Node, Stylesheet } from 'EmmetFlatNode';
8
import { isValidLocationForEmmetAbbreviation, getSyntaxFromArgs } from './abbreviationActions';
9
import { getEmmetHelper, getMappingForIncludedLanguages, parsePartialStylesheet, getEmmetConfiguration, getEmmetMode, isStyleSheet, getFlatNode, allowedMimeTypesInScriptTag, toLSTextDocument, getHtmlFlatNode, getEmbeddedCssNodeIfAny } from './util';
10
import { Range as LSRange } from 'vscode-languageserver-textdocument';
11
import { getRootNode } from './parseDocument';
12
13
export class DefaultCompletionItemProvider implements vscode.CompletionItemProvider {
14
15
private lastCompletionType: string | undefined;
16
17
public provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, _: vscode.CancellationToken, context: vscode.CompletionContext): Thenable<vscode.CompletionList | undefined> | undefined {
18
const completionResult = this.provideCompletionItemsInternal(document, position, context);
19
if (!completionResult) {
20
this.lastCompletionType = undefined;
21
return;
22
}
23
24
return completionResult.then(completionList => {
25
if (!completionList || !completionList.items.length) {
26
this.lastCompletionType = undefined;
27
return completionList;
28
}
29
const item = completionList.items[0];
30
const expandedText = item.documentation ? item.documentation.toString() : '';
31
32
if (expandedText.startsWith('<')) {
33
this.lastCompletionType = 'html';
34
} else if (expandedText.indexOf(':') > 0 && expandedText.endsWith(';')) {
35
this.lastCompletionType = 'css';
36
} else {
37
this.lastCompletionType = undefined;
38
}
39
return completionList;
40
});
41
}
42
43
private provideCompletionItemsInternal(document: vscode.TextDocument, position: vscode.Position, context: vscode.CompletionContext): Thenable<vscode.CompletionList | undefined> | undefined {
44
const emmetConfig = vscode.workspace.getConfiguration('emmet');
45
const excludedLanguages = emmetConfig['excludeLanguages'] ? emmetConfig['excludeLanguages'] : [];
46
if (excludedLanguages.includes(document.languageId)) {
47
return;
48
}
49
50
const mappedLanguages = getMappingForIncludedLanguages();
51
const isSyntaxMapped = mappedLanguages[document.languageId] ? true : false;
52
const emmetMode = getEmmetMode((isSyntaxMapped ? mappedLanguages[document.languageId] : document.languageId), mappedLanguages, excludedLanguages);
53
54
if (!emmetMode
55
|| emmetConfig['showExpandedAbbreviation'] === 'never'
56
|| ((isSyntaxMapped || emmetMode === 'jsx') && emmetConfig['showExpandedAbbreviation'] !== 'always')) {
57
return;
58
}
59
60
let syntax = emmetMode;
61
62
let validateLocation = syntax === 'html' || syntax === 'jsx' || syntax === 'xml';
63
let rootNode: Node | undefined;
64
let currentNode: Node | undefined;
65
66
const lsDoc = toLSTextDocument(document);
67
position = document.validatePosition(position);
68
69
// Don't show completions if there's a comment at the beginning of the line
70
const lineRange = new vscode.Range(position.line, 0, position.line, position.character);
71
if (document.getText(lineRange).trimStart().startsWith('//')) {
72
return;
73
}
74
75
const helper = getEmmetHelper();
76
if (syntax === 'html') {
77
if (context.triggerKind === vscode.CompletionTriggerKind.TriggerForIncompleteCompletions) {
78
switch (this.lastCompletionType) {
79
case 'html':
80
validateLocation = false;
81
break;
82
case 'css':
83
validateLocation = false;
84
syntax = 'css';
85
break;
86
default:
87
break;
88
}
89
}
90
if (validateLocation) {
91
const positionOffset = document.offsetAt(position);
92
const emmetRootNode = getRootNode(document, true);
93
const foundNode = getHtmlFlatNode(document.getText(), emmetRootNode, positionOffset, false);
94
if (foundNode) {
95
if (foundNode.name === 'script') {
96
const typeNode = foundNode.attributes.find(attr => attr.name.toString() === 'type');
97
if (typeNode) {
98
const typeAttrValue = typeNode.value.toString();
99
if (typeAttrValue === 'application/javascript' || typeAttrValue === 'text/javascript') {
100
if (!getSyntaxFromArgs({ language: 'javascript' })) {
101
return;
102
} else {
103
validateLocation = false;
104
}
105
}
106
else if (allowedMimeTypesInScriptTag.includes(typeAttrValue)) {
107
validateLocation = false;
108
}
109
} else {
110
return;
111
}
112
}
113
else if (foundNode.name === 'style') {
114
syntax = 'css';
115
validateLocation = false;
116
} else {
117
const styleNode = foundNode.attributes.find(attr => attr.name.toString() === 'style');
118
if (styleNode && styleNode.value.start <= positionOffset && positionOffset <= styleNode.value.end) {
119
syntax = 'css';
120
validateLocation = false;
121
}
122
}
123
}
124
}
125
}
126
127
const expandOptions = isStyleSheet(syntax) ?
128
{ lookAhead: false, syntax: 'stylesheet' } :
129
{ lookAhead: true, syntax: 'markup' };
130
const extractAbbreviationResults = helper.extractAbbreviation(lsDoc, position, expandOptions);
131
if (!extractAbbreviationResults || !helper.isAbbreviationValid(syntax, extractAbbreviationResults.abbreviation)) {
132
return;
133
}
134
135
const offset = document.offsetAt(position);
136
if (isStyleSheet(document.languageId) && context.triggerKind !== vscode.CompletionTriggerKind.TriggerForIncompleteCompletions) {
137
validateLocation = true;
138
const usePartialParsing = vscode.workspace.getConfiguration('emmet')['optimizeStylesheetParsing'] === true;
139
rootNode = usePartialParsing && document.lineCount > 1000 ? parsePartialStylesheet(document, position) : <Stylesheet>getRootNode(document, true);
140
if (!rootNode) {
141
return;
142
}
143
currentNode = getFlatNode(rootNode, offset, true);
144
}
145
146
// Fix for https://github.com/microsoft/vscode/issues/107578
147
// Validate location if syntax is of styleSheet type to ensure that location is valid for emmet abbreviation.
148
// For an html document containing a <style> node, compute the embeddedCssNode and fetch the flattened node as currentNode.
149
if (!isStyleSheet(document.languageId) && isStyleSheet(syntax) && context.triggerKind !== vscode.CompletionTriggerKind.TriggerForIncompleteCompletions) {
150
validateLocation = true;
151
rootNode = getRootNode(document, true);
152
if (!rootNode) {
153
return;
154
}
155
const flatNode = getFlatNode(rootNode, offset, true);
156
const embeddedCssNode = getEmbeddedCssNodeIfAny(document, flatNode, position);
157
currentNode = getFlatNode(embeddedCssNode, offset, true);
158
}
159
160
if (validateLocation && !isValidLocationForEmmetAbbreviation(document, rootNode, currentNode, syntax, offset, toRange(extractAbbreviationResults.abbreviationRange))) {
161
return;
162
}
163
164
let isNoisePromise: Thenable<boolean> = Promise.resolve(false);
165
166
// Fix for https://github.com/microsoft/vscode/issues/32647
167
// Check for document symbols in js/ts/jsx/tsx and avoid triggering emmet for abbreviations of the form symbolName.sometext
168
// Presence of > or * or + in the abbreviation denotes valid abbreviation that should trigger emmet
169
if (!isStyleSheet(syntax) && (document.languageId === 'javascript' || document.languageId === 'javascriptreact' || document.languageId === 'typescript' || document.languageId === 'typescriptreact')) {
170
const abbreviation: string = extractAbbreviationResults.abbreviation;
171
// For the second condition, we don't want abbreviations that have [] characters but not ='s in them to expand
172
// In turn, users must explicitly expand abbreviations of the form Component[attr1 attr2], but it means we don't try to expand a[i].
173
if (abbreviation.startsWith('this.') || /\[[^\]=]*\]/.test(abbreviation)) {
174
isNoisePromise = Promise.resolve(true);
175
} else {
176
isNoisePromise = vscode.commands.executeCommand<vscode.SymbolInformation[] | undefined>('vscode.executeDocumentSymbolProvider', document.uri).then(symbols => {
177
return !!symbols && symbols.some(x => abbreviation === x.name || (abbreviation.startsWith(x.name + '.') && !/>|\*|\+/.test(abbreviation)));
178
});
179
}
180
}
181
182
return isNoisePromise.then((isNoise): vscode.CompletionList | undefined => {
183
if (isNoise) {
184
return undefined;
185
}
186
187
const config = getEmmetConfiguration(syntax!);
188
const result = helper.doComplete(toLSTextDocument(document), position, syntax, config);
189
190
const newItems: vscode.CompletionItem[] = [];
191
if (result && result.items) {
192
result.items.forEach((item: any) => {
193
const newItem = new vscode.CompletionItem(item.label);
194
newItem.documentation = item.documentation;
195
newItem.detail = item.detail;
196
newItem.insertText = new vscode.SnippetString(item.textEdit.newText);
197
const oldrange = item.textEdit.range;
198
newItem.range = new vscode.Range(oldrange.start.line, oldrange.start.character, oldrange.end.line, oldrange.end.character);
199
200
newItem.filterText = item.filterText;
201
newItem.sortText = item.sortText;
202
203
if (emmetConfig['showSuggestionsAsSnippets'] === true) {
204
newItem.kind = vscode.CompletionItemKind.Snippet;
205
}
206
newItems.push(newItem);
207
});
208
}
209
210
return new vscode.CompletionList(newItems, true);
211
});
212
}
213
}
214
215
function toRange(lsRange: LSRange) {
216
return new vscode.Range(lsRange.start.line, lsRange.start.character, lsRange.end.line, lsRange.end.character);
217
}
218
219