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
|
local ckind = require 'define.CompletionItemKind'
local files = require 'files'
local guide = require 'parser.guide'
local matchKey = require 'core.matchKey'
local vm = require 'vm'
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 nil
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)
guide.getVisibleLocalNames(ast.ast, offset, function (name)
if matchKey(word, name) then
results[#results+1] = {
label = name,
kind = ckind.Variable,
}
end
end)
end
local function checkField(ast, text, word, offset, results)
local parent, oop = findParent(ast, text, offset - #word)
if not parent then
parent = guide.getLocal(ast.ast, '_ENV', offset)
if not parent then
return
end
end
vm.eachField(parent, function (info)
local key = info.key
if key
and key:sub(1, 1) == 's'
and info.source.finish ~= offset then
local name = key:sub(3)
if matchKey(word, name) then
results[#results+1] = {
label = name,
kind = ckind.Field,
}
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)
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
|