/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; import { Node, Stylesheet } from 'EmmetFlatNode'; import { isValidLocationForEmmetAbbreviation, getSyntaxFromArgs } from './abbreviationActions'; import { getEmmetHelper, getMappingForIncludedLanguages, parsePartialStylesheet, getEmmetConfiguration, getEmmetMode, isStyleSheet, getFlatNode, allowedMimeTypesInScriptTag, toLSTextDocument, getHtmlFlatNode, getEmbeddedCssNodeIfAny } from './util'; import { Range as LSRange } from 'vscode-languageserver-textdocument'; import { getRootNode } from './parseDocument'; export class DefaultCompletionItemProvider implements vscode.CompletionItemProvider { private lastCompletionType: string | undefined; public provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, _: vscode.CancellationToken, context: vscode.CompletionContext): Thenable | undefined { const completionResult = this.provideCompletionItemsInternal(document, position, context); if (!completionResult) { this.lastCompletionType = undefined; return; } return completionResult.then(completionList => { if (!completionList || !completionList.items.length) { this.lastCompletionType = undefined; return completionList; } const item = completionList.items[0]; const expandedText = item.documentation ? item.documentation.toString() : ''; if (expandedText.startsWith('<')) { this.lastCompletionType = 'html'; } else if (expandedText.indexOf(':') > 0 && expandedText.endsWith(';')) { this.lastCompletionType = 'css'; } else { this.lastCompletionType = undefined; } return completionList; }); } private provideCompletionItemsInternal(document: vscode.TextDocument, position: vscode.Position, context: vscode.CompletionContext): Thenable | undefined { const emmetConfig = vscode.workspace.getConfiguration('emmet'); const excludedLanguages = emmetConfig['excludeLanguages'] ? emmetConfig['excludeLanguages'] : []; if (excludedLanguages.indexOf(document.languageId) > -1) { return; } const mappedLanguages = getMappingForIncludedLanguages(); const isSyntaxMapped = mappedLanguages[document.languageId] ? true : false; let emmetMode = getEmmetMode((isSyntaxMapped ? mappedLanguages[document.languageId] : document.languageId), excludedLanguages); if (!emmetMode || emmetConfig['showExpandedAbbreviation'] === 'never' || ((isSyntaxMapped || emmetMode === 'jsx') && emmetConfig['showExpandedAbbreviation'] !== 'always')) { return; } let syntax = emmetMode; const helper = getEmmetHelper(); let validateLocation = syntax === 'html' || syntax === 'jsx' || syntax === 'xml'; let rootNode: Node | undefined; let currentNode: Node | undefined; const lsDoc = toLSTextDocument(document); position = document.validatePosition(position); if (syntax === 'html') { if (context.triggerKind === vscode.CompletionTriggerKind.TriggerForIncompleteCompletions) { switch (this.lastCompletionType) { case 'html': validateLocation = false; break; case 'css': validateLocation = false; syntax = 'css'; break; default: break; } } if (validateLocation) { const positionOffset = document.offsetAt(position); const emmetRootNode = getRootNode(document, true); const foundNode = getHtmlFlatNode(document.getText(), emmetRootNode, positionOffset, false); if (foundNode) { if (foundNode.name === 'script') { const typeNode = foundNode.attributes.find(attr => attr.name.toString() === 'type'); if (typeNode) { const typeAttrValue = typeNode.value.toString(); if (typeAttrValue === 'application/javascript' || typeAttrValue === 'text/javascript') { if (!getSyntaxFromArgs({ language: 'javascript' })) { return; } else { validateLocation = false; } } else if (allowedMimeTypesInScriptTag.includes(typeAttrValue)) { validateLocation = false; } } else { return; } } else if (foundNode.name === 'style') { syntax = 'css'; validateLocation = false; } else { const styleNode = foundNode.attributes.find(attr => attr.name.toString() === 'style'); if (styleNode && styleNode.value.start <= positionOffset && positionOffset <= styleNode.value.end) { syntax = 'css'; validateLocation = false; } } } } } const expandOptions = isStyleSheet(syntax) ? { lookAhead: false, syntax: 'stylesheet' } : { lookAhead: true, syntax: 'markup' }; const extractAbbreviationResults = helper.extractAbbreviation(lsDoc, position, expandOptions); if (!extractAbbreviationResults || !helper.isAbbreviationValid(syntax, extractAbbreviationResults.abbreviation)) { return; } const offset = document.offsetAt(position); if (isStyleSheet(document.languageId) && context.triggerKind !== vscode.CompletionTriggerKind.TriggerForIncompleteCompletions) { validateLocation = true; let usePartialParsing = vscode.workspace.getConfiguration('emmet')['optimizeStylesheetParsing'] === true; rootNode = usePartialParsing && document.lineCount > 1000 ? parsePartialStylesheet(document, position) : getRootNode(document, true); if (!rootNode) { return; } currentNode = getFlatNode(rootNode, offset, true); } // Fix for https://github.com/microsoft/vscode/issues/107578 // Validate location if syntax is of styleSheet type to ensure that location is valid for emmet abbreviation. // For an html document containing a