diff options
-rw-r--r-- | script/capability/semantic.lua | 28 | ||||
-rw-r--r-- | script/constant/TokenModifiers.lua | 8 | ||||
-rw-r--r-- | script/constant/TokenTypes.lua | 21 | ||||
-rw-r--r-- | script/method/init.lua | 1 | ||||
-rw-r--r-- | script/method/textDocument/semanticTokens.lua | 179 | ||||
-rw-r--r-- | script/service.lua | 2 |
6 files changed, 236 insertions, 3 deletions
diff --git a/script/capability/semantic.lua b/script/capability/semantic.lua index a5c8746b..72c889e1 100644 --- a/script/capability/semantic.lua +++ b/script/capability/semantic.lua @@ -1,11 +1,27 @@ -local rpc = require 'rpc' +local rpc = require 'rpc' +local TokenTypes = require 'constant.TokenTypes' +local TokenModifiers = require 'constant.TokenModifiers' local isEnable = false -local function enable() +local function toArray(map) + local array = {} + for k in pairs(map) do + array[#array+1] = k + end + table.sort(array, function (a, b) + return map[a] < map[b] + end) + return array +end + +local function enable(lsp) if isEnable then return end + if not lsp.client.capabilities.textDocument.semanticTokens then + return + end isEnable = true log.debug('Enable semantic.') rpc:request('client/registerCapability', { @@ -13,6 +29,14 @@ local function enable() { id = 'semantic', method = 'textDocument/semanticTokens', + registerOptions = { + legend = { + tokenTypes = toArray(TokenTypes), + tokenModifiers = toArray(TokenModifiers), + }, + rangeProvider = false, + documentProvider = false, + }, }, } }) diff --git a/script/constant/TokenModifiers.lua b/script/constant/TokenModifiers.lua new file mode 100644 index 00000000..b77fd386 --- /dev/null +++ b/script/constant/TokenModifiers.lua @@ -0,0 +1,8 @@ +return { + ["declaration"] = 1 << 0, + ["documentation"] = 1 << 1, + ["static"] = 1 << 2, + ["abstract"] = 1 << 3, + ["deprecated"] = 1 << 4, + ["readonly"] = 1 << 5, +} diff --git a/script/constant/TokenTypes.lua b/script/constant/TokenTypes.lua new file mode 100644 index 00000000..236a7805 --- /dev/null +++ b/script/constant/TokenTypes.lua @@ -0,0 +1,21 @@ +return { + ["comment"] = 0, + ["keyword"] = 1, + ["number"] = 2, + ["regexp"] = 3, + ["operator"] = 4, + ["namespace"] = 5, + ["type"] = 6, + ["struct"] = 7, + ["class"] = 8, + ["interface"] = 9, + ["enum"] = 10, + ["typeParameter"] = 11, + ["function"] = 12, + ["member"] = 13, + ["macro"] = 14, + ["variable"] = 15, + ["parameter"] = 16, + ["property"] = 17, + ["label"] = 18, +} diff --git a/script/method/init.lua b/script/method/init.lua index 8827768b..dd662a2d 100644 --- a/script/method/init.lua +++ b/script/method/init.lua @@ -23,6 +23,7 @@ init 'textDocument/onTypeFormatting' init 'textDocument/publishDiagnostics' init 'textDocument/rename' init 'textDocument/references' +init 'textDocument/semanticTokens' init 'textDocument/signatureHelp' init 'workspace/didChangeConfiguration' init 'workspace/didChangeWatchedFiles' diff --git a/script/method/textDocument/semanticTokens.lua b/script/method/textDocument/semanticTokens.lua new file mode 100644 index 00000000..6595459e --- /dev/null +++ b/script/method/textDocument/semanticTokens.lua @@ -0,0 +1,179 @@ +local TokenTypes = require 'constant.TokenTypes' +local TokenModifiers = require 'constant.TokenModifiers' +local findLib = require 'core.find_lib' + +local timerCache = {} +local constLib = { + ['_G'] = true, + ['_VERSION'] = true, + ['math.pi'] = true, + ['math.huge'] = true, + ['math.maxinteger'] = true, + ['math.mininteger'] = true, + ['utf8.charpattern'] = true, + ['io.stdin'] = true, + ['io.stdout'] = true, + ['io.stderr'] = true, + ['package.config'] = true, + ['package.cpath'] = true, + ['package.loaded'] = true, + ['package.loaders'] = true, + ['package.path'] = true, + ['package.preload'] = true, + ['package.searchers'] = true +} + +local function buildLibToken(source, lib) + local modifieres + if constLib[lib.doc] then + modifieres = TokenModifiers.readonly + else + modifieres = TokenModifiers.static + end + return { + start = source.start, + finish = source.finish, + type = TokenTypes.namespace, + modifieres = modifieres, + } +end + +local Care = { + ['name'] = function (source) + local lib = findLib(source) + if lib then + return buildLibToken(source, lib) + end + if source:get 'global' then + return { + start = source.start, + finish = source.finish, + type = TokenTypes.namespace, + modifieres = TokenModifiers.deprecated, + } + else + return { + start = source.start, + finish = source.finish, + type = TokenTypes.variable, + } + end + end, +} + +local function buildTokens(sources, lines) + local tokens = {} + local lastLine = 0 + local lastStartChar = 0 + for i, source in ipairs(sources) do + local row, col = lines:rowcol(source.start) + local line = row - 1 + local startChar = col - 1 + local deltaLine = line - lastLine + local deltaStartChar + if deltaLine == 0 then + deltaStartChar = startChar - lastStartChar + else + deltaStartChar = startChar + end + lastLine = line + lastStartChar = startChar + local len = i * 5 - 5 + tokens[len + 1] = deltaLine + tokens[len + 2] = deltaStartChar + tokens[len + 3] = source.finish - source.start + 1 -- length + tokens[len + 4] = source.type + tokens[len + 5] = source.modifieres or 0 + end + return tokens +end + +local function resolveTokens(vm, lines) + local sources = {} + for _, source in ipairs(vm.sources) do + if Care[source.type] then + sources[#sources+1] = Care[source.type](source) + end + end + + -- 先进行排序 + table.sort(sources, function (a, b) + return a.start < b.start + end) + + log.debug(table.dump(sources)) + + local tokens = buildTokens(sources, lines) + + return tokens +end + +local function toArray(map) + local array = {} + for k in pairs(map) do + array[#array+1] = k + end + table.sort(array, function (a, b) + return map[a] < map[b] + end) + return array +end + +local function testTokens(vm, lines) + local text = vm.text + local sources = {} + local init = 1 + while true do + local start, finish = text:find('[%w_%.]+', init) + if not start then + break + end + init = finish + 1 + local token = text:sub(start, finish) + local type = token:match '[%w_]+' + local mod = token:match '%.([%w_]+)' + sources[#sources+1] = { + start = start, + finish = finish, + type = TokenTypes[type], + modifieres = TokenModifiers[mod] or 0, + } + end + local tokens = buildTokens(sources, lines) + log.debug(table.dump(sources)) + log.debug(table.dump(tokens)) + return tokens +end + +return function (lsp, params) + local uri = params.textDocument.uri + + if timerCache[uri] then + timerCache[uri]:remove() + timerCache[uri] = nil + end + + return function (response) + local clock = os.clock() + timerCache[uri] = ac.loop(0.1, function (t) + local vm, lines = lsp:getVM(uri) + if not vm then + if os.clock() - clock > 10 then + t:remove() + timerCache[uri] = nil + response(nil) + end + return + end + + t:remove() + timerCache[uri] = nil + + local tokens = resolveTokens(vm, lines) + --local tokens = testTokens(vm, lines) + response { + data = tokens, + } + end) + end +end diff --git a/script/service.lua b/script/service.lua index 88e8448c..1e3f7108 100644 --- a/script/service.lua +++ b/script/service.lua @@ -774,7 +774,7 @@ function mt:onUpdateConfig(updated, other) capability.completion.disable(self) end if newConfig.color.mode == 'Semantic' then - capability.semantic.enable() + capability.semantic.enable(self) else capability.semantic.disable() end |