import * as fs from "fs" ; import * as p from "path" ; import { CompletionItem , CompletionItemKind , CompletionList , createConnection , Diagnostic , DiagnosticSeverity , DidChangeTextDocumentParams , IConnection , InitializeParams , InitializeResult , IPCMessageReader , IPCMessageWriter , Location , Position , PublishDiagnosticsParams , Range , TextDocument , TextDocumentPositionParams , TextDocuments , TextDocumentSyncKind , } from "vscode-languageserver" ; import * as vscodels from "vscode-languageserver" ; import * as xml from "xml2js" ; import { File } from "./filehandler" ; import { LogLevel } from "./Log" ; import { XmlCompletionHandler } from "./providers/XmlCompletionProvider" ; import { DiagnosticCollection , XmlAttributeDiagnosticProvider , XmlWellFormedDiagnosticProvider , } from "./providers/XmlDiagnosticProvider" ; import { XmlHoverProvider } from "./providers/XmlHoverProvider" ; import { IStorageSchema , XmlStorage } from "./xmltypes" ; const controllerFileEx = "\\.controller\\.(js|ts)$" ; if ( ! ( "toJSON" in Error . prototype ) ) Object . defineProperty ( Error . prototype , "toJSON" , { configurable : true , value : ( ) => { const alt = { } ; Object . getOwnPropertyNames ( this ) . forEach ( ( key ) => { alt [ key ] = this [ key ] ; } , this ) ; return alt ; } , writable : true , } ) ; interface interface IXmlInitOptions { type { string } memberOf storagepath : string ; } export namespace Global { /**
     * settings coming from the language client (the extension). Can be changed by the user.
     */ export let settings ; /**
     * Static settings for the server
     */ export let serverSettings ; /**
     * Instance of the schemastore, which handles the xml schemas
     */ export let schemastore ; /**
     * Root folder of the current workspace
     */ export let workspaceRoot ; } const connection = createConnection ( new IPCMessageReader ( process ) , new IPCMessageWriter ( process ) ) ; const documents = new TextDocuments ( ) ; connection . onInitialize ( ( params ) : InitializeResult => { connection . console . info ( "Initializing XML language server" ) ; Global . serverSettings = params . initializationOptions ; Global . workspaceRoot = params . rootPath ; Global . schemastore = new XmlStorage ( Global . serverSettings . storagepath , connection , LogLevel . None ) ; connection . console . info ( "Starting Listener" ) ; documents . listen ( connection ) ; return { capabilities : { codeActionProvider : false , completionProvider : { resolveProvider : false , triggerCharacters : [ "<" , ">" , '"' , "'" , "." , "/" ] , } , definitionProvider : false , hoverProvider : true , textDocumentSync : documents . syncKind , } , } ; } ) ; connection . onCompletion ( async ( params , token ) : Promise < CompletionList > => { connection . console . info ( "Completion providing request received" ) ; const cl = { isIncomplete : true , items : [ ] , } ; const doc = documents . get ( params . textDocument . uri ) ; const line = getLine ( doc . getText ( ) , params . position . line ) ; try { const ch = new XmlCompletionHandler ( Global . schemastore , doc , connection , "./schemastore" , Global . settings . ui5ts . lang . xml . LogLevel ) ; cl . items = cl . items . concat ( await ch . getCompletionSuggestions ( params ) ) ; cl . isIncomplete = false ; } catch ( error ) { connection . console . error ( "Error when getting XML completion entries: " + JSON . stringify ( error ) ) ; } return cl ; } ) ; connection . onHover ( async ( params , token ) => { connection . console . info ( "Hover request received" ) ; const doc = documents . get ( params . textDocument . uri ) ; const line = getLine ( doc . getText ( ) , params . position . line ) ; try { const ch = new XmlHoverProvider ( Global . schemastore , documents , connection , "./schemastore" , Global . settings . ui5ts . lang . xml . LogLevel ) ; return await ch . getHoverInformation ( params ) ; } catch ( error ) { connection . console . error ( "Error when getting XML completion entries: " + JSON . stringify ( error ) ) ; } return undefined ; } ) ; documents . onDidChangeContent ( async ( params ) => { const doc = documents . get ( params . document . uri ) ; if ( ! doc ) return ; const diagnostics = { uri : doc . uri , diagnostics : [ ] } ; try { const wfd = new XmlWellFormedDiagnosticProvider ( connection , Global . settings . ui5ts . lang . xml . LogLevel ) ; diagnostics . diagnostics = diagnostics . diagnostics . concat ( await wfd . diagnose ( doc ) ) ; } catch ( error ) { } try { const ad = new XmlAttributeDiagnosticProvider ( Global . schemastore , connection , Global . settings . ui5ts . lang . xml . LogLevel ) ; diagnostics . diagnostics = diagnostics . diagnostics . concat ( await ad . diagnose ( doc ) ) ; } catch ( error ) { } connection . sendDiagnostics ( diagnostics ) ; } ) ; connection . onDidChangeConfiguration ( ( change ) => { connection . console . info ( "Changed settings: " + JSON . stringify ( change ) ) ; Global . settings = change . settings as IClientSettings ; if ( ! change . settings . ui5ts . lang || ! change . settings . ui5ts . lang . xml ) { Global . settings . ui5ts . lang = { xml : { LogLevel : 4 , autoCloseEmptyElement : false , } } ; } } ) ; export function getLine ( text , linenumber ) { const lines = text . split ( /\n/ ) ; if ( linenumber > lines . length - 1 ) linenumber = lines . length - 1 ; return lines [ linenumber ] ; } export function getRange ( docText , searchPattern ) { const lineRegex = /.*(?:\n|\r\n)/gm ; let l ; const ret = [ ] ; let linectr = 0 ; while ( ( l = lineRegex . exec ( docText ) ) !== null ) { linectr = linectr + 1 ; if ( l . index === lineRegex . lastIndex ) lineRegex . lastIndex ++ ; const match = searchPattern . exec ( l ) ; if ( ! match ) continue ; ret . push ( { start : { line : linectr , character : match . index } , end : { line : linectr , character : match . index + match [ 0 ] . length } } ) ; } return ret ; } export function getPositionFromIndex ( input , index ) { const lines = input . split ( "\n" ) ; let curindex = 0 ; let lineindex = 0 ; for ( const line of lines ) { if ( index <= curindex + line . length ) { return { character : index - curindex , line : lineindex , } ; } curindex += line . length + 1 ; lineindex ++ ; } } export function getLineCount ( input ) { return input . split ( "\n" ) . length ; } interface IClientSettings { ui5ts : { lang : { xml : { autoCloseEmptyElement : boolean ; LogLevel : LogLevel ; } ; } ; } ; } function tryOpenEventHandler ( line , positionInLine , documentText ) { const rightpart = line . substr ( positionInLine ) . match ( /(\w*?)"/ ) [ 1 ] ; if ( ! rightpart ) return null ; let leftpart = line . substr ( 0 , positionInLine ) ; const leftquotepos = leftpart . match ( /.*"/ ) [ 0 ] . length ; if ( ! leftquotepos ) return ; leftpart = leftpart . substr ( leftquotepos ) ; const name = leftpart + rightpart ; const cnameri = documentText . match ( /controllerName="([\w\.]+)"/ ) ; if ( ! cnameri ) { return [ ] ; } const cname = cnameri [ 1 ] . split ( "." ) . pop ( ) ; const files = File . find ( new RegExp ( cname + controllerFileEx ) ) ; const foundControllers = [ ] ; const ret = [ ] ; for ( let i = 0 ; i < files . length ; i = i + 2 ) { const f = files . length > 1 ? files [ i + 1 ] : files [ i ] ; foundControllers . push ( { range : { start : { character : 0 , line : 0 } , end : { character : 0 , line : 0 } } , uri : "file:///" + f } ) ; } for ( const entry of foundControllers ) { const txt = File . open ( entry . uri . substr ( 8 ) ) ; const matchRegex = new RegExp ( name + /\s*?\(.*?\)/ . source , "g" ) ; const ranges = getRange ( txt , matchRegex ) ; if ( ranges . length > 0 ) for ( const r of ranges ) ret . push ( { uri : entry . uri , range : r } ) ; } return ret ; } connection . listen ( ) ;