import { getLanguageService , HTMLDocument , TokenType , ScannerState , Scanner , Node , Position } from 'vscode-html-languageservice' ; import { Document } from './Document' ; import { isInsideMoustacheTag } from './utils' ; const parser = getLanguageService ( ) ; /**
 * Parses text as HTML
 */ export function parseHtml ( text ) { const preprocessed = preprocess ( text ) ; const parsedDoc = parser . parseHTMLDocument ( < any > { getText : ( ) => preprocessed } ) ; return parsedDoc ; } const createScanner = parser . createScanner as ( input : string , initialOffset ? : number , initialState ? : ScannerState ) => Scanner ; /**
 * scan the text and remove any `>` or `<` that cause the tag to end short,
 */ function preprocess ( text ) { let scanner = createScanner ( text ) ; let token = scanner . scan ( ) ; let currentStartTagStart = null ; while ( token !== TokenType . EOS ) { const offset = scanner . getTokenOffset ( ) ; if ( token === TokenType . StartTagOpen ) { if ( shouldBlankStartOrEndTagLike ( offset ) ) { blankStartOrEndTagLike ( offset ) ; } else { currentStartTagStart = offset ; } } if ( token === TokenType . StartTagClose ) { if ( shouldBlankStartOrEndTagLike ( offset ) ) { blankStartOrEndTagLike ( offset ) ; } else { currentStartTagStart = null ; } } if ( token === TokenType . StartTagSelfClose ) { currentStartTagStart = null ; } if ( token === TokenType . Unknown && scanner . getScannerState ( ) === ScannerState . WithinTag && scanner . getTokenText ( ) === '<' && shouldBlankStartOrEndTagLike ( offset ) ) { blankStartOrEndTagLike ( offset ) ; } token = scanner . scan ( ) ; } return text ; function shouldBlankStartOrEndTagLike ( offset ) { return isInsideMoustacheTag ( text , currentStartTagStart , offset ) ; } function blankStartOrEndTagLike ( offset ) { text = text . substring ( 0 , offset ) + ' ' + text . substring ( offset + 1 ) ; scanner = createScanner ( text , offset , ScannerState . WithinTag ) ; } } export interface AttributeContext { name : string ; inValue : boolean ; elementTag : Node ; valueRange ? : [ number , number ] ; } export function getAttributeContextAtPosition ( document , position ) { const offset = document . offsetAt ( position ) ; const { html } = document ; const tag = html . findNodeAt ( offset ) ; if ( ! inStartTag ( offset , tag ) || ! tag . attributes ) { return null ; } const text = document . getText ( ) ; const beforeStartTagEnd = text . substring ( 0 , tag . start ) + preprocess ( text . substring ( tag . start , tag . startTagEnd ) ) ; const scanner = createScanner ( beforeStartTagEnd , tag . start ) ; let token = scanner . scan ( ) ; let currentAttributeName ; const inTokenRange = ( ) => scanner . getTokenOffset ( ) <= offset && offset <= scanner . getTokenEnd ( ) ; while ( token != TokenType . EOS ) { if ( token === TokenType . AttributeName ) { currentAttributeName = scanner . getTokenText ( ) ; if ( inTokenRange ( ) ) { return { elementTag : tag , name : currentAttributeName , inValue : false } ; } } else if ( token === TokenType . DelimiterAssign ) { if ( scanner . getTokenEnd ( ) === offset && currentAttributeName ) { const nextToken = scanner . scan ( ) ; return { elementTag : tag , name : currentAttributeName , inValue : true , valueRange : [ offset , nextToken === TokenType . AttributeValue ? scanner . getTokenEnd ( ) : offset ] } ; } } else if ( token === TokenType . AttributeValue ) { if ( inTokenRange ( ) && currentAttributeName ) { let start = scanner . getTokenOffset ( ) ; let end = scanner . getTokenEnd ( ) ; const char = text [ start ] ; if ( char === '"' || char === "'" ) { start ++ ; end -- ; } return { elementTag : tag , name : currentAttributeName , inValue : true , valueRange : [ start , end ] } ; } currentAttributeName = undefined ; } token = scanner . scan ( ) ; } return null ; } function inStartTag ( offset , node ) { return offset > node . start && node . startTagEnd != undefined && offset < node . startTagEnd ; }