Skip to content

Commit 1460a15

Browse files
committed
more work on lsp
- integration tests for lsp features - more work on renaming, find references, go to definition, symbol tree, etc - some error handling improvements in the extension - overall better editor support
1 parent cf4c39a commit 1460a15

28 files changed

Lines changed: 1923 additions & 277 deletions

File tree

language_server/editor_helper.lua

Lines changed: 556 additions & 107 deletions
Large diffs are not rendered by default.

language_server/lsp.lua

Lines changed: 145 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ local function get_range(code, start, stop)
6464
end
6565

6666
local editor_helper = EditorHelper.New()
67+
lsp.editor_helper = editor_helper
6768
editor_helper.debug = false
6869

6970
local function to_fs_path(url)
@@ -128,10 +129,15 @@ function editor_helper:OnDiagnostics(path, data)
128129

129130
for i, v in ipairs(data) do
130131
local range = get_range(v.code, v.start, v.stop)
132+
local tags
133+
134+
if v.message:find("is never used") then tags = {1} end
135+
131136
diagnostics[i] = {
132137
severity = DiagnosticSeverity[v.severity],
133138
range = range,
134139
message = v.message .. "\n" .. (v.trace or "no trace??"),
140+
tags = tags,
135141
}
136142
end
137143

@@ -147,7 +153,12 @@ function editor_helper:OnDiagnostics(path, data)
147153
end
148154

149155
lsp.methods["initialize"] = function(params)
150-
editor_helper:SetWorkingDirectory(to_fs_path(params.workspaceFolders[1].uri))
156+
if params.workspaceFolders and params.workspaceFolders[1] then
157+
editor_helper:SetWorkingDirectory(to_fs_path(params.workspaceFolders[1].uri))
158+
elseif params.rootUri then
159+
editor_helper:SetWorkingDirectory(to_fs_path(params.rootUri))
160+
end
161+
151162
return {
152163
clientInfo = {name = "NattLua", version = "1.0"},
153164
capabilities = {
@@ -182,30 +193,68 @@ lsp.methods["initialize"] = function(params)
182193
resolveProvider = true,
183194
},]]
184195
documentSymbolProvider = true,
185-
-- for symbols like all functions within a file
186-
-- highlighting equal upvalues
187-
-- documentHighlightProvider = true,
188-
--[[
196+
documentHighlightProvider = true,
189197
signatureHelpProvider = {
190-
triggerCharacters = { "(" },
198+
triggerCharacters = {"(", ","},
191199
},
192-
193-
workspaceSymbolProvider = true,
194-
codeActionProvider = true,
195-
documentFormattingProvider = true,
196-
documentRangeFormattingProvider = true,
197-
documentOnTypeFormattingProvider = {
198-
firstTriggerCharacter = "}",
199-
moreTriggerCharacter = { "end" },
200+
codeActionProvider = {
201+
codeActionKinds = {"quickfix", "source.fixAll.unusedImports"},
200202
},
201-
renameProvider = true,
202-
]]
203+
documentFormattingProvider = true,
203204
},
204205
}
205206
end
206207
lsp.methods["initialized"] = function(params)
207208
editor_helper:Initialize()
208209
end
210+
lsp.methods["textDocument/codeAction"] = function(params)
211+
local actions = {}
212+
local has_unused = false
213+
214+
for _, diagnostic in ipairs(params.context.diagnostics) do
215+
if diagnostic.tags then
216+
for _, tag in ipairs(diagnostic.tags) do
217+
if tag == 1 then
218+
has_unused = true
219+
220+
break
221+
end
222+
end
223+
end
224+
end
225+
226+
if has_unused then
227+
local path = to_fs_path(params.textDocument.uri)
228+
local code = editor_helper:GetFileContent(path)
229+
local new_code = editor_helper:Format(code, path, {remove_unused = true})
230+
231+
if new_code ~= code then
232+
table.insert(
233+
actions,
234+
{
235+
title = "Remove all unused code",
236+
kind = "source.fixAll.unusedImports",
237+
edit = {
238+
changes = {
239+
[params.textDocument.uri] = {
240+
{
241+
range = {
242+
start = {line = 0, character = 0},
243+
["end"] = {line = 100000, character = 0},
244+
},
245+
newText = new_code,
246+
},
247+
},
248+
},
249+
},
250+
}
251+
)
252+
end
253+
end
254+
255+
return actions
256+
end
257+
209258
lsp.methods["nattlua/format"] = function(params)
210259
local path = to_fs_path(params.textDocument.uri)
211260
local code = editor_helper:Format(params.code, path)
@@ -216,6 +265,25 @@ lsp.methods["nattlua/format"] = function(params)
216265
code = b64.encode(code),
217266
}
218267
end
268+
lsp.methods["textDocument/formatting"] = function(params)
269+
local path = to_fs_path(params.textDocument.uri)
270+
local code = editor_helper:GetFileContent(path)
271+
local new_code = editor_helper:Format(code, path)
272+
273+
if new_code ~= code then
274+
return {
275+
{
276+
range = {
277+
start = {line = 0, character = 0},
278+
["end"] = {line = 100000, character = 0},
279+
},
280+
newText = new_code,
281+
},
282+
}
283+
end
284+
285+
return {}
286+
end
219287
lsp.methods["shutdown"] = function(params)
220288
table.print(params)
221289
end
@@ -234,8 +302,9 @@ lsp.methods["$/cancelRequest"] = function(params)
234302
table.print(params)
235303
end
236304
lsp.methods["workspace/didChangeConfiguration"] = function(params)
237-
print("configuration changed")
238-
table.print(params)
305+
if params.settings and params.settings.nattlua then
306+
editor_helper.workspace_config = params.settings.nattlua
307+
end
239308
end
240309
lsp.methods["textDocument/didOpen"] = function(params)
241310
local path = to_fs_path(params.textDocument.uri)
@@ -255,21 +324,32 @@ lsp.methods["textDocument/references"] = function(params)
255324

256325
if not editor_helper:IsLoaded(path) then return {} end
257326

258-
local data = editor_helper:GetFile(path)
259-
local nodes = editor_helper:GetReferences(path, params.position.line, params.position.character - 1)
327+
local items = editor_helper:GetReferences(path, params.position.line, params.position.character)
260328

261-
if not nodes then return {} end
329+
if not items then return {} end
262330

263331
local result = {}
264332

265-
for k, node in pairs(nodes) do
266-
local path = node:GetSourcePath() or to_fs_path(path)
267-
editor_helper:OpenFile(path, node.Code:GetString())
333+
for k, item in pairs(items) do
334+
local start, stop
335+
local source_path
336+
337+
if item.GetStartStop then
338+
start, stop = item:GetStartStop()
339+
source_path = item:GetSourcePath()
340+
else
341+
start, stop = item.start, item.stop
342+
source_path = item.lexer.Code:GetName()
343+
end
344+
345+
source_path = source_path or path
346+
local fs_path = to_fs_path(source_path)
347+
local lsp_path = to_lsp_path(source_path)
268348
table.insert(
269349
result,
270350
{
271-
uri = to_fs_path(path),
272-
range = get_range(editor_helper:GetCode(path), node:GetStartStop()),
351+
uri = lsp_path,
352+
range = get_range(editor_helper:GetCode(fs_path), start, stop),
273353
}
274354
)
275355
end
@@ -483,6 +563,44 @@ if false then
483563
end
484564
end
485565

566+
lsp.methods["textDocument/documentHighlight"] = function(params)
567+
local path = to_fs_path(params.textDocument.uri)
568+
569+
if not editor_helper:IsLoaded(path) then return {} end
570+
571+
local highlights = editor_helper:GetUpvalueHighlightRanges(path, params.position.line, params.position.character)
572+
573+
if not highlights then return {} end
574+
575+
local result = {}
576+
577+
for i, range_data in ipairs(highlights) do
578+
local range = {
579+
start = {
580+
line = range_data.line_start - 1,
581+
character = range_data.character_start - 1,
582+
},
583+
["end"] = {
584+
line = range_data.line_stop - 1,
585+
character = range_data.character_stop,
586+
},
587+
}
588+
table.insert(result, {
589+
range = range,
590+
kind = 1, -- Text
591+
})
592+
end
593+
594+
return result
595+
end
596+
lsp.methods["textDocument/signatureHelp"] = function(params)
597+
local path = to_fs_path(params.textDocument.uri)
598+
599+
if not editor_helper:IsLoaded(path) then return {} end
600+
601+
local result = editor_helper:GetSignatureHelp(path, params.position.line, params.position.character)
602+
return result or {signatures = {}, activeSignature = 0, activeParameter = 0}
603+
end
486604
lsp.methods["textDocument/rename"] = function(params)
487605
local fs_path = to_fs_path(params.textDocument.uri)
488606

@@ -517,7 +635,7 @@ lsp.methods["textDocument/definition"] = function(params)
517635

518636
local node = editor_helper:GetDefinition(path, params.position.line, params.position.character)
519637

520-
if node then
638+
if node and node.GetStartStop then
521639
local start, stop = node:GetStartStop()
522640
local path = node:GetSourcePath() or path
523641
path = to_fs_path(path)

language_server/main.lua

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,29 +12,42 @@ io.stdout:setvbuf("no")
1212
local function read_message()
1313
local line = INPUT:read("*l")
1414

15+
while line and not line:match("Content%-Length:") do
16+
line = INPUT:read("*l")
17+
end
18+
1519
if not line then return nil end
1620

1721
local content_length = tonumber(line:match("Content%-Length: (%d+)"))
18-
INPUT:read("*l") -- Read the empty line
22+
23+
if not content_length then return nil end
24+
25+
-- Skip any other headers until we find the empty line
26+
while line and line ~= "" and line ~= "\r" do
27+
line = INPUT:read("*l")
28+
end
29+
1930
local str = INPUT:read(content_length)
20-
session_input:write(str, "\n\n")
21-
session_input:flush()
31+
32+
if session_input then
33+
session_input:write(str, "\n\n")
34+
session_input:flush()
35+
end
36+
2237
return str
2338
end
2439

2540
-- Without this, it seems like vscode will error as the body length deviates from content-length with unicode characters
26-
-- I initially thought utf8.length would work, but that doesn't seem to be it.
27-
local function escape_unicode(c)
28-
return string.format("\\u%04x", c:byte())
29-
end
30-
3141
local function write_message(message)
3242
local encoded = json.encode(message)
3343
local data = string.format("Content-Length: %d\r\n\r\n%s", #encoded, encoded)
3444
OUTPUT:write(data)
3545
OUTPUT:flush()
36-
session_output:write(data)
37-
session_output:flush()
46+
47+
if session_output then
48+
session_output:write(data)
49+
session_output:flush()
50+
end
3851
end
3952

4053
OUTPUT:setvbuf("no")

nattlua/analyzer/analyzer.lua

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,25 +63,31 @@ do
6363
node.Type == "statement_local_type_function"
6464
then
6565
self:PushAnalyzerEnvironment(node.Type == "statement_local_function" and "runtime" or "typesystem")
66-
self:CreateLocalValue(node.tokens["identifier"]:GetValueString(), AnalyzeFunction(self, node))
66+
local val = AnalyzeFunction(self, node)
67+
local ident_token = node.tokens["identifier"]
68+
local upvalue = self:CreateLocalValue(ident_token:GetValueString(), val, false, ident_token)
69+
self:MapTypeToNode(val, ident_token)
70+
self:MapTypeToNode(upvalue, ident_token)
6771
self:PopAnalyzerEnvironment()
6872
elseif
6973
node.Type == "statement_function" or
7074
node.Type == "statement_analyzer_function" or
7175
node.Type == "statement_type_function"
7276
then
73-
local key = node.expression
77+
local key_node = node.expression
7478
self:PushAnalyzerEnvironment(node.Type == "statement_function" and "runtime" or "typesystem")
7579

76-
if key.Type == "expression_binary_operator" then
77-
local obj = self:AnalyzeExpression(key.left)
78-
local key = self:AnalyzeExpression(key.right)
80+
if key_node.Type == "expression_binary_operator" then
81+
local obj = self:AnalyzeExpression(key_node.left)
82+
local key = self:AnalyzeExpression(key_node.right)
7983
local val = AnalyzeFunction(self, node)
8084
self:NewIndexOperator(obj, key, val)
85+
self:MapTypeToNode(val, key_node.right)
8186
else
82-
local key = ConstString(key.value:GetValueString())
87+
local key = ConstString(key_node.value:GetValueString())
8388
local val = AnalyzeFunction(self, node)
8489
self:SetLocalOrGlobalValue(key, val)
90+
self:MapTypeToNode(val, key_node)
8591
end
8692

8793
self:PopAnalyzerEnvironment()
@@ -310,7 +316,11 @@ do
310316
end
311317

312318
function META:MapTypeToNode(typ, node)
319+
if not typ or not node then return end
320+
313321
self.type_to_node[typ] = node
322+
323+
if node.AssociateType then node:AssociateType(typ) end
314324
end
315325

316326
function META:GetTypeToNodeMap()

nattlua/analyzer/base/error_handling.lua

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,4 +270,25 @@ return function(META--[[#: any]])
270270
if info ~= false and info.msg then self:Warning(info.msg, info.node) end
271271
end
272272
end
273+
274+
function META:ReportUnusedUpvalues(custom_scope)
275+
if not self.config.remove_unused then return end
276+
277+
local scope = custom_scope or self:GetScope()
278+
local unused = scope:FindUnusedUpvalues()
279+
280+
if unused then
281+
for _, upvalue in ipairs(unused) do
282+
local name = upvalue:GetKey()
283+
284+
if name ~= "_" then
285+
local identifier = upvalue:GetIdentifier()
286+
287+
if identifier and type(identifier) == "table" and identifier.GetStartStop then
288+
self:Warning(name .. " is never used", identifier)
289+
end
290+
end
291+
end
292+
end
293+
end
273294
end

0 commit comments

Comments
 (0)