summaryrefslogtreecommitdiff
path: root/server/src/core/completion.old.lua
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/core/completion.old.lua')
-rw-r--r--server/src/core/completion.old.lua578
1 files changed, 578 insertions, 0 deletions
diff --git a/server/src/core/completion.old.lua b/server/src/core/completion.old.lua
new file mode 100644
index 00000000..57e006e9
--- /dev/null
+++ b/server/src/core/completion.old.lua
@@ -0,0 +1,578 @@
+local findSource = require 'core.find_source'
+local hover = require 'core.hover'
+
+local CompletionItemKind = {
+ Text = 1,
+ Method = 2,
+ Function = 3,
+ Constructor = 4,
+ Field = 5,
+ Variable = 6,
+ Class = 7,
+ Interface = 8,
+ Module = 9,
+ Property = 10,
+ Unit = 11,
+ Value = 12,
+ Enum = 13,
+ Keyword = 14,
+ Snippet = 15,
+ Color = 16,
+ File = 17,
+ Reference = 18,
+ Folder = 19,
+ EnumMember = 20,
+ Constant = 21,
+ Struct = 22,
+ Event = 23,
+ Operator = 24,
+ TypeParameter = 25,
+}
+
+local function matchKey(me, other)
+ if me == other then
+ return true
+ end
+ if me == '' then
+ return true
+ end
+ if #me > #other then
+ return false
+ end
+ local lMe = me:lower()
+ local lOther = other:lower()
+ if lMe:sub(1, 1) ~= lOther:sub(1, 1) then
+ return false
+ end
+ if lMe == lOther:sub(1, #lMe) then
+ return true
+ end
+ local used = {}
+ local cur = 1
+ local lookup
+ local researched
+ for i = 1, #lMe do
+ local c = lMe:sub(i, i)
+ -- 1. 看当前字符是否匹配
+ if c == lOther:sub(cur, cur) then
+ used[cur] = true
+ goto NEXT
+ end
+ -- 2. 看前一个字符是否匹配
+ if not used[cur-1] then
+ if c == lOther:sub(cur-1, cur-1) then
+ used[cur-1] = true
+ goto NEXT
+ end
+ end
+ -- 3. 向后找这个字
+ lookup = lOther:find(c, cur+1, true)
+ if lookup then
+ cur = lookup
+ used[cur] = true
+ goto NEXT
+ end
+
+ -- 4. 重新搜索整个字符串,但是只允许1次,否则失败.如果找不到也失败
+ if researched then
+ return false
+ else
+ researched = true
+ for j = 1, cur - 2 do
+ if c == lOther:sub(j, j) then
+ used[j] = true
+ goto NEXT
+ end
+ end
+ return false
+ end
+ -- 5. 找到下一个可用的字,如果超出长度且把自己所有字都用尽就算成功
+ ::NEXT::
+ repeat
+ cur = cur + 1
+ until not used[cur]
+ if cur > #lOther then
+ return i == #lMe
+ end
+ end
+ return true
+end
+
+local function searchLocals(vm, pos, name, callback)
+ for _, loc in ipairs(vm.results.locals) do
+ if loc.source.start == 0 then
+ goto CONTINUE
+ end
+ if not loc.close then
+ log.debug('Miss loc close', table.dump(loc))
+ end
+ if loc.source.start <= pos and loc.close >= pos then
+ if matchKey(name, loc.key) then
+ callback(loc)
+ end
+ end
+ ::CONTINUE::
+ end
+end
+
+local function sortPairs(t)
+ local keys = {}
+ for k in pairs(t) do
+ keys[#keys+1] = k
+ end
+ table.sort(keys)
+ local i = 0
+ return function ()
+ i = i + 1
+ local k = keys[i]
+ return k, t[k]
+ end
+end
+
+local function searchFields(name, parentValue, source, callback)
+ if not parentValue then
+ return
+ end
+ if type(name) ~= 'string' then
+ return
+ end
+ local map = {}
+ parentValue:eachField(function (key, field)
+ if type(key) ~= 'string' then
+ goto CONTINUE
+ end
+ if source.object and field.value:getType() ~= 'function' then
+ goto CONTINUE
+ end
+ if matchKey(name, key) then
+ map[key] = field
+ end
+ ::CONTINUE::
+ end)
+ for _, field in sortPairs(map) do
+ callback(field)
+ end
+end
+
+local KEYS = {'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for', 'function', 'goto', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat', 'return', 'then', 'true', 'until', 'while', 'toclose'}
+local function searchKeyWords(name, callback)
+ for _, key in ipairs(KEYS) do
+ if matchKey(name, key) then
+ callback(key)
+ end
+ end
+end
+
+local function getKind(var, default)
+ local value = var.value
+ if default == CompletionItemKind.Variable then
+ if value:getType() == 'function' then
+ return CompletionItemKind.Function
+ end
+ end
+ if default == CompletionItemKind.Field then
+ local tp = type(value:getValue())
+ if tp == 'number' or tp == 'integer' or tp == 'string' then
+ return CompletionItemKind.Enum
+ end
+ if value:getType() == 'function' then
+ if value.object then
+ return CompletionItemKind.Method
+ else
+ return CompletionItemKind.Function
+ end
+ end
+ end
+ return default
+end
+
+local function getDetail(var)
+ local tp = type(var.value:getValue())
+ if tp == 'boolean' then
+ return ('= %q'):format(var.value:getValue())
+ elseif tp == 'number' then
+ if math.type(var.value:getValue()) == 'integer' then
+ return ('= %q'):format(var.value:getValue())
+ else
+ local str = ('= %.10f'):format(var.value:getValue())
+ local dot = str:find('.', 1, true)
+ local suffix = str:find('[0]+$', dot+2)
+ if suffix then
+ return str:sub(1, suffix-1)
+ else
+ return str
+ end
+ end
+ elseif tp == 'string' then
+ return ('= %q'):format(var.value:getValue())
+ end
+ return nil
+end
+
+local function getDocument(var, source)
+ if not source then
+ return nil
+ end
+ if var.value:getType() == 'function' then
+ local hvr = hover(var, source)
+ if not hvr then
+ return nil
+ end
+ local text = ([[
+```lua
+%s
+```
+%s
+```lua
+%s
+```
+]]):format(hvr.label or '', hvr.description or '', hvr.enum or '')
+ return {
+ kind = 'markdown',
+ value = text,
+ }
+ end
+ return nil
+end
+
+local function searchAsLocal(vm, word, pos, result, source, callback)
+ searchFields(word, vm.env, source, function (var)
+ callback(var, CompletionItemKind.Variable)
+ end)
+
+ -- 支持 local function
+ if matchKey(word, 'function') then
+ callback('function', CompletionItemKind.Keyword)
+ end
+end
+
+local function searchAsArg(vm, word, pos, result, source, callback)
+ searchFields(word, vm.env, source, function (var)
+ if var.value.lib then
+ return
+ end
+ callback(var, CompletionItemKind.Variable)
+ end)
+end
+
+local function searchAsGlobal(vm, word, pos, result, source, callback)
+ if word == '' or word == nil then
+ return
+ end
+ searchLocals(vm, pos, word, function (var)
+ callback(var, CompletionItemKind.Variable)
+ end)
+ searchFields(word, result.parentValue, source, function (var)
+ callback(var, CompletionItemKind.Field)
+ end)
+ searchKeyWords(word, function (name)
+ callback(name, CompletionItemKind.Keyword)
+ end)
+end
+
+local function searchAsSuffix(result, source, word, callback)
+ searchFields(word, result.parentValue, source, function (var)
+ callback(var, CompletionItemKind.Field)
+ end)
+end
+
+local function searchInArg(vm, inCall, inString, callback)
+ local lib = inCall.func.lib
+ if not lib then
+ return
+ end
+
+ -- require列举出可以引用到的文件
+ if lib.special == 'require' then
+ if not vm.lsp or not vm.lsp.workspace or not inString then
+ return
+ end
+ local results = vm.lsp.workspace:matchPath(vm.uri, inString[1])
+ if not results then
+ return
+ end
+ for _, v in ipairs(results) do
+ if v ~= inString[1] then
+ callback(v, CompletionItemKind.File, {
+ textEdit = {
+ start = inString.start+1,
+ finish = inString.finish-1,
+ newText = ('%q'):format(v):sub(2, -2),
+ }
+ })
+ end
+ end
+ end
+
+ -- 其他库函数,根据参数位置找枚举值
+ if lib.args and lib.enums then
+ local arg = lib.args[inCall.select]
+ local name = arg and arg.name
+ for _, enum in ipairs(lib.enums) do
+ if enum.name == name and enum.enum then
+ if inString then
+ callback(enum.enum, CompletionItemKind.EnumMember, {
+ documentation = enum.description
+ })
+ else
+ callback(('%q'):format(enum.enum), CompletionItemKind.EnumMember, {
+ documentation = enum.description
+ })
+ end
+ end
+ end
+ end
+end
+
+local function searchAsIndex(vm, word, pos, result, source, callback)
+ searchLocals(vm, pos, word, function (var)
+ callback(var, CompletionItemKind.Variable)
+ end)
+ for _, source in ipairs(vm.results.sources) do
+ if source.isIndex then
+ local index = source.bind
+ if matchKey(word, index.key) then
+ callback(index.key, CompletionItemKind.Property)
+ end
+ end
+ end
+ searchFields(word, result.parentValue, source, function (var)
+ callback(var, CompletionItemKind.Field)
+ end)
+end
+
+local function findClosePos(vm, pos)
+ local curDis = math.maxinteger
+ local parent = nil
+ local inputSource = nil
+ local function found(object, source)
+ local dis = pos - source.finish
+ if dis > 1 and dis < curDis then
+ curDis = dis
+ parent = object
+ inputSource = source
+ end
+ end
+ for _, source in ipairs(vm.results.sources) do
+ found(source.bind, source)
+ end
+ if not parent then
+ return nil
+ end
+ if parent.type ~= 'local' and parent.type ~= 'field' then
+ return nil
+ end
+ local sep = inputSource.dot or inputSource.colon
+ if not sep then
+ return nil
+ end
+ if sep.finish > pos then
+ return nil
+ end
+ -- 造个假的 DirtyName
+ local source = {
+ type = 'name',
+ start = pos,
+ finish = pos,
+ object = sep.type == ':' and parent,
+ isSuffix = true,
+ [1] = '',
+ }
+ local result = {
+ type = 'field',
+ parent = parent,
+ parentValue = parent.value,
+ key = '',
+ }
+ return result, source
+end
+
+local function isContainPos(obj, pos)
+ if obj.start <= pos and obj.finish + 1 >= pos then
+ return true
+ end
+ return false
+end
+
+local function findString(vm, word, pos)
+ local finishPos = pos + #word - 1
+ for _, source in ipairs(vm.results.strings) do
+ if isContainPos(source, finishPos) then
+ return source
+ end
+ end
+ return nil
+end
+
+local function findArgCount(args, pos)
+ for i, arg in ipairs(args) do
+ if isContainPos(arg, pos) then
+ return i
+ end
+ end
+ return #args + 1
+end
+
+-- 找出范围包含pos的call
+local function findCall(vm, word, pos)
+ local finishPos = pos + #word - 1
+ local results = {}
+ for _, call in ipairs(vm.results.calls) do
+ if isContainPos(call.args, finishPos) then
+ local n = findArgCount(call.args, finishPos)
+ local var = call.lastObj.bind
+ if var then
+ results[#results+1] = {
+ func = call.func,
+ var = var,
+ source = call.lastObj,
+ select = n,
+ args = call.args,
+ }
+ end
+ end
+ end
+ if #results == 0 then
+ return nil
+ end
+ -- 可能处于 'func1(func2(' 的嵌套中,因此距离越远的函数层级越低
+ table.sort(results, function (a, b)
+ return a.args.start < b.args.start
+ end)
+ return results[#results]
+end
+
+local function makeList(list, source)
+ local mark = {}
+ local function callback(var, defualt, data)
+ local key
+ if type(var) == 'string' then
+ key = var
+ else
+ key = var.key
+ end
+ if mark[key] then
+ return
+ end
+ mark[key] = true
+ data = data or {}
+ if var == key then
+ data.label = var
+ data.kind = defualt
+ elseif var.source ~= source then
+ data.label = var.key
+ data.kind = getKind(var, defualt)
+ data.detail = data.detail or getDetail(var)
+ data.documentation = data.documentation or getDocument(var, source)
+ else
+ return
+ end
+ list[#list+1] = data
+ end
+ return callback
+end
+
+local function searchInResult(result, word, source, vm, pos, callback)
+ if result.type == 'local' then
+ if result.link then
+ result = result.link
+ end
+ if source.isArg then
+ searchAsArg(vm, word, pos, result, source, callback)
+ elseif source.isLocal then
+ searchAsLocal(vm, word, pos, result, source, callback)
+ else
+ searchAsGlobal(vm, word, pos, result, source, callback)
+ end
+ elseif result.type == 'field' then
+ if source.isIndex then
+ searchAsIndex(vm, word, pos, result, source, callback)
+ elseif source.isSuffix then
+ searchAsSuffix(result, source, word, callback)
+ else
+ searchAsGlobal(vm, word, pos, result, source, callback)
+ end
+ end
+end
+
+local function searchAllWords(text, vm, callback)
+ if text == '' then
+ return
+ end
+ if type(text) ~= 'string' then
+ return
+ end
+ for _, source in ipairs(vm.results.sources) do
+ if source.type == 'name' then
+ if text ~= source[1] and matchKey(text, source[1]) then
+ callback(source[1], CompletionItemKind.Text)
+ end
+ end
+ end
+end
+
+local function searchSpecial(vm, pos, callback)
+ -- 尝试 #
+ local _, source = findSource(vm, pos, 2)
+ if source and source.indexName and source[1].op == '#'
+ then
+ local label = source.indexName .. '+1'
+ callback(label, CompletionItemKind.Snippet, {
+ textEdit = {
+ start = source.start,
+ finish = source.finish,
+ newText = ('[#%s] = '):format(label),
+ }
+ })
+ end
+end
+
+local function clearList(list, source)
+ local key = source[1]
+ -- 清除自己
+ for i, v in ipairs(list) do
+ if v.label == key then
+ table.remove(list, i)
+ return
+ end
+ end
+end
+
+local function isValidResult(result)
+ if not result then
+ return false
+ end
+ if result.type == 'local' or result.type == 'field' then
+ return true
+ end
+ return false
+end
+
+return function (vm, pos, word)
+ local list = {}
+ local callback = makeList(list)
+ local inCall = findCall(vm, word, pos)
+ local inString = findString(vm, word, pos)
+ if inCall then
+ searchInArg(vm, inCall, inString, callback)
+ end
+ searchSpecial(vm, pos, callback)
+ if not inString then
+ local result, source = findSource(vm, pos)
+ if not isValidResult(result) then
+ result, source = findClosePos(vm, pos)
+ end
+ if isValidResult(result) then
+ callback = makeList(list, source)
+ searchInResult(result, word, source, vm, pos, callback)
+ searchAllWords(result.key, vm, callback)
+ clearList(list, source)
+ else
+ searchAllWords(word, vm, callback)
+ end
+ end
+ if #list == 0 then
+ return nil
+ end
+ return list
+end