import { CompletionItem , CompletionItemKind , IConnection , TextDocument , TextDocumentPositionParams , TextDocuments } from "vscode-languageserver" ; import { LogLevel } from "../Log" ; import { IComplexTypeEx , IElementEx , IFoundCursor , IStorageSchema , XmlBaseHandler , XmlStorage } from "../xmltypes" ; export class XmlCompletionHandler extends XmlBaseHandler { constructor ( schemastorage , private document , connection , private schemastorePath , loglevel ) { super ( schemastorage , connection , loglevel ) ; this . schemastorage = schemastorage . schemas ; } public async getCompletionSuggestions ( handler ) { const txt = this . document . getText ( ) ; const pos = this . document . offsetAt ( handler . position ) ; this . getUsedNamespaces ( txt ) ; const foundCursor = this . textGetElementAtCursorPos ( txt , pos ) ; this . logDebug ( ( ( ) => { let ret = "Used Namespaces: " ; for ( const ns in this . usedNamespaces ) { if ( ns ) { ret += ns + " = " + this . usedNamespaces [ ns ] + " | " ; } } return ret . substring ( 0 , ret . length - 3 ) ; } ) ) ; if ( foundCursor . isOnElementHeader && ! foundCursor . isInAttribute ) { this . logDebug ( "Found cursor location to be in element" ) ; return new Promise < CompletionItem [ ] > ( ( resolve , reject ) => { resolve ( this . getElementsInTagHeader ( foundCursor ) ) ; } ) ; } else if ( ! foundCursor . isOnElementHeader ) { this . logDebug ( "Cursor location is in an element body." ) ; return new Promise < CompletionItem [ ] > ( ( resolve , reject ) => { resolve ( this . getElementsInBody ( foundCursor ) ) ; } ) ; } else if ( foundCursor . isInAttribute ) { return new Promise < CompletionItem [ ] > ( ( resolve , reject ) => { resolve ( this . getCompletionItemsForAttribute ( foundCursor ) ) ; } ) ; } } getCompletionItemsForAttribute ( cursor ) { this . logDebug ( "Processing Tagstring: " + cursor . tagName ) ; let namespace = this . usedNamespaces [ cursor . tagNamespace ] ; this . logDebug ( "Using Namespace: " + namespace ) ; let schema = this . schemastorage [ namespace ] ; this . logDebug ( "Using Schema: " + schema . targetNamespace ) ; const element = this . findElement ( cursor . tagName , schema ) ; this . logDebug ( ( ) => "Found element: " + element . $ . name ) ; const elementType = this . getTypeOf ( element ) as IComplexTypeEx ; this . logDebug ( ( ) => "Found Element type: " + elementType . $ . name ) ; const types = this . getBaseTypes ( elementType , [ ] ) ; if ( types && types . length > 0 ) elementType . basetype = types [ 0 ] ; const matchingAttributeType = elementType . attribute . find ( ( value , index , obj ) => value . $ . name === cursor . attribute . name ) ; if ( matchingAttributeType ) { const attributetype = this . getTypeOf ( matchingAttributeType ) as SimpleType ; if ( attributetype . restriction && attributetype . restriction [ 0 ] . enumeration ) { return attributetype . restriction [ 0 ] . enumeration . map < CompletionItem > ( ( value , index , array ) => { return { label : value . $ . value , documentation : value . annotation ? ( value . annotation [ 0 ] . documentation ? this . markdownText ( value . annotation [ 0 ] . documentation [ 0 ] ) : "" ) : "" , kind : CompletionItemKind . Enum } ; } ) ; } } return undefined ; } private getElementsInTagHeader ( cursor ) { this . logDebug ( "Processing Tagstring: " + cursor . tagName ) ; let namespace = this . usedNamespaces [ cursor . tagNamespace ] ; this . logDebug ( "Using Namespace: " + namespace ) ; let schema = this . schemastorage [ namespace ] ; this . logDebug ( "Using Schema: " + schema . targetNamespace ) ; let element = this . findElement ( cursor . tagName , schema ) ; this . logDebug ( ( ) => "Found element: " + element . $ . name ) ; let elementType = this . getTypeOf ( element ) as IComplexTypeEx ; this . logDebug ( ( ) => "Found Element type: " + elementType . $ . name ) ; let types = this . getBaseTypes ( elementType , [ ] ) ; if ( types && types . length > 0 ) elementType . basetype = types [ 0 ] ; let attributes = this . getAttributes ( elementType ) ; this . logDebug ( ( ) => "Found " + attributes . length + " Attributes" ) ; let ret = [ ] ; for ( const attribute of attributes ) { if ( ! ( cursor . attributes . findIndex ( x => x . name === attribute . $ . name ) > 0 ) ) ret . push ( this . getCompletionItemForSingleAttribute ( attribute , schema ) ) ; } return ret ; } private getCompletionItemForSingleAttribute ( attribute , schema ) { const ce = { label : attribute . $ . name , kind : CompletionItemKind . Property , insertText : " " + attribute . $ . name + "=\"$0\" " , insertTextFormat : 2 } ; try { ce . detail = attribute . owner ? "from " + attribute . owner . $ . name : undefined ; } catch ( error ) { } try { ce . documentation = this . markdownText ( attribute . annotation [ 0 ] . documentation [ 0 ] ) ; } catch ( error ) { } return ce ; } private getElementsInBody ( cursor ) { let foundElements = [ ] ; let baseElements = [ ] ; let path = cursor . path ; let part ; let downpath = [ ] ; let element ; element = this . findElement ( this . getElementName ( cursor . fullName ) , this . getSchema ( cursor . fullName ) ) ; if ( ! element && cursor . path . length > 0 ) { downpath . push ( cursor . fullName ) ; while ( part = path . pop ( ) ) { element = this . findElement ( this . getElementName ( part ) , this . getSchema ( part ) ) ; if ( element ) { break ; } else { downpath . push ( part ) ; } } } if ( ! element ) { this . logInfo ( "Element not found." ) ; return ; } if ( element . $ && element . $ . ref ) { element = this . getElementFromReference ( element . $ . ref , element . schema ) ; } let elements = this . getRightSubElements ( element , downpath ) ; let derivedelements = [ ] ; let ownelements = [ ] ; for ( let e of elements ) if ( e . $ && e . $ . type ) { derivedelements = derivedelements . concat ( this . getDerivedElements ( e , element . schema ) ) ; } else if ( e . $ && e . $ . ref ) { e = this . getElementFromReference ( e . $ . ref , element . schema ) ; if ( e && e . $ && e . $ . type ) derivedelements = derivedelements . concat ( this . getDerivedElements ( e , element . schema ) ) ; } else { ownelements . push ( e ) ; } for ( const ns in this . usedNamespaces ) { if ( this . usedNamespaces [ ns ] === element . schema . targetNamespace ) { foundElements . push ( { namespace : ns , elements : ownelements , ciKind : CompletionItemKind . Property } ) ; break ; } } foundElements = foundElements . concat ( derivedelements ) ; let ret = [ ] ; for ( let item of foundElements ) { for ( const entry of item . elements ) try { const citem = CompletionItem . create ( entry . $ . name ) ; const nsprefix = item . namespace . length > 0 ? item . namespace + ":" : "" ; citem . insertText = "<" + nsprefix + entry . $ . name + ">$0</" + nsprefix + entry . $ . name + ">" ; citem . insertTextFormat = 2 ; citem . kind = item . ciKind || CompletionItemKind . Class ; if ( item . namespace . length > 0 ) citem . detail = "Namespace: " + item . namespace ; try { citem . documentation = this . markdownText ( entry . annotation [ 0 ] . documentation [ 0 ] ) ; } catch ( error ) { } ret . push ( citem ) ; } catch ( error ) { this . connection . console . error ( "Item error: " + error . toString ( ) ) ; } } return ret ; } }