import * as vscode from 'vscode' ; import { Node , Stylesheet } from 'EmmetNode' ; import { isValidLocationForEmmetAbbreviation , getSyntaxFromArgs } from './abbreviationActions' ; import { getEmmetHelper , getMappingForIncludedLanguages , parsePartialStylesheet , getEmmetConfiguration , getEmmetMode , isStyleSheet , parseDocument , getNode , allowedMimeTypesInScriptTag , trimQuotes } from './util' ; import { getLanguageService , TextDocument , TokenType } from 'vscode-html-languageservice' ; export class DefaultCompletionItemProvider implements vscode . CompletionItemProvider { private lastCompletionType : string | undefined ; private htmlLS = getLanguageService ( ) ; public provideCompletionItems ( document , position , _ , context ) { 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 , position , context ) { 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 syntax = getEmmetMode ( ( isSyntaxMapped ? mappedLanguages [ document . languageId ] : document . languageId ) , excludedLanguages ) ; if ( ! syntax || emmetConfig [ 'showExpandedAbbreviation' ] === 'never' || ( ( isSyntaxMapped || syntax === 'jsx' ) && emmetConfig [ 'showExpandedAbbreviation' ] !== 'always' ) ) { return ; } const helper = getEmmetHelper ( ) ; let validateLocation = syntax === 'html' || syntax === 'jsx' || syntax === 'xml' ; let rootNode = undefined ; let currentNode = null ; if ( document . languageId === '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 lsDoc = TextDocument . create ( document . uri . toString ( ) , 'html' , 0 , document . getText ( ) ) ; const parsedLsDoc = this . htmlLS . parseHTMLDocument ( lsDoc ) ; const positionOffset = document . offsetAt ( position ) ; const node = parsedLsDoc . findNodeAt ( positionOffset ) ; if ( node . tag === 'script' ) { if ( node . attributes && 'type' in node . attributes ) { const rawTypeAttrValue = node . attributes [ 'type' ] ; if ( rawTypeAttrValue ) { const typeAttrValue = trimQuotes ( rawTypeAttrValue ) ; if ( typeAttrValue === 'application/javascript' || typeAttrValue === 'text/javascript' ) { if ( ! getSyntaxFromArgs ( { language : 'javascript' } ) ) { return ; } else { validateLocation = false ; } } else if ( allowedMimeTypesInScriptTag . indexOf ( trimQuotes ( rawTypeAttrValue ) ) > - 1 ) { validateLocation = false ; } } } else { return ; } } else if ( node . tag === 'style' ) { syntax = 'css' ; validateLocation = false ; } else { if ( node . attributes && node . attributes [ 'style' ] ) { const scanner = this . htmlLS . createScanner ( document . getText ( ) , node . start ) ; let tokenType = scanner . scan ( ) ; let prevAttr = undefined ; let styleAttrValueRange = undefined ; while ( tokenType !== TokenType . EOS && ( scanner . getTokenEnd ( ) <= positionOffset ) ) { tokenType = scanner . scan ( ) ; if ( tokenType === TokenType . AttributeName ) { prevAttr = scanner . getTokenText ( ) ; } else if ( tokenType === TokenType . AttributeValue && prevAttr === 'style' ) { styleAttrValueRange = [ scanner . getTokenOffset ( ) , scanner . getTokenEnd ( ) ] ; } } if ( prevAttr === 'style' && styleAttrValueRange && positionOffset > styleAttrValueRange [ 0 ] && positionOffset < styleAttrValueRange [ 1 ] ) { syntax = 'css' ; validateLocation = false ; } } } } } const extractAbbreviationResults = helper . extractAbbreviation ( document , position , ! isStyleSheet ( syntax ) ) ; if ( ! extractAbbreviationResults || ! helper . isAbbreviationValid ( syntax , extractAbbreviationResults . abbreviation ) ) { return ; } 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 ) : < Stylesheet > parseDocument ( document , false ) ; if ( ! rootNode ) { return ; } currentNode = getNode ( rootNode , position , true ) ; } if ( validateLocation && ! isValidLocationForEmmetAbbreviation ( document , rootNode , currentNode , syntax , position , extractAbbreviationResults . abbreviationRange ) ) { return ; } let noiseCheckPromise = Promise . resolve ( ) ; if ( ! isStyleSheet ( syntax ) && ( document . languageId === 'javascript' || document . languageId === 'javascriptreact' || document . languageId === 'typescript' || document . languageId === 'typescriptreact' ) ) { let abbreviation = extractAbbreviationResults . abbreviation ; if ( abbreviation . startsWith ( 'this.' ) ) { noiseCheckPromise = Promise . resolve ( true ) ; } else { noiseCheckPromise = vscode . commands . executeCommand < vscode . SymbolInformation [ ] > ( 'vscode.executeDocumentSymbolProvider' , document . uri ) . then ( ( symbols ) => { return symbols && symbols . find ( x => abbreviation === x . name || ( abbreviation . startsWith ( x . name + '.' ) && ! />|\*|\+/ . test ( abbreviation ) ) ) ; } ) ; } } return noiseCheckPromise . then ( ( noise ) : vscode . CompletionList | undefined => { if ( noise ) { return ; } let result = helper . doComplete ( document , position , syntax , getEmmetConfiguration ( syntax ! ) ) ; let newItems = [ ] ; if ( result && result . items ) { result . items . forEach ( ( item ) => { let newItem = new vscode . CompletionItem ( item . label ) ; newItem . documentation = item . documentation ; newItem . detail = item . detail ; newItem . insertText = new vscode . SnippetString ( item . textEdit . newText ) ; let oldrange = item . textEdit . range ; newItem . range = new vscode . Range ( oldrange . start . line , oldrange . start . character , oldrange . end . line , oldrange . end . character ) ; newItem . filterText = item . filterText ; newItem . sortText = item . sortText ; if ( emmetConfig [ 'showSuggestionsAsSnippets' ] === true ) { newItem . kind = vscode . CompletionItemKind . Snippet ; } newItems . push ( newItem ) ; } ) ; } return new vscode . CompletionList ( newItems , true ) ; } ) ; } }