in src/emmetHelper.ts [65:217]
export function doComplete(document: TextDocument, position: Position, syntax: string, emmetConfig: VSCodeEmmetConfig): CompletionList | undefined {
if (emmetConfig.showExpandedAbbreviation === 'never' || !getEmmetMode(syntax, emmetConfig.excludeLanguages)) {
return;
}
const isStyleSheetRes = isStyleSheet(syntax);
// Fetch markupSnippets so that we can provide possible abbreviation completions
// For example, when text at position is `a`, completions should return `a:blank`, `a:link`, `acr` etc.
if (!isStyleSheetRes) {
if (!snippetKeyCache.has(syntax)) {
const registry: SnippetsMap = {
...getDefaultSnippets(syntax),
...customSnippetsRegistry[syntax]
};
snippetKeyCache.set(syntax, Object.keys(registry));
}
markupSnippetKeys = snippetKeyCache.get(syntax) ?? [];
}
const extractOptions: Partial<ExtractOptions> = { lookAhead: !isStyleSheetRes, type: isStyleSheetRes ? 'stylesheet' : 'markup' };
const extractedValue = extractAbbreviation(document, position, extractOptions);
if (!extractedValue) {
return;
}
const { abbreviationRange, abbreviation, filter } = extractedValue;
const currentLineTillPosition = getCurrentLine(document, position).substr(0, position.character);
const currentWord = getCurrentWord(currentLineTillPosition);
// Don't attempt to expand open tags
if (currentWord === abbreviation
&& currentLineTillPosition.endsWith(`<${abbreviation}`)
&& syntaxes.markup.includes(syntax)) {
return;
}
const expandOptions = getExpandOptions(syntax, emmetConfig, filter);
let expandedText: string = "";
let expandedAbbr: CompletionItem | undefined;
let completionItems: CompletionItem[] = [];
// Create completion item after expanding given abbreviation
// if abbreviation is valid and expanded value is not noise
const createExpandedAbbr = (syntax: string, abbr: string) => {
if (!isAbbreviationValid(syntax, abbreviation)) {
return;
}
try {
expandedText = expand(abbr, expandOptions);
// manually patch https://github.com/microsoft/vscode/issues/120245 for now
if (isStyleSheetRes && '!important'.startsWith(abbr)) {
expandedText = '!important';
}
} catch (e) {
}
if (!expandedText || isExpandedTextNoise(syntax, abbr, expandedText, expandOptions.options)) {
return;
}
expandedAbbr = CompletionItem.create(abbr);
expandedAbbr.textEdit = TextEdit.replace(abbreviationRange, escapeNonTabStopDollar(addFinalTabStop(expandedText)));
expandedAbbr.documentation = replaceTabStopsWithCursors(expandedText);
expandedAbbr.insertTextFormat = InsertTextFormat.Snippet;
expandedAbbr.detail = localize('Emmet abbreviation', "Emmet Abbreviation");
expandedAbbr.label = abbreviation;
expandedAbbr.label += filter ? '|' + filter.replace(',', '|') : "";
completionItems = [expandedAbbr];
}
if (isStyleSheet(syntax)) {
createExpandedAbbr(syntax, abbreviation);
// When abbr is longer than usual emmet snippets and matches better with existing css property, then no emmet
if (abbreviation.length > 4
&& cssData.properties.find(x => x.startsWith(abbreviation))) {
return CompletionList.create([], true);
}
if (expandedAbbr && expandedText.length) {
expandedAbbr.textEdit = TextEdit.replace(abbreviationRange, escapeNonTabStopDollar(addFinalTabStop(expandedText)));
expandedAbbr.documentation = replaceTabStopsWithCursors(expandedText);
expandedAbbr.label = removeTabStops(expandedText);
expandedAbbr.filterText = abbreviation;
// Custom snippets should show up in completions if abbreviation is a prefix
const stylesheetCustomSnippetsKeys = stylesheetCustomSnippetsKeyCache.has(syntax) ?
stylesheetCustomSnippetsKeyCache.get(syntax) : stylesheetCustomSnippetsKeyCache.get('css');
completionItems = makeSnippetSuggestion(
stylesheetCustomSnippetsKeys ?? [],
abbreviation,
abbreviation,
abbreviationRange,
expandOptions,
'Emmet Custom Snippet',
false);
if (!completionItems.find(x => x.textEdit?.newText && x.textEdit?.newText === expandedAbbr?.textEdit?.newText)) {
// Fix for https://github.com/Microsoft/vscode/issues/28933#issuecomment-309236902
// When user types in propertyname, emmet uses it to match with snippet names, resulting in width -> widows or font-family -> font: family
// Filter out those cases here.
const abbrRegex = new RegExp('.*' + abbreviation.split('').map(x => (x === '$' || x === '+') ? '\\' + x : x).join('.*') + '.*', 'i');
if (/\d/.test(abbreviation) || abbrRegex.test(expandedAbbr.label)) {
completionItems.push(expandedAbbr);
}
}
}
} else {
createExpandedAbbr(syntax, abbreviation);
let tagToFindMoreSuggestionsFor = abbreviation;
const newTagMatches = abbreviation.match(/(>|\+)([\w:-]+)$/);
if (newTagMatches && newTagMatches.length === 3) {
tagToFindMoreSuggestionsFor = newTagMatches[2];
}
if (syntax !== 'xml') {
const commonlyUsedTagSuggestions = makeSnippetSuggestion(commonlyUsedTags, tagToFindMoreSuggestionsFor, abbreviation, abbreviationRange, expandOptions, 'Emmet Abbreviation');
completionItems = completionItems.concat(commonlyUsedTagSuggestions);
}
if (emmetConfig.showAbbreviationSuggestions === true) {
const abbreviationSuggestions = makeSnippetSuggestion(markupSnippetKeys.filter(x => !commonlyUsedTags.includes(x)), tagToFindMoreSuggestionsFor, abbreviation, abbreviationRange, expandOptions, 'Emmet Abbreviation');
// Workaround for the main expanded abbr not appearing before the snippet suggestions
if (expandedAbbr && abbreviationSuggestions.length > 0 && tagToFindMoreSuggestionsFor !== abbreviation) {
expandedAbbr.sortText = '0' + expandedAbbr.label;
abbreviationSuggestions.forEach(item => {
// Workaround for snippet suggestions items getting filtered out as the complete abbr does not start with snippetKey
item.filterText = abbreviation
// Workaround for the main expanded abbr not appearing before the snippet suggestions
item.sortText = '9' + abbreviation;
});
}
completionItems = completionItems.concat(abbreviationSuggestions);
}
// https://github.com/microsoft/vscode/issues/66680
if (syntax === 'html' && completionItems.length >= 2 && abbreviation.includes(":")
&& expandedAbbr?.textEdit?.newText === `<${abbreviation}>\${0}</${abbreviation}>`) {
completionItems = completionItems.filter(item => item.label !== abbreviation);
}
}
if (emmetConfig.showSuggestionsAsSnippets === true) {
completionItems.forEach(x => x.kind = CompletionItemKind.Snippet);
}
return completionItems.length ? CompletionList.create(completionItems, true) : undefined;
}