1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
|
local ckind = require 'define.CompletionItemKind'
local files = require 'files'
local guide = require 'parser.guide'
local matchKey = require 'core.matchKey'
local vm = require 'vm'
local library = require 'library'
local getLabel = require 'core.hover.label'
local getName = require 'core.hover.name'
local getArg = require 'core.hover.arg'
local config = require 'config'
local util = require 'utility'
local function isSpace(char)
if char == ' '
or char == '\n'
or char == '\r'
or char == '\t' then
return true
end
return false
end
local function findWord(text, offset)
for i = offset, 1, -1 do
if not text:sub(i, i):match '[%w_]' then
if i == offset then
return nil
end
return text:sub(i+1, offset)
end
end
return text:sub(1, offset)
end
local function findAnyPos(text, offset)
for i = offset, 1, -1 do
if not isSpace(text:sub(i, i)) then
return i
end
end
return nil
end
local function findParent(ast, text, offset)
for i = offset, 1, -1 do
local char = text:sub(i, i)
if isSpace(char) then
goto CONTINUE
end
local oop
if char == '.' then
oop = false
elseif char == ':' then
oop = true
else
return nil, nil
end
local anyPos = findAnyPos(text, i-1)
if not anyPos then
return nil, nil
end
local parent = guide.eachSourceContain(ast.ast, anyPos, function (source)
if source.finish == anyPos then
return source
end
end)
if parent then
return parent, oop
end
::CONTINUE::
end
return nil, nil
end
local function checkLocal(ast, word, offset, results)
local locals = guide.getVisibleLocals(ast.ast, offset)
for name, source in pairs(locals) do
if matchKey(word, name) then
results[#results+1] = {
label = name,
kind = ckind.Variable,
detail = getLabel(source),
}
end
end
end
local function isSameSource(source, pos)
return source.start <= pos and source.finish >= pos
end
local function checkField(ast, text, word, offset, results)
local myStart = offset - #word + 1
local parent, oop = findParent(ast, text, myStart - 1)
if not parent then
parent = guide.getLocal(ast.ast, '_ENV', offset)
if not parent then
return
end
end
local used = {}
vm.eachField(parent, function (info)
local key = info.key
if key
and key:sub(1, 1) == 's'
and not isSameSource(info.source, myStart) then
local name = key:sub(3)
if not used[name] and matchKey(word, name) then
results[#results+1] = {
label = name,
kind = ckind.Field,
}
end
used[name] = true
end
end)
end
local function buildFunctionSnip(source)
local name = getName(source)
local args = getArg(source)
local id = 0
args = args:gsub('[^,]+', function (arg)
id = id + 1
return arg:gsub('^(%s*)(.+)', function (sp, word)
return ('%s${%d:%s}'):format(sp, id, word)
end)
end)
return ('%s(%s)'):format(name, args)
end
local function buildFunction(results, source, oop, data)
local snipType = config.config.completion.callSnippet
if snipType == 'Disable' or snipType == 'Both' then
results[#results+1] = data
end
if snipType == 'Both' or snipType == 'Replace' then
local snipData = util.deepCopy(data)
snipData.kind = ckind.Snippet
snipData.label = snipData.label .. '()'
snipData.insertText = buildFunctionSnip(source)
snipData.insertTextFormat = 2
results[#results+1] = snipData
end
end
local function checkLibrary(ast, text, word, offset, results)
for name, lib in pairs(library.global) do
if matchKey(word, name) then
if lib.type == 'function' then
buildFunction(results, lib, false, {
label = name,
kind = ckind.Function,
documentation = lib.description,
detail = getLabel(lib),
})
end
end
end
end
local function checkCommon(word, text, results)
local used = {}
for _, result in ipairs(results) do
used[result.label] = true
end
for str in text:gmatch '[%a_][%w_]*' do
if not used[str] and str ~= word then
used[str] = true
if matchKey(word, str) then
results[#results+1] = {
label = str,
kind = ckind.Text,
}
end
end
end
end
local function isInString(ast, offset)
return guide.eachSourceContain(ast.ast, offset, function (source)
if source.type == 'string' then
return true
end
end)
end
local function tryWord(ast, text, offset, results)
local word = findWord(text, offset)
if not word then
return nil
end
if not isInString(ast, offset) then
checkLocal(ast, word, offset, results)
checkField(ast, text, word, offset, results)
checkLibrary(ast, text, word, offset, results)
end
checkCommon(word, text, results)
end
return function (uri, offset)
local ast = files.getAst(uri)
if not ast then
return nil
end
local text = files.getText(uri)
local results = {}
tryWord(ast, text, offset, results)
if #results == 0 then
return nil
end
return results
end
|