import * as fs from 'fs' ; import * as luaparse from "luaparse" ; import * as rd from 'rd' ; import { URL } from 'url' ; import { CompletionItem , CompletionItemKind , InsertTextFormat , Definition , Location } from "vscode-languageserver" ; import URI from 'vscode-uri' ; import { parseDoc } from './AstParser' ; import { Config } from './Config' ; import { DocItem , FileDoc } from './types' ; import { ServerProxy } from './Proxy' ; import { setLineEnding } from 'crlf-helper' ; interface ParseOption { comments : boolean ; scope : boolean ; locations : boolean ; ranges : boolean ; luaVersion : string ; } /**
 * 生成自动列表
 */ class Builder0 { private _option : ParseOption ; private _cppList : DocItem [ ] ; private _keyList : CompletionItem [ ] ; private _fileList : FileDoc [ ] ; private _lastText : string = "" ; private _inited : boolean = false ; private _total : number ; private _count : number ; constructor ( ) { this . _option = < ParseOption > { } ; this . _option . comments = false ; this . _option . scope = true ; this . _option . locations = true ; this . _option . ranges = false ; this . _option . luaVersion = '5.1' ; this . _cppList = [ ] ; this . _fileList = [ ] ; this . _keyList = [ ] ; this . initKeys ( ) ; } /**
     * 关键字
     */ private initKeys ( ) { var strs = [ "and" , "break" , "if" , "else" , "elseif" , "end" , "false" , "function" , "local" , "in" , "nil" , "not" , "or" , "return" , "then" , "true" , "while" ] ; for ( let i = 0 ; i < strs . length ; i ++ ) { const str = strs [ i ] ; let item = < CompletionItem > { } ; item . label = str ; item . kind = CompletionItemKind . Keyword ; this . _keyList . push ( item ) ; } } /**
     * 初始化整个工作区，解析所有的lua代码文件
     */ public initWorkspace ( ) { let statusText = "初始化 Quickx 项目..." ; if ( ! Config . isQuickX ) { statusText = "初始化 lua 项目..." ; } ServerProxy . showStatusBar ( "$(zap)" , "#00ff00" ) ; ServerProxy . showStatusBar ( statusText ) ; setTimeout ( ( ) => { this . initWorkspace2 ( ) ; } , 150 ) ; } private initWorkspace2 ( ) { if ( Config . isQuickX ) { this . initCpp ( ) ; } this . initLuaSystem ( ) ; let rootPath = URI . parse ( Config . workspaceRoot ) . fsPath ; rd . readFileFilter ( rootPath , /\.lua$/ , ( err , paths ) => { this . _total = paths . length ; this . _count = 0 ; for ( let i = 0 ; i < paths . length ; i ++ ) { const p = paths [ i ] ; fs . readFile ( p , "utf-8" , ( err1 , data ) => { this . _count ++ ; let fileuri = URI . file ( p ) . toString ( ) ; this . parse ( fileuri , data ) ; if ( this . _count >= this . _total ) { this . _inited = true ; ServerProxy . hideStatusBar ( ) ; } } ) ; } } ) ; } /**
     * 初始化lua系统变量及函数
     */ private initLuaSystem ( ) { let path = Config . root + "/files/lua5_1.lua" ; path = URI . parse ( path ) . fsPath ; fs . readFile ( path , "utf-8" , ( err1 , data ) => { let fileuri = URI . file ( path ) . toString ( ) ; this . parse ( fileuri , data ) ; for ( let i = 0 ; i < this . _fileList . length ; i ++ ) { const file = this . _fileList [ i ] ; if ( file . uri == fileuri ) { for ( let j = 0 ; j < file . globals . length ; j ++ ) { const docItem = file . globals [ j ] ; if ( docItem . items && docItem . items . length > 0 ) { docItem . completion . kind = CompletionItemKind . Module ; } } } } } ) ; } private initedTest ( ) { for ( let i = 0 ; i < this . _fileList . length ; i ++ ) { const fileDoc = this . _fileList [ i ] ; for ( let j = 0 ; j < fileDoc . globals . length ; j ++ ) { const item = fileDoc . globals [ j ] ; if ( item . name == "CONFIG_SCREEN_HEIGHT" ) { console . log ( item . virtual , fileDoc . uri , item . loc ) ; } } } } /**
     * 建立cpp列表
     */ private initCpp ( ) { this . _cppList = [ ] ; let cppPath = Config . root + "/files/cpp.json" ; let data = fs . readFileSync ( new URL ( cppPath ) , "utf-8" ) ; let jsonData = JSON . parse ( data ) ; for ( let i = 0 ; i < jsonData . length ; i ++ ) { const moduleItem = jsonData [ i ] ; let loc = this . getCppLoc ( moduleItem [ "loc" ] ) ; let moduleDocItem = this . createVarDocItem ( moduleItem [ "name" ] , null , true , loc ) ; moduleDocItem . items = [ ] ; this . _cppList . push ( moduleDocItem ) ; let classes = moduleItem [ "classes" ] ; if ( classes && classes . length > 0 ) { for ( let j = 0 ; j < classes . length ; j ++ ) { const classItem = classes [ j ] ; let loc = this . getCppLoc ( classItem [ "loc" ] ) ; let classDocItem = this . createVarDocItem ( classItem [ "name" ] , moduleDocItem . name , false , loc ) ; classDocItem . items = [ ] ; moduleDocItem . items . push ( classDocItem ) ; let funcs = classItem [ "functions" ] ; if ( funcs && funcs . length > 0 ) { for ( let k = 0 ; k < funcs . length ; k ++ ) { const funcItem = funcs [ k ] ; let loc = this . getCppLoc ( funcItem [ "loc" ] ) ; let funcDocItem = this . createFuncDocItem ( funcItem [ "name" ] , classDocItem . name , funcItem [ "params" ] , loc ) ; classDocItem . items . push ( funcDocItem ) ; } } } } } } private createVarDocItem ( name , base , isModule , loc ) { var docItem = < DocItem > { } ; docItem . name = name ; let completion = < CompletionItem > { } ; completion . label = name ; if ( isModule ) { completion . kind = CompletionItemKind . Module ; } else { completion . kind = CompletionItemKind . Variable ; } var detail = name ; if ( base ) { detail = "(" + base + ") " + detail ; } completion . detail = detail ; docItem . completion = completion ; docItem . loc = loc ; return docItem ; } private createFuncDocItem ( name , base , params , loc ) { let params2 = "" ; let params3 = "" ; for ( let i = 0 ; i < params . length ; i ++ ) { const p = params [ i ] ; if ( i != 0 ) { params2 += ", " ; params3 += ", " ; } params2 += "${" + ( i + 1 ) + ":" + p + "}" ; params3 += p ; } let completion = < CompletionItem > { } ; completion . label = name ; completion . kind = CompletionItemKind . Function ; completion . insertTextFormat = InsertTextFormat . Snippet ; completion . insertText = name + "(" + params2 + ")" ; let detail = name + "(" + params3 + ")" ; if ( base ) { detail = "(" + base + ") " + detail ; } completion . detail = detail ; let funcDocItem = < DocItem > { } ; funcDocItem . name = name ; funcDocItem . loc = loc ; funcDocItem . completion = completion ; return funcDocItem ; } param data private getCppLoc ( data ) { let loc = [ ] ; if ( data && Config . quickRoot ) { let root = Config . quickRoot ; if ( ! root . endsWith ( "/" ) ) { root += "/" ; } loc = [ data [ "line" ] , data [ "char" ] , root + data [ "src" ] ] ; } return loc ; } param uri param data private parse ( uri , data ) { if ( ! data ) { return ; } data = setLineEnding ( data , "CRLF" ) ; try { let ast = luaparse . parse ( data , this . _option ) ; let file = parseDoc ( ast ) ; file . uri = uri ; for ( let i = 0 ; i < this . _fileList . length ; i ++ ) { const f = this . _fileList [ i ] ; if ( f . uri == uri ) { this . _fileList . splice ( i , 1 ) ; break ; } } this . _fileList . push ( file ) ; } catch ( error ) { } } /**
     * 解析代码内容，一般是内容改变时
     */ public parseCode ( uri , code ) { if ( this . _inited ) { this . _lastText = code ; this . parse ( uri , code ) ; } } param lineData param character public handleDefinition ( uri , lineData , character ) { let prevContent = lineData . substring ( 0 , character ) ; let afterContent = lineData . substring ( character ) ; let arr = prevContent . split ( /[^\w+|\.|\:]/ ) ; let prev = arr [ arr . length - 1 ] ; if ( prev . startsWith ( "." ) || prev . startsWith ( ":" ) ) { prev = prev . substr ( 1 ) ; } arr = afterContent . split ( /[^\w+]/ ) ; let after = arr [ 0 ] ; let str = prev + after ; let paths = str . split ( /\.|\:/ ) ; if ( paths . length == 0 ) { return [ ] ; } let locations = [ ] ; this . findLocation ( paths , this . _cppList , null , locations ) ; for ( let i = 0 ; i < this . _fileList . length ; i ++ ) { const file = this . _fileList [ i ] ; if ( file . uri == uri ) { this . findLocation ( paths , file . locals , file . uri , locations ) ; } this . findLocation ( paths , file . globals , file . uri , locations ) ; } if ( locations . length == 0 ) { let name = paths [ paths . length - 1 ] ; this . addAllDefinition ( name , this . _cppList , null , locations ) ; for ( let i = 0 ; i < this . _fileList . length ; i ++ ) { const file = this . _fileList [ i ] ; if ( file . uri == uri ) { this . addAllDefinition ( name , file . locals , file . uri , locations ) ; } this . addAllDefinition ( name , file . globals , file . uri , locations ) ; } } return locations ; } param paths param list param locations private findLocation ( paths , list , fileUri , locations ) { let item = this . findDocItem ( paths , list ) ; if ( item ) { this . add2Definition ( item , fileUri , locations ) ; } } param name param fileUri param locations private addAllDefinition ( name , list , fileUri , locations ) { for ( let i = 0 ; i < list . length ; i ++ ) { const item1 = list [ i ] ; if ( item1 . name == name ) { this . add2Definition ( item1 , fileUri , locations ) ; } if ( item1 . items && item1 . items . length > 0 ) { this . addAllDefinition ( name , item1 . items , fileUri , locations ) ; } } } param item param fileUri param locations private add2Definition ( item , fileUri , locations ) { if ( ! item . loc ) { return ; } if ( ! fileUri && item . loc . length == 3 ) { fileUri = item . loc [ 2 ] ; } if ( ! fileUri || item . loc . length < 2 ) { return ; } let line = item . loc [ 0 ] - 1 ; let character = item . loc [ 1 ] ; let location = Location . create ( fileUri , { start : { line : line , character : character } , end : { line : line , character : character } } ) ; locations . push ( location ) ; } param uri param prevContent public handleComplete ( uri , prevContent ) { prevContent . trim ( ) ; let arr = prevContent . split ( /[^\w+|\.|\:]/ ) ; prevContent = arr [ arr . length - 1 ] ; let index = prevContent . search ( /\w+/ ) ; if ( index >= 0 ) { return this . handleCompleteText ( uri , prevContent ) ; } return [ ] ; } param uri param str private handleCompleteText ( uri , str ) { let completions = [ ] ; let paths = str . split ( /\.|\:/ ) ; paths . pop ( ) ; this . findCompletions ( paths , this . _cppList , completions ) ; let curFile ; for ( let i = 0 ; i < this . _fileList . length ; i ++ ) { const f = this . _fileList [ i ] ; this . findCompletions ( paths , f . globals , completions ) ; if ( f . uri == uri ) { curFile = f ; } } if ( completions . length == 0 ) { this . pickAllGlobal ( completions ) ; if ( curFile ) { this . pickItems ( curFile . locals , completions , true ) ; } } else if ( curFile ) { this . findCompletions ( paths , curFile . locals , completions ) ; } if ( paths . length == 0 ) { completions = completions . concat ( this . _keyList ) ; } return completions ; } param completions private pickAllGlobal ( completions ) { this . pickItems ( this . _cppList , completions , true ) ; for ( let i = 0 ; i < this . _fileList . length ; i ++ ) { const f = this . _fileList [ i ] ; this . pickItems ( f . globals , completions , true ) ; } } param paths param list param completions private findCompletions ( paths , list , completions ) { if ( ! paths || paths . length == 0 ) { this . pickItems ( list , completions , false ) ; return ; } let lastItem ; for ( let i = 0 ; i < paths . length ; i ++ ) { const p = paths [ i ] ; let has = false ; for ( let j = 0 ; j < list . length ; j ++ ) { let item = list [ j ] ; if ( item . name == p ) { if ( item . refer ) { item = item . refer ; } if ( item . items && item . items . length > 0 ) { list = item . items ; lastItem = item ; has = true ; break ; } else { return ; } } } if ( ! has ) { return ; } } if ( lastItem ) { if ( lastItem . refer ) { lastItem = lastItem . refer ; } if ( lastItem . items && lastItem . items . length > 0 ) { this . pickItems ( lastItem . items , completions , false ) ; } } } param paths param list private findDocItem ( paths , list ) { if ( ! paths || paths . length == 0 ) { return null ; } let findList = list ; for ( let i = 0 ; i < paths . length ; i ++ ) { const p = paths [ i ] ; let curItem ; for ( let j = 0 ; j < findList . length ; j ++ ) { let item = findList [ j ] ; if ( item . name == p ) { curItem = item ; break ; } } if ( ! curItem ) { return null ; } if ( i == paths . length - 1 ) { return curItem ; } if ( curItem . refer ) { curItem = curItem . refer ; } if ( curItem . items && curItem . items . length > 0 ) { findList = curItem . items ; } else { return null ; } } return null ; } param items param completions param pickChildren private pickItems ( items , completions , pickChildren ) { for ( let i = 0 ; i < items . length ; i ++ ) { this . pickItem ( items [ i ] , completions , pickChildren ) ; } } param item param completions param pickChildren private pickItem ( item , completions , pickChildren ) { if ( ! item . virtual ) { completions . push ( item . completion ) ; } if ( item . refer ) { return ; } if ( pickChildren && item . items && item . items . length > 0 ) { this . pickItems ( item . items , completions , true ) ; } } } /**
 * 解析器，导出单例
 */ export const Builder = new Builder0 ( ) ;