diff options
-rw-r--r-- | changelog.md | 21 | ||||
-rw-r--r-- | main.lua | 40 | ||||
-rw-r--r-- | script/config.lua | 1 | ||||
-rw-r--r-- | script/core/completion.lua | 218 | ||||
-rw-r--r-- | script/core/diagnostics/redundant-parameter.lua | 56 | ||||
-rw-r--r-- | script/core/diagnostics/undefined-field.lua | 17 | ||||
-rw-r--r-- | script/core/hover/table.lua | 5 | ||||
-rw-r--r-- | script/core/rename.lua | 2 | ||||
-rw-r--r-- | script/core/semantic-tokens.lua | 10 | ||||
-rw-r--r-- | script/file-uri.lua | 6 | ||||
-rw-r--r-- | script/files.lua | 128 | ||||
-rw-r--r-- | script/library.lua | 7 | ||||
-rw-r--r-- | script/parser/ast.lua | 18 | ||||
-rw-r--r-- | script/parser/guide.lua | 158 | ||||
-rw-r--r-- | script/parser/luadoc.lua | 15 | ||||
-rw-r--r-- | script/provider/capability.lua | 12 | ||||
-rw-r--r-- | script/provider/completion.lua | 2 | ||||
-rw-r--r-- | script/provider/provider.lua | 12 | ||||
-rw-r--r-- | script/provider/semantic-tokens.lua | 9 | ||||
-rw-r--r-- | script/service/telemetry.lua | 4 | ||||
-rw-r--r-- | script/vm/guideInterface.lua | 4 | ||||
-rw-r--r-- | script/workspace/workspace.lua | 80 | ||||
-rw-r--r-- | test.lua | 4 | ||||
-rw-r--r-- | test/completion/init.lua | 29 | ||||
-rw-r--r-- | test/hover/init.lua | 4 |
25 files changed, 680 insertions, 182 deletions
diff --git a/changelog.md b/changelog.md index 76681f22..6b9d4833 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,27 @@ # changelog +## 1.10.0 +* `NEW` workspace: supports `.dll`(`.so`) in `require` +* `CHG` supports `~` in command line +* `CHG` completion: improve workspace words +* `FIX` [#339](https://github.com/sumneko/lua-language-server/issues/339) + +## 1.9.0 +`2020-12-31` +* `NEW` YEAR! Peace and love! +* `NEW` specify path of `log` and `meta` by `--logpath=xxx` and `--metapath=XXX` in command line +* `NEW` completion: worksapce word +* `NEW` completion: show words in comment +* `NEW` completion: generate function documentation +* `CHG` got arg after script name: `lua-language-server.exe main.lua --logpath=D:\log --metapath=D:\meta --develop=false` +* `FIX` runtime errors + +## 1.8.2 +`2020-12-29` +* `CHG` performance optimization + ## 1.8.1 +`2020-12-24` * `FIX` telemetry: connect failed caused not working ## 1.8.0 @@ -2,17 +2,51 @@ local currentPath = debug.getinfo(1, 'S').source:sub(2) local rootPath = currentPath:gsub('[/\\]*[^/\\]-$', '') loadfile((rootPath == '' and '.' or rootPath) .. '/platform.lua')('script') local fs = require 'bee.filesystem' -ROOT = fs.path(rootPath) -LANG = LANG or 'en-US' + +local function expanduser(path) + if path:sub(1, 1) == '~' then + local home = os.getenv('HOME') + if not home then -- has to be Windows + home = os.getenv 'USERPROFILE' or (os.getenv 'HOMEDRIVE' .. os.getenv 'HOMEPATH') + end + return home .. path:sub(2) + else + return path + end +end + +local function loadArgs() + for _, v in ipairs(arg) do + local key, value = v:match '^%-%-([%w_]+)%=(.+)' + if value == 'true' then + value = true + elseif value == 'false' then + value = false + elseif tonumber(value) then + value = tonumber(value) + elseif value:sub(1, 1) == '"' and value:sub(-1, -1) == '"' then + value = value:sub(2, -2) + end + _G[key:upper()] = value + end +end + +loadArgs() + +ROOT = fs.path(expanduser(rootPath)) +LOGPATH = LOGPATH and expanduser(LOGPATH) or (ROOT:string() .. '/log') +METAPATH = METAPATH and expanduser(METAPATH) or (ROOT:string() .. '/meta') debug.setcstacklimit(200) collectgarbage('generational', 10, 100) --collectgarbage('incremental', 120, 120, 0) log = require 'log' -log.init(ROOT, ROOT / 'log' / 'service.log') +log.init(ROOT, fs.path(LOGPATH) / 'service.log') log.info('Lua Lsp startup, root: ', ROOT) log.debug('ROOT:', ROOT:string()) +log.debug('LOGPATH:', LOGPATH) +log.debug('METAPATH:', METAPATH) require 'tracy' diff --git a/script/config.lua b/script/config.lua index eecd8ffa..b6ae4767 100644 --- a/script/config.lua +++ b/script/config.lua @@ -137,6 +137,7 @@ local ConfigTemplate = { callSnippet = {'Disable', String}, keywordSnippet = {'Replace', String}, displayContext = {6, Integer}, + workspaceWord = {true, Boolean}, }, signatureHelp = { enable = {true, Boolean}, diff --git a/script/core/completion.lua b/script/core/completion.lua index b1610ff0..ba848272 100644 --- a/script/core/completion.lua +++ b/script/core/completion.lua @@ -592,7 +592,8 @@ local function checkTableField(ast, word, start, results) end) end -local function checkCommon(word, text, offset, results) +local function checkCommon(ast, word, text, offset, results) + local myUri = ast.uri local used = {} for _, result in ipairs(results) do used[result.label] = true @@ -600,14 +601,56 @@ local function checkCommon(word, text, offset, results) for _, data in ipairs(keyWordMap) do used[data[1]] = true end - for str, pos in text:gmatch '([%a_][%w_]*)()' do - if not used[str] and pos - 1 ~= offset then - used[str] = true - if matchKey(word, str) then - results[#results+1] = { - label = str, - kind = define.CompletionItemKind.Text, - } + if config.config.completion.workspaceWord then + for uri in files.eachFile() do + local cache = files.getCache(uri) + if not cache.commonWords then + cache.commonWords = {} + local mark = {} + for str in files.getText(uri):gmatch '([%a_][%w_]*)' do + if not mark[str] then + mark[str] = true + cache.commonWords[#cache.commonWords+1] = str + end + end + end + for _, str in ipairs(cache.commonWords) do + if not used[str] + and (str ~= word or not files.eq(myUri, uri)) then + used[str] = true + if matchKey(word, str) then + results[#results+1] = { + label = str, + kind = define.CompletionItemKind.Text, + } + end + end + end + end + for uri in files.eachDll() do + local words = files.getDllWords(uri) or {} + for _, str in ipairs(words) do + if not used[str] and str ~= word then + used[str] = true + if matchKey(word, str) then + results[#results+1] = { + label = str, + kind = define.CompletionItemKind.Text, + } + end + end + end + end + else + for str, pos in text:gmatch '([%a_][%w_]*)()' do + if not used[str] and pos - 1 ~= offset then + used[str] = true + if matchKey(word, str) then + results[#results+1] = { + label = str, + kind = define.CompletionItemKind.Text, + } + end end end end @@ -821,6 +864,27 @@ local function checkUri(ast, text, offset, results) end ::CONTINUE:: end + for uri in files.eachDll() do + local opens = files.getDllOpens(uri) or {} + local path = workspace.getRelativePath(uri) + for _, open in ipairs(opens) do + if matchKey(literal, open) then + if not collect[open] then + collect[open] = { + textEdit = { + start = source.start + #source[2], + finish = source.finish - #source[2], + newText = open, + } + } + end + collect[open][#collect[open]+1] = ([=[* [%s](%s)]=]):format( + path, + uri + ) + end + end + end elseif libName == 'dofile' or libName == 'loadfile' then for uri in files.eachFile() do @@ -970,6 +1034,9 @@ local function checkTypingEnum(ast, text, offset, infers, str, results) end local myResults = {} mergeEnums(myResults, enums, str) + table.sort(myResults, function (a, b) + return a.label < b.label + end) for _, res in ipairs(myResults) do results[#results+1] = res end @@ -1119,7 +1186,7 @@ local function tryWord(ast, text, offset, results) end end if not hasSpace then - checkCommon(word, text, offset, results) + checkCommon(ast, word, text, offset, results) end end end @@ -1266,6 +1333,15 @@ local function getComment(ast, offset) return nil end +local function getLuaDoc(ast, offset) + for _, doc in ipairs(ast.ast.docs) do + if offset >= doc.start and offset <= doc.range then + return doc + end + end + return nil +end + local function tryLuaDocCate(line, results) local word = line:sub(3) for _, docType in ipairs { @@ -1347,6 +1423,7 @@ local function tryLuaDocBySource(ast, offset, source, results) end end end + return true elseif source.type == 'doc.type.name' then for _, doc in ipairs(vm.getDocTypes '*') do if (doc.type == 'doc.class.name' or doc.type == 'doc.alias.name') @@ -1358,6 +1435,7 @@ local function tryLuaDocBySource(ast, offset, source, results) } end end + return true elseif source.type == 'doc.param.name' then local funcs = {} guide.eachSourceBetween(ast.ast, offset, math.huge, function (src) @@ -1380,7 +1458,9 @@ local function tryLuaDocBySource(ast, offset, source, results) } end end + return true end + return false end local function tryLuaDocByErr(ast, offset, err, docState, results) @@ -1446,40 +1526,121 @@ local function tryLuaDocByErr(ast, offset, err, docState, results) end end -local function tryLuaDocFeatures(line, ast, comm, offset, results) +local function buildLuaDocOfFunction(func) + local index = 1 + local buf = {} + buf[#buf+1] = '${1:comment}' + local args = {} + local returns = {} + if func.args then + for _, arg in ipairs(func.args) do + args[#args+1] = vm.getInferType(arg) + end + end + if func.returns then + for _, rtns in ipairs(func.returns) do + for n = 1, #rtns do + if not returns[n] then + returns[n] = vm.getInferType(rtns[n]) + end + end + end + end + for n, arg in ipairs(args) do + index = index + 1 + buf[#buf+1] = ('---@param %s ${%d:%s}'):format( + func.args[n][1], + index, + arg + ) + end + for _, rtn in ipairs(returns) do + index = index + 1 + buf[#buf+1] = ('---@return ${%d:%s}'):format( + index, + rtn + ) + end + local insertText = table.concat(buf, '\n') + return insertText end -local function tryLuaDoc(ast, text, offset, results) - local comm = getComment(ast, offset) - local line = text:sub(comm.start, offset) - if not line then +local function tryLuaDocOfFunction(doc, results) + if not doc.bindSources then return end - if line:sub(1, 2) ~= '-@' then - return + local func + for _, source in ipairs(doc.bindSources) do + if source.type == 'function' then + func = source + break + end end - -- 尝试 ---@$ - local cate = line:match('%a*', 3) - if #cate + 2 >= #line then - tryLuaDocCate(line, results) + if not func then return end - -- 尝试一些其他特征 - if tryLuaDocFeatures(line, ast, comm, offset, results) then + for _, otherDoc in ipairs(doc.bindGroup) do + if otherDoc.type == 'doc.param' + or otherDoc.type == 'doc.return' then + return + end + end + local insertText = buildLuaDocOfFunction(func) + results[#results+1] = { + label = '@param;@return', + kind = define.CompletionItemKind.Snippet, + insertTextFormat = 2, + filterText = '---', + insertText = insertText + } +end + +local function tryLuaDoc(ast, text, offset, results) + local doc = getLuaDoc(ast, offset) + if not doc then return end + if doc.type == 'doc.comment' then + local line = text:sub(doc.start, doc.range) + -- 尝试 ---$ + if line == '-' then + tryLuaDocOfFunction(doc, results) + return + end + -- 尝试 ---@$ + local cate = line:match('^-@(%a*)$') + if cate then + tryLuaDocCate(line, results) + return + end + end -- 根据输入中的source来补全 local source = getLuaDocByContain(ast, offset) if source then - tryLuaDocBySource(ast, offset, source, results) - return + local suc = tryLuaDocBySource(ast, offset, source, results) + if suc then + return + end end -- 根据附近的错误消息来补全 - local err, doc = getLuaDocByErr(ast, text, comm.start, offset) + local err, expectDoc = getLuaDocByErr(ast, text, doc.start, offset) if err then - tryLuaDocByErr(ast, offset, err, doc, results) + tryLuaDocByErr(ast, offset, err, expectDoc, results) + return + end +end + +local function tryComment(ast, text, offset, results) + local word = findWord(text, offset) + local doc = getLuaDoc(ast, offset) + local line = text:sub(doc.start, offset) + if not word then + return + end + if doc and doc.type ~= 'doc.comment' then return end + checkCommon(ast, word, text, offset, results) end local function completion(uri, offset) @@ -1490,6 +1651,7 @@ local function completion(uri, offset) if ast then if getComment(ast, offset) then tryLuaDoc(ast, text, offset, results) + tryComment(ast, text, offset, results) else trySpecial(ast, text, offset, results) tryWord(ast, text, offset, results) @@ -1500,7 +1662,7 @@ local function completion(uri, offset) else local word = findWord(text, offset) if word then - checkCommon(word, text, offset, results) + checkCommon(ast, word, text, offset, results) end end diff --git a/script/core/diagnostics/redundant-parameter.lua b/script/core/diagnostics/redundant-parameter.lua index ac52ab2a..f7b0ae75 100644 --- a/script/core/diagnostics/redundant-parameter.lua +++ b/script/core/diagnostics/redundant-parameter.lua @@ -48,12 +48,41 @@ local function countOverLoadArgs(source, doc) return result end +local function getFuncArgs(func) + local funcArgs + local defs = vm.getDefs(func) + for _, def in ipairs(defs) do + if def.value then + def = def.value + end + if def.type == 'function' then + local args = countFuncArgs(def) + if not funcArgs or args > funcArgs then + funcArgs = args + end + if def.bindDocs then + for _, doc in ipairs(def.bindDocs) do + if doc.type == 'doc.overload' then + args = countOverLoadArgs(def, doc) + if not funcArgs or args > funcArgs then + funcArgs = args + end + end + end + end + end + end + return funcArgs +end + return function (uri, callback) local ast = files.getAst(uri) if not ast then return end + local cache = vm.getCache 'redundant-parameter' + guide.eachSourceType(ast.ast, 'call', function (source) local callArgs = countCallArgs(source) if callArgs == 0 then @@ -61,27 +90,12 @@ return function (uri, callback) end local func = source.node - local funcArgs - local defs = vm.getDefs(func) - for _, def in ipairs(defs) do - if def.value then - def = def.value - end - if def.type == 'function' then - local args = countFuncArgs(def) - if not funcArgs or args > funcArgs then - funcArgs = args - end - if def.bindDocs then - for _, doc in ipairs(def.bindDocs) do - if doc.type == 'doc.overload' then - args = countOverLoadArgs(def, doc) - if not funcArgs or args > funcArgs then - funcArgs = args - end - end - end - end + local funcArgs = cache[func] + if funcArgs == nil then + funcArgs = getFuncArgs(func) or false + local refs = vm.getRefs(func, 0) + for _, ref in ipairs(refs) do + cache[ref] = funcArgs end end diff --git a/script/core/diagnostics/undefined-field.lua b/script/core/diagnostics/undefined-field.lua index 31bd9008..ffa70364 100644 --- a/script/core/diagnostics/undefined-field.lua +++ b/script/core/diagnostics/undefined-field.lua @@ -11,10 +11,19 @@ return function (uri, callback) return end + local cache = vm.getCache 'undefined-field' + local function getAllDocClassFromInfer(src) - tracy.ZoneBeginN('undefined-field getInfers') - local infers = vm.getInfers(src, 0) - tracy.ZoneEnd() + local infers = cache[src] + if cache[src] == nil then + tracy.ZoneBeginN('undefined-field getInfers') + infers = vm.getInfers(src, 0) or false + local refs = vm.getRefs(src, 0) + for _, ref in ipairs(refs) do + cache[ref] = infers + end + tracy.ZoneEnd() + end if not infers then return nil @@ -55,7 +64,9 @@ return function (uri, callback) local fields = {} local empty = true for _, docClass in ipairs(allDocClass) do + tracy.ZoneBeginN('undefined-field getDefFields') local refs = vm.getDefFields(docClass) + tracy.ZoneEnd() for _, ref in ipairs(refs) do local name = vm.getKeyName(ref) diff --git a/script/core/hover/table.lua b/script/core/hover/table.lua index 9fd79a25..58e64951 100644 --- a/script/core/hover/table.lua +++ b/script/core/hover/table.lua @@ -31,9 +31,10 @@ local function getKey(src) end local function getFieldFull(src) - local tp = vm.getInferType(src) + local value = guide.getObjectValue(src) or src + local tp = vm.getInferType(value, 0) --local class = vm.getClass(src) - local literal = vm.getInferLiteral(src) + local literal = vm.getInferLiteral(value) if type(literal) == 'string' and #literal >= 50 then literal = literal:sub(1, 47) .. '...' end diff --git a/script/core/rename.lua b/script/core/rename.lua index b823cb86..3f73c338 100644 --- a/script/core/rename.lua +++ b/script/core/rename.lua @@ -292,7 +292,7 @@ local function ofField(source, newname, callback) else node = source.node end - for _, src in ipairs(vm.getFields(node, 0)) do + for _, src in ipairs(vm.getFields(node, 5)) do ofFieldThen(key, src, newname, callback) end end diff --git a/script/core/semantic-tokens.lua b/script/core/semantic-tokens.lua index 37eabda7..1c59d80d 100644 --- a/script/core/semantic-tokens.lua +++ b/script/core/semantic-tokens.lua @@ -187,6 +187,16 @@ return function (uri, start, finish) end end) + for _, comm in ipairs(ast.comms) do + if comm.semantic then + results[#results+1] = { + start = comm.start, + finish = comm.finish, + type = define.TokenTypes.comment, + } + end + end + table.sort(results, function (a, b) return a.start < b.start end) diff --git a/script/file-uri.lua b/script/file-uri.lua index ba44f2e7..d1c495b4 100644 --- a/script/file-uri.lua +++ b/script/file-uri.lua @@ -1,5 +1,7 @@ local platform = require 'bee.platform' +---@class uri: string + local escPatt = '[^%w%-%.%_%~%/]' local function esc(c) @@ -20,7 +22,7 @@ local m = {} --- path -> uri ---@param path string ----@return string uri +---@return uri uri function m.encode(path) local authority = '' if platform.OS == 'Windows' then @@ -62,7 +64,7 @@ end -- file://server/share/some/path --> \\server\share\some\path --- uri -> path ----@param uri string +---@param uri uri ---@return string path function m.decode(uri) local scheme, authority, path = uri:match('([^:]*):?/?/?([^/]*)(.*)') diff --git a/script/files.lua b/script/files.lua index 9ffcd2a1..22ffab95 100644 --- a/script/files.lua +++ b/script/files.lua @@ -10,19 +10,20 @@ local timer = require 'timer' local m = {} -m.openMap = {} -m.libraryMap = {} -m.fileMap = {} -m.watchList = {} -m.notifyCache = {} -m.assocVersion = -1 -m.assocMatcher = nil +m.openMap = {} +m.libraryMap = {} +m.fileMap = {} +m.dllMap = {} +m.watchList = {} +m.notifyCache = {} +m.assocVersion = -1 +m.assocMatcher = nil m.globalVersion = 0 m.linesMap = setmetatable({}, { __mode = 'v' }) m.astMap = setmetatable({}, { __mode = 'v' }) --- 打开文件 ----@param uri string +---@param uri uri function m.open(uri) local originUri = uri if platform.OS == 'Windows' then @@ -33,7 +34,7 @@ function m.open(uri) end --- 关闭文件 ----@param uri string +---@param uri uri function m.close(uri) local originUri = uri if platform.OS == 'Windows' then @@ -44,7 +45,7 @@ function m.close(uri) end --- 是否打开 ----@param uri string +---@param uri uri ---@return boolean function m.isOpen(uri) if platform.OS == 'Windows' then @@ -98,7 +99,7 @@ function m.asKey(uri) end --- 设置文件文本 ----@param uri string +---@param uri uri ---@param text string function m.setText(uri, text) if not text then @@ -147,7 +148,7 @@ function m.getVersion(uri) end --- 获取文件文本 ----@param uri string +---@param uri uri ---@return string text function m.getText(uri) if platform.OS == 'Windows' then @@ -161,7 +162,7 @@ function m.getText(uri) end --- 移除文件 ----@param uri string +---@param uri uri function m.remove(uri) local originUri = uri if platform.OS == 'Windows' then @@ -218,6 +219,16 @@ function m.eachFile() return pairs(map) end +--- Pairs dll files +---@return function +function m.eachDll() + local map = {} + for uri, file in pairs(m.dllMap) do + map[uri] = file + end + return pairs(map) +end + function m.compileAst(uri, text) if not m.isOpen(uri) and #text >= config.config.workspace.preloadFileSize * 1000 then if not m.notifyCache['preloadFileSize'] then @@ -267,7 +278,7 @@ function m.compileAst(uri, text) end --- 获取文件语法树 ----@param uri string +---@param uri uri ---@return table ast function m.getAst(uri) if platform.OS == 'Windows' then @@ -290,7 +301,7 @@ function m.getAst(uri) end --- 获取文件行信息 ----@param uri string +---@param uri uri ---@return table lines function m.getLines(uri) if platform.OS == 'Windows' then @@ -313,7 +324,7 @@ function m.getOriginUri(uri) if platform.OS == 'Windows' then uri = uri:lower() end - local file = m.fileMap[uri] + local file = m.fileMap[uri] or m.dllMap[uri] if not file then return nil end @@ -369,7 +380,7 @@ function m.getAssoc() end --- 判断是否是Lua文件 ----@param uri string +---@param uri uri ---@return boolean function m.isLua(uri) local ext = uri:match '%.([^%.%/%\\]+)$' @@ -384,6 +395,89 @@ function m.isLua(uri) return matcher(path) end +--- Does the uri look like a `Dynamic link library` ? +---@param uri uri +---@return boolean +function m.isDll(uri) + local ext = uri:match '%.([^%.%/%\\]+)$' + if not ext then + return false + end + if platform.OS == 'Windows' then + if m.eq(ext, 'dll') then + return true + end + else + if m.eq(ext, 'so') then + return true + end + end + return false +end + +--- Save dll, makes opens and words, discard content +---@param uri uri +---@param content string +function m.saveDll(uri, content) + if not content then + return + end + local luri = uri + if platform.OS == 'Windows' then + luri = uri:lower() + end + local file = { + uri = uri, + opens = {}, + words = {}, + } + for word in content:gmatch 'luaopen_([%w_]+)' do + file.opens[#file.opens+1] = word:gsub('_', '.') + end + if #file.opens == 0 then + return + end + local mark = {} + for word in content:gmatch '(%a[%w_]+)\0' do + if word:sub(1, 3) ~= 'lua' then + if not mark[word] then + mark[word] = true + file.words[#file.words+1] = word + end + end + end + + m.dllMap[luri] = file +end + +--- +---@param uri uri +---@return string[]|nil +function m.getDllOpens(uri) + if platform.OS == 'Windows' then + uri = uri:lower() + end + local file = m.dllMap[uri] + if not file then + return nil + end + return file.opens +end + +--- +---@param uri uri +---@return string[]|nil +function m.getDllWords(uri) + if platform.OS == 'Windows' then + uri = uri:lower() + end + local file = m.dllMap[uri] + if not file then + return nil + end + return file.words +end + --- 注册事件 function m.watch(callback) m.watchList[#m.watchList+1] = callback diff --git a/script/library.lua b/script/library.lua index a49bef85..09b14d25 100644 --- a/script/library.lua +++ b/script/library.lua @@ -164,13 +164,10 @@ end local function compileMetaDoc() local langID = lang.id local version = config.config.runtime.version - local metapath = ROOT / 'meta' / config.config.runtime.meta:gsub('%$%{(.-)%}', { + local metapath = fs.path(METAPATH) / config.config.runtime.meta:gsub('%$%{(.-)%}', { version = version, language = langID, }) - if fs.exists(metapath) then - --return - end local metaLang = loadMetaLocale('en-US') if langID ~= 'en-US' then @@ -180,7 +177,7 @@ local function compileMetaDoc() m.metaPath = metapath:string() m.metaPaths = {} - fs.create_directory(metapath) + fs.create_directories(metapath) local templateDir = ROOT / 'meta' / 'template' for fullpath in templateDir:list_directory() do local filename = fullpath:filename() diff --git a/script/parser/ast.lua b/script/parser/ast.lua index 127bb08c..68fd59d7 100644 --- a/script/parser/ast.lua +++ b/script/parser/ast.lua @@ -333,10 +333,11 @@ local Defs = { } } end - return { - type = 'nonstandardSymbol.comment', - start = start1, - finish = finish2 - 1, + PushComment { + start = start1, + finish = finish2 - 1, + semantic = true, + text = '', } end, CCommentPrefix = function (start, finish, commentFinish) @@ -356,10 +357,11 @@ local Defs = { } } end - return { - type = 'nonstandardSymbol.comment', - start = start, - finish = commentFinish - 1, + PushComment { + start = start, + finish = commentFinish - 1, + semantic = true, + text = '', } end, String = function (start, quote, str, finish) diff --git a/script/parser/guide.lua b/script/parser/guide.lua index 7a5be591..fed3577f 100644 --- a/script/parser/guide.lua +++ b/script/parser/guide.lua @@ -27,6 +27,8 @@ local function logWarn(...) log.warn(...) end +---@class guide +---@field debugMode boolean local m = {} m.ANY = {"<ANY>"} @@ -1236,6 +1238,7 @@ function m.status(parentStatus, interface, deep) searchDeep= parentStatus and parentStatus.searchDeep or deep or -999, interface = parentStatus and parentStatus.interface or {}, deep = parentStatus and parentStatus.deep, + clock = parentStatus and parentStatus.clock or osClock(), results = {}, } if interface then @@ -1365,6 +1368,10 @@ function m.getCallValue(source) else return end + if call.node.special == 'pcall' + or call.node.special == 'xpcall' then + return call.args[1], call.args, index - 1 + end return call.node, call.args, index end @@ -1478,6 +1485,9 @@ function m.checkSameSimpleInValueOfSetMetaTable(status, func, start, pushQueue) end local call = func.parent local args = call.args + if not args then + return + end local obj = args[1] local mt = args[2] if obj then @@ -1653,6 +1663,20 @@ function m.checkSameSimpleOfRefByDocSource(status, obj, start, pushQueue, mode) end end +local function getArrayLevel(obj) + local level = 0 + while true do + local parent = obj.parent + if parent.type == 'doc.type.array' then + level = level + 1 + else + break + end + obj = parent + end + return level +end + function m.checkSameSimpleByDoc(status, obj, start, pushQueue, mode) if obj.type == 'doc.class.name' or obj.type == 'doc.class' then @@ -1705,7 +1729,7 @@ function m.checkSameSimpleByDoc(status, obj, start, pushQueue, mode) if not parentDocTypeTable then local state = m.getDocState(obj) if state.type == 'doc.type' and mode == 'ref' then - m.checkSameSimpleOfRefByDocSource(status, state, start, pushQueue, mode) + m.checkSameSimpleOfRefByDocSource(status, state, start - getArrayLevel(obj), pushQueue, mode) end end return true @@ -1833,6 +1857,7 @@ function m.searchSameFieldsCrossMethod(status, ref, start, pushQueue) end local function checkSameSimpleAndMergeFunctionReturnsByDoc(status, results, source, index, args) + source = m.getObjectValue(source) or source if not source or source.type ~= 'function' then return end @@ -1922,13 +1947,13 @@ function m.checkSameSimpleInCall(status, ref, start, pushQueue, mode) return end status.share.crossCallCount = status.share.crossCallCount or 0 - if status.share.crossCallCount >= 5 then - return - end - status.share.crossCallCount = status.share.crossCallCount + 1 -- 检查赋值是 semetatable() 的情况 m.checkSameSimpleInValueOfSetMetaTable(status, func, start, pushQueue) -- 检查赋值是 func() 的情况 + if status.share.crossCallCount >= 2 then + return + end + status.share.crossCallCount = status.share.crossCallCount + 1 local objs = m.checkSameSimpleInCallInSameFile(status, func, args, index) if status.interface.call then local cobjs = status.interface.call(func, args, index) @@ -1941,15 +1966,22 @@ function m.checkSameSimpleInCall(status, ref, start, pushQueue, mode) end end m.cleanResults(objs) - local newStatus = m.status(status) + local mark = {} for _, obj in ipairs(objs) do + if mark[obj] then + goto CONTINUE + end + local newStatus = m.status(status) m.searchRefs(newStatus, obj, mode) pushQueue(obj, start, true) + mark[obj] = true + for _, obj in ipairs(newStatus.results) do + pushQueue(obj, start, true) + mark[obj] = true + end + ::CONTINUE:: end status.share.crossCallCount = status.share.crossCallCount - 1 - for _, obj in ipairs(newStatus.results) do - pushQueue(obj, start, true) - end end local function searchRawset(ref, results) @@ -2320,6 +2352,7 @@ function m.pushResult(status, mode, ref, simple) results[#results+1] = ref elseif ref.type == 'doc.type.function' or ref.type == 'doc.class.name' + or ref.type == 'doc.alias.name' or ref.type == 'doc.field' then results[#results+1] = ref end @@ -2362,6 +2395,7 @@ function m.pushResult(status, mode, ref, simple) end elseif ref.type == 'doc.type.function' or ref.type == 'doc.class.name' + or ref.type == 'doc.alias.name' or ref.type == 'doc.field' then results[#results+1] = ref end @@ -2369,6 +2403,7 @@ function m.pushResult(status, mode, ref, simple) results[#results+1] = ref end if m.isLiteral(ref) + and ref.parent and ref.parent.type == 'callargs' and ref ~= simple.node then results[#results+1] = ref @@ -2392,18 +2427,12 @@ function m.pushResult(status, mode, ref, simple) elseif ref.type == 'setglobal' or ref.type == 'getglobal' then results[#results+1] = ref - elseif ref.type == 'function' then - results[#results+1] = ref - elseif ref.type == 'table' then - results[#results+1] = ref elseif ref.type == 'call' then if ref.node.special == 'rawset' or ref.node.special == 'rawget' then results[#results+1] = ref end - elseif ref.type == 'doc.type.function' - or ref.type == 'doc.class.name' - or ref.type == 'doc.field' then + elseif ref.type == 'doc.field' then results[#results+1] = ref end elseif mode == 'deffield' then @@ -2417,17 +2446,11 @@ function m.pushResult(status, mode, ref, simple) results[#results+1] = ref elseif ref.type == 'setglobal' then results[#results+1] = ref - elseif ref.type == 'function' then - results[#results+1] = ref - elseif ref.type == 'table' then - results[#results+1] = ref elseif ref.type == 'call' then if ref.node.special == 'rawset' then results[#results+1] = ref end - elseif ref.type == 'doc.type.function' - or ref.type == 'doc.class.name' - or ref.type == 'doc.field' then + elseif ref.type == 'doc.field' then results[#results+1] = ref end end @@ -2438,10 +2461,6 @@ function m.checkSameSimpleName(ref, sm) return true end - if sm == m.ANY_DEF and m.isSet(ref) then - return true - end - if m.getSimpleName(ref) == sm then return true end @@ -2567,6 +2586,9 @@ function m.searchSameFields(status, simple, mode) local obj = queues[queueLen] local start = starts[queueLen] local force = forces[queueLen] + queues[queueLen] = nil + starts[queueLen] = nil + forces[queueLen] = nil queueLen = queueLen - 1 local lock = locks[start] if not lock then @@ -2577,6 +2599,18 @@ function m.searchSameFields(status, simple, mode) lock[obj] = true max = max + 1 status.share.count = status.share.count + 1 + if status.share.count % 10000 == 0 then + if TEST then + print('####', status.share.count, osClock() - status.clock) + end + if status.interface and status.interface.pulse then + status.interface.pulse() + end + end + --if status.share.count >= 100000 then + -- logWarn('Count too large!') + -- break + --end m.checkSameSimple(status, simple, obj, start, force, mode, pushQueue) if max >= 10000 then logWarn('Queue too large!') @@ -2584,7 +2618,7 @@ function m.searchSameFields(status, simple, mode) end end end - deallocQueue(queues, starts, forces) + --deallocQueue(queues, starts, forces) end function m.getCallerInSameFile(status, func) @@ -2741,12 +2775,9 @@ end --end function m.getRefCache(status, obj, mode) - local cache, globalCache - if status.depth == 1 - and status.deep then - globalCache = status.interface.cache and status.interface.cache() or {} - end - cache = status.share.refCache or {} + local isDeep = status.deep and status.depth == 1 + local cache = status.share.refCache or {} + local deepCache = status.interface.cache and status.interface.cache() or {} status.share.refCache = cache if m.isGlobal(obj) then obj = m.getKeyName(obj) @@ -2754,22 +2785,42 @@ function m.getRefCache(status, obj, mode) if not cache[mode] then cache[mode] = {} end - if globalCache and not globalCache[mode] then - globalCache[mode] = {} + if not deepCache[mode] then + deepCache[mode] = {} + end + local sourceCache + if isDeep then + sourceCache = deepCache[mode][obj] + else + sourceCache = cache[mode][obj] end - local sourceCache = globalCache and globalCache[mode][obj] or cache[mode][obj] if sourceCache then return sourceCache end sourceCache = {} cache[mode][obj] = sourceCache - if globalCache then - globalCache[mode][obj] = sourceCache + if isDeep then + deepCache[mode][obj] = sourceCache end return nil, function (results) for i = 1, #results do sourceCache[i] = results[i] end + if not isDeep then + return + end + if mode == 'ref' + or mode == 'def' then + for i = 1, #results do + local res = results[i] + if not deepCache[mode][res] then + cache[mode][res] = sourceCache + if isDeep then + deepCache[mode][res] = sourceCache + end + end + end + end end end @@ -3230,6 +3281,13 @@ function m.inferCheckDoc(status, source) end return true end + if source.type == 'doc.alias.name' then + local results = m.getDocTypeNames(status, m.getDocState(source).extends) + for _, res in ipairs(results) do + status.results[#status.results+1] = res + end + return true + end end function m.getVarargDocType(status, source) @@ -3766,10 +3824,18 @@ function m.inferByDef(status, obj) for _, src in ipairs(newStatus.results) do local inferStatus = m.status(newStatus) m.searchInfer(inferStatus, src) - for _, infer in ipairs(inferStatus.results) do - if not mark[infer.source] then - mark[infer.source] = true - status.results[#status.results+1] = infer + if #inferStatus.results == 0 then + status.results[#status.results+1] = { + type = 'any', + source = src, + level = 0, + } + else + for _, infer in ipairs(inferStatus.results) do + if not mark[infer.source] then + mark[infer.source] = true + status.results[#status.results+1] = infer + end end end end @@ -3797,7 +3863,7 @@ local function inferBySetOfLocal(status, source) end end -function m.inferBySet(status, source) +function m.inferByLocalRef(status, source) if #status.results ~= 0 then return end @@ -4183,18 +4249,16 @@ function m.searchInfer(status, obj) return end + m.inferByLocalRef(status, obj) if status.deep then tracy.ZoneBeginN('inferByDef') m.inferByDef(status, obj) tracy.ZoneEnd() end - m.inferBySet(status, obj) m.inferByCall(status, obj) m.inferByGetTable(status, obj) m.inferByUnary(status, obj) m.inferByBinary(status, obj) - m.inferByCallReturn(status, obj) - m.inferByPCallReturn(status, obj) m.cleanInfers(status.results, obj) if makeCache then makeCache(status.results) diff --git a/script/parser/luadoc.lua b/script/parser/luadoc.lua index 9ea674f4..47248ba4 100644 --- a/script/parser/luadoc.lua +++ b/script/parser/luadoc.lua @@ -257,6 +257,7 @@ local function parseTypeUnitArray(node) finish = getFinish(), node = node, } + node.parent = result return result end @@ -920,6 +921,7 @@ local function buildLuaDoc(comment) type = 'doc.comment', start = comment.start, finish = comment.finish, + range = comment.finish, comment = comment, } end @@ -929,6 +931,7 @@ local function buildLuaDoc(comment) parseTokens(doc, comment.start + startPos - 1) local result = convertTokens() if result then + result.range = comment.finish local cstart = text:find('%S', result.finish - comment.start + 2) if cstart and cstart < comment.finish then result.comment = { @@ -940,7 +943,17 @@ local function buildLuaDoc(comment) end end - return result + if result then + return result + end + + return { + type = 'doc.comment', + start = comment.start, + finish = comment.finish, + range = comment.finish, + comment = comment, + } end ---当前行在注释doc前是否有代码 diff --git a/script/provider/capability.lua b/script/provider/capability.lua index e50a40f0..aa1cdb8b 100644 --- a/script/provider/capability.lua +++ b/script/provider/capability.lua @@ -5,7 +5,7 @@ local client = require 'provider.client' local m = {} local function allWords() - local str = [[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.:('"[,#*@|= ]] + local str = [[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.:('"[,#*@|=- ]] local list = {} for c in str:gmatch '.' do list[#list+1] = c @@ -32,7 +32,15 @@ function m.getIniter() documentSymbolProvider = true, workspaceSymbolProvider = true, documentHighlightProvider = true, - codeActionProvider = true, + codeActionProvider = { + codeActionKinds = { + '', + 'quickfix', + 'refactor.rewrite', + 'refactor.extract', + }, + resolveProvider = false, + }, signatureHelpProvider = { triggerCharacters = { '(', ',' }, }, diff --git a/script/provider/completion.lua b/script/provider/completion.lua index 7a18f2f8..16f2029e 100644 --- a/script/provider/completion.lua +++ b/script/provider/completion.lua @@ -3,7 +3,7 @@ local proto = require 'proto' local isEnable = false local function allWords() - local str = [[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.:('"[,#*@|= ]] + local str = [[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.:('"[,#*@|=- ]] local list = {} for c in str:gmatch '.' do list[#list+1] = c diff --git a/script/provider/provider.lua b/script/provider/provider.lua index 32778399..2d092002 100644 --- a/script/provider/provider.lua +++ b/script/provider/provider.lua @@ -35,6 +35,10 @@ local function updateConfig() } }, }) + if not configs or not configs[1] then + log.warn('No config?', util.dump(configs)) + return + end local updated = configs[1] local other = { @@ -42,11 +46,6 @@ local function updateConfig() exclude = configs[3], } - if not updated then - log.warn('No config?', util.dump(configs)) - return - end - local oldConfig = util.deepCopy(config.config) local oldOther = util.deepCopy(config.other) config.setConfig(updated, other) @@ -55,6 +54,7 @@ local function updateConfig() if not util.equal(oldConfig.runtime, newConfig.runtime) then library.init() workspace.reload() + semantic.refresh() end if not util.equal(oldConfig.diagnostics, newConfig.diagnostics) then diagnostics.diagnosticsAll() @@ -67,6 +67,7 @@ local function updateConfig() or not util.equal(oldOther.exclude, newOther.exclude) then workspace.reload() + semantic.refresh() end if not util.equal(oldConfig.intelliSense, newConfig.intelliSense) then files.flushCache() @@ -379,6 +380,7 @@ proto.on('textDocument/completion', function (params) kind = res.kind, deprecated = res.deprecated, sortText = ('%04d'):format(i), + filterText = res.filterText, insertText = res.insertText, insertTextFormat = 2, commitCharacters = res.commitCharacters, diff --git a/script/provider/semantic-tokens.lua b/script/provider/semantic-tokens.lua index 65235509..e4b2fc6f 100644 --- a/script/provider/semantic-tokens.lua +++ b/script/provider/semantic-tokens.lua @@ -1,6 +1,7 @@ local proto = require 'proto' local define = require 'proto.define' local client = require 'provider.client' +local json = require "json" local isEnable = false @@ -58,7 +59,13 @@ local function disable() }) end +local function refresh() + log.debug('Refresh semantic tokens.') + proto.notify('workspace/semanticTokens/refresh', json.null) +end + return { - enable = enable, + enable = enable, disable = disable, + refresh = refresh, } diff --git a/script/service/telemetry.lua b/script/service/telemetry.lua index 52ad193b..9fbbfe9e 100644 --- a/script/service/telemetry.lua +++ b/script/service/telemetry.lua @@ -60,7 +60,7 @@ local function pushErrorLog(link) end timer.wait(5, function () - timer.loop(60, function () + timer.loop(300, function () if not config.config.telemetry.enable then return end @@ -68,7 +68,7 @@ timer.wait(5, function () if not suc then suc, link = pcall(net.connect, 'tcp', '119.45.194.183', 11577) end - if not suc then + if not suc or not link then return end function link:on_connect() diff --git a/script/vm/guideInterface.lua b/script/vm/guideInterface.lua index 7fd515eb..b464ab98 100644 --- a/script/vm/guideInterface.lua +++ b/script/vm/guideInterface.lua @@ -105,3 +105,7 @@ end function vm.interface.getSearchDepth() return config.config.intelliSense.searchDepth end + +function vm.interface.pulse() + await.delay() +end diff --git a/script/workspace/workspace.lua b/script/workspace/workspace.lua index c493823d..57bdc00f 100644 --- a/script/workspace/workspace.lua +++ b/script/workspace/workspace.lua @@ -29,7 +29,7 @@ function m.init(uri) end m.uri = uri m.path = m.normalize(furi.decode(uri)) - local logPath = ROOT / 'log' / (uri:gsub('[/:]+', '_') .. '.log') + local logPath = fs.path(LOGPATH) / (uri:gsub('[/:]+', '_') .. '.log') log.info('Log path: ', logPath) log.init(ROOT, logPath) end @@ -66,7 +66,7 @@ function m.getNativeMatcher() if not m.path then return nil end - if m.nativeVersion == config.version then + if m.nativeMatcher then return m.nativeMatcher end @@ -128,7 +128,7 @@ end --- 创建代码库筛选器 function m.getLibraryMatchers() - if m.libraryVersion == config.version then + if m.libraryMatchers then return m.libraryMatchers end @@ -172,31 +172,48 @@ end local function loadFileFactory(root, progress, isLibrary) return function (path) local uri = furi.encode(path) - if not files.isLua(uri) then - return - end - if not isLibrary and progress.preload >= config.config.workspace.maxPreload then - if not m.hasHitMaxPreload then - m.hasHitMaxPreload = true - proto.notify('window/showMessage', { - type = 3, - message = lang.script('MWS_MAX_PRELOAD', config.config.workspace.maxPreload), - }) + if files.isLua(uri) then + if not isLibrary and progress.preload >= config.config.workspace.maxPreload then + if not m.hasHitMaxPreload then + m.hasHitMaxPreload = true + proto.notify('window/showMessage', { + type = 3, + message = lang.script('MWS_MAX_PRELOAD', config.config.workspace.maxPreload), + }) + end + return + end + if not isLibrary then + progress.preload = progress.preload + 1 end - return + progress.max = progress.max + 1 + pub.task('loadFile', uri, function (text) + progress.read = progress.read + 1 + if text then + log.info(('Preload file at: %s , size = %.3f KB'):format(uri, #text / 1000.0)) + if isLibrary then + log.info('++++As library of:', root) + files.setLibraryPath(uri, root) + end + files.setText(uri, text) + else + files.remove(uri) + end + end) end - if not isLibrary then - progress.preload = progress.preload + 1 + if files.isDll(uri) then + progress.max = progress.max + 1 + pub.task('loadFile', uri, function (content) + progress.read = progress.read + 1 + if content then + log.info(('Preload file at: %s , size = %.3f KB'):format(uri, #content / 1000.0)) + if isLibrary then + log.info('++++As library of:', root) + end + files.saveDll(uri, content) + end + end) end - progress.max = progress.max + 1 - pub.task('loadFile', uri, function (text) - progress.read = progress.read + 1 - --log.info(('Preload file at: %s , size = %.3f KB'):format(uri, #text / 1000.0)) - if isLibrary then - files.setLibraryPath(uri, root) - end - files.setText(uri, text) - end) end end @@ -239,6 +256,8 @@ end function m.awaitPreload() await.close 'preload' await.setID 'preload' + m.libraryMatchers = nil + m.nativeMatcher = nil local progress = { max = 0, read = 0, @@ -249,10 +268,12 @@ function m.awaitPreload() local native = m.getNativeMatcher() local librarys = m.getLibraryMatchers() if native then + log.info('Scan files at:', m.path) native:scan(m.path, nativeLoader) end for _, library in ipairs(librarys) do local libraryLoader = loadFileFactory(library.path, progress, true) + log.info('Scan library at:', library.path) library.matcher:scan(library.path, libraryLoader) end @@ -307,6 +328,15 @@ function m.findUrisByRequirePath(path) local results = {} local mark = {} local searchers = {} + for uri in files.eachDll() do + local opens = files.getDllOpens(uri) or {} + for _, open in ipairs(opens) do + if open == path then + results[#results+1] = uri + end + end + end + local input = path:gsub('%.', '/') :gsub('%%', '%%%%') for _, luapath in ipairs(config.config.runtime.path) do @@ -7,8 +7,10 @@ package.path = package.path .. ';' .. rootPath .. '/test/?/init.lua' local fs = require 'bee.filesystem' ROOT = fs.path(rootPath) -LANG = 'zh-CN' TEST = true +DEVELOP = true +LOGPATH = LOGPATH or (ROOT .. '/log') +METAPATH = METAPATH or (ROOT .. '/meta') collectgarbage 'generational' diff --git a/test/completion/init.lua b/test/completion/init.lua index 69ca83c3..eb696eb8 100644 --- a/test/completion/init.lua +++ b/test/completion/init.lua @@ -72,6 +72,7 @@ end config.config.completion.callSnippet = 'Both' config.config.completion.keywordSnippet = 'Both' +config.config.completion.workspaceWord = false TEST [[ local zabcde @@ -371,7 +372,7 @@ results$ (nil) TEST [[ -results$ +result$ local results ]] (EXISTS) @@ -1220,10 +1221,6 @@ z$ newText = '_ENV["z.b.c"]', }, }, - { - label = 'z', - kind = define.CompletionItemKind.Text, - } } TEST [[ @@ -1970,4 +1967,26 @@ function (${1:x}, ${2:y})\ end", }, } + +TEST [[ +---$ +local function f(a, b, c) + return a + 1, b .. '', c[1] +end +]] +{ + { + label = '@param;@return', + kind = define.CompletionItemKind.Snippet, + insertText = "\z +${1:comment}\ +---@param a ${2:number}\ +---@param b ${3:string}\ +---@param c ${4:table}\ +---@return ${5:number}\ +---@return ${6:string}\ +---@return ${7:any}", + }, +} + Cared['insertText'] = nil diff --git a/test/hover/init.lua b/test/hover/init.lua index d3d61aed..35222f32 100644 --- a/test/hover/init.lua +++ b/test/hover/init.lua @@ -878,7 +878,7 @@ end ]] [[ function a(b: table) - -> any + -> table ]] TEST [[ @@ -1264,7 +1264,7 @@ end local <?x?> = f() ]] [[ -local x: integer +local x: any ]] TEST [[ |