Skip to content

Commit 9f87baa

Browse files
committed
more work on lsp
1 parent cf4c39a commit 9f87baa

8 files changed

Lines changed: 667 additions & 88 deletions

File tree

language_server/editor_helper.lua

Lines changed: 285 additions & 56 deletions
Large diffs are not rendered by default.

language_server/lsp.lua

Lines changed: 62 additions & 8 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)
@@ -182,6 +183,10 @@ lsp.methods["initialize"] = function(params)
182183
resolveProvider = true,
183184
},]]
184185
documentSymbolProvider = true,
186+
documentHighlightProvider = true,
187+
signatureHelpProvider = {
188+
triggerCharacters = {"(", ","},
189+
},
185190
-- for symbols like all functions within a file
186191
-- highlighting equal upvalues
187192
-- documentHighlightProvider = true,
@@ -255,21 +260,32 @@ lsp.methods["textDocument/references"] = function(params)
255260

256261
if not editor_helper:IsLoaded(path) then return {} end
257262

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

261-
if not nodes then return {} end
265+
if not items then return {} end
262266

263267
local result = {}
264268

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())
269+
for k, item in pairs(items) do
270+
local start, stop
271+
local source_path
272+
273+
if item.GetStartStop then
274+
start, stop = item:GetStartStop()
275+
source_path = item:GetSourcePath()
276+
else
277+
start, stop = item.start, item.stop
278+
source_path = item.lexer.Code:GetName()
279+
end
280+
281+
source_path = source_path or path
282+
local fs_path = to_fs_path(source_path)
283+
local lsp_path = to_lsp_path(source_path)
268284
table.insert(
269285
result,
270286
{
271-
uri = to_fs_path(path),
272-
range = get_range(editor_helper:GetCode(path), node:GetStartStop()),
287+
uri = lsp_path,
288+
range = get_range(editor_helper:GetCode(fs_path), start, stop),
273289
}
274290
)
275291
end
@@ -483,6 +499,44 @@ if false then
483499
end
484500
end
485501

502+
lsp.methods["textDocument/documentHighlight"] = function(params)
503+
local path = to_fs_path(params.textDocument.uri)
504+
505+
if not editor_helper:IsLoaded(path) then return {} end
506+
507+
local highlights = editor_helper:GetUpvalueHighlightRanges(path, params.position.line, params.position.character)
508+
509+
if not highlights then return {} end
510+
511+
local result = {}
512+
513+
for i, range_data in ipairs(highlights) do
514+
local range = {
515+
start = {
516+
line = range_data.line_start - 1,
517+
character = range_data.character_start - 1,
518+
},
519+
["end"] = {
520+
line = range_data.line_stop - 1,
521+
character = range_data.character_stop,
522+
},
523+
}
524+
table.insert(result, {
525+
range = range,
526+
kind = 1, -- Text
527+
})
528+
end
529+
530+
return result
531+
end
532+
lsp.methods["textDocument/signatureHelp"] = function(params)
533+
local path = to_fs_path(params.textDocument.uri)
534+
535+
if not editor_helper:IsLoaded(path) then return {} end
536+
537+
local result = editor_helper:GetSignatureHelp(path, params.position.line, params.position.character)
538+
return result or {signatures = {}, activeSignature = 0, activeParameter = 0}
539+
end
486540
lsp.methods["textDocument/rename"] = function(params)
487541
local fs_path = to_fs_path(params.textDocument.uri)
488542

nattlua/analyzer/analyzer.lua

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,25 +63,30 @@ 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 upvalue = self:CreateLocalValue(node.tokens["identifier"]:GetValueString(), val)
68+
self:MapTypeToNode(upvalue, node.tokens["identifier"])
69+
self:MapTypeToNode(val, node.tokens["identifier"])
6770
self:PopAnalyzerEnvironment()
6871
elseif
6972
node.Type == "statement_function" or
7073
node.Type == "statement_analyzer_function" or
7174
node.Type == "statement_type_function"
7275
then
73-
local key = node.expression
76+
local key_node = node.expression
7477
self:PushAnalyzerEnvironment(node.Type == "statement_function" and "runtime" or "typesystem")
7578

76-
if key.Type == "expression_binary_operator" then
77-
local obj = self:AnalyzeExpression(key.left)
78-
local key = self:AnalyzeExpression(key.right)
79+
if key_node.Type == "expression_binary_operator" then
80+
local obj = self:AnalyzeExpression(key_node.left)
81+
local key = self:AnalyzeExpression(key_node.right)
7982
local val = AnalyzeFunction(self, node)
8083
self:NewIndexOperator(obj, key, val)
84+
self:MapTypeToNode(val, key_node.right)
8185
else
82-
local key = ConstString(key.value:GetValueString())
86+
local key = ConstString(key_node.value:GetValueString())
8387
local val = AnalyzeFunction(self, node)
8488
self:SetLocalOrGlobalValue(key, val)
89+
self:MapTypeToNode(val, key_node)
8590
end
8691

8792
self:PopAnalyzerEnvironment()
@@ -310,6 +315,8 @@ do
310315
end
311316

312317
function META:MapTypeToNode(typ, node)
318+
if not typ or not node then return end
319+
313320
self.type_to_node[typ] = node
314321
end
315322

nattlua/analyzer/statements/assignment.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ return {
257257
end
258258

259259
local val = self:SetLocalOrGlobalValue(key, val)
260+
self:MapTypeToNode(val, exp_key)
260261

261262
if false and val then
262263
-- this is used for tracking function dependencies

nattlua/base_environment.lua

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,13 @@ local DISABLE = _G.DISABLE_BASE_ENV
6464
local REUSE = _G.REUSE_BASE_ENV
6565
local cached_runtime
6666
local cached_typesystem
67+
local cached_node_map
6768
return {
6869
BuildBaseEnvironment = function(root_node, parent_analyzer)
69-
if DISABLE then return Table(), Table() end
70+
if DISABLE then return Table(), Table(), {} end
7071

7172
if REUSE and cached_runtime and cached_typesystem then
72-
return cached_runtime, cached_typesystem
73+
return cached_runtime, cached_typesystem, cached_node_map
7374
end
7475

7576
local compiler = load_definitions(root_node, parent_analyzer and parent_analyzer.config)
@@ -87,6 +88,7 @@ return {
8788

8889
if parent_analyzer then
8990
analyzer = require("nattlua.analyzer.analyzer").New(parent_analyzer.config)
91+
analyzer.type_to_node = parent_analyzer.type_to_node
9092
-- Ensure we're using a common statement count if we want to track it
9193
-- but for base environment it might be noisy. Let's at least record parsed paths.
9294
end
@@ -109,8 +111,9 @@ return {
109111
if REUSE then
110112
cached_runtime = runtime_env
111113
cached_typesystem = typesystem_env
114+
cached_node_map = compiler.analyzer:GetTypeToNodeMap()
112115
end
113116

114-
return runtime_env, typesystem_env
117+
return runtime_env, typesystem_env, compiler.analyzer:GetTypeToNodeMap()
115118
end,
116119
}

nattlua/lexer/token.lua

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ function META:FindType()
9999
do
100100
local node = self.parent
101101

102-
while node and node.parent do
102+
while node do
103103
table_insert(found_parents, node)
104104
node = node.parent
105105
end
@@ -120,19 +120,21 @@ function META:FindType()
120120
for _, node in ipairs(found_parents) do
121121
local found = false
122122

123-
for _, obj in ipairs(node:GetAssociatedTypes()) do
124-
if
125-
(
126-
obj.Type == "string" or
127-
obj.Type == "number"
128-
)
129-
and
130-
tostring(obj:GetData()) == self:GetValueString()
131-
then
132-
133-
else
134-
table_insert(types, obj)
135-
found = true
123+
if node.GetAssociatedTypes then
124+
for _, obj in ipairs(node:GetAssociatedTypes()) do
125+
if
126+
(
127+
obj.Type == "string" or
128+
obj.Type == "number"
129+
)
130+
and
131+
tostring(obj:GetData()) == self:GetValueString()
132+
then
133+
134+
else
135+
table_insert(types, obj)
136+
found = true
137+
end
136138
end
137139
end
138140

@@ -150,7 +152,7 @@ function META:FindUpvalue()
150152

151153
if #types > 0 then
152154
for i, v in ipairs(types) do
153-
local upvalue = v:GetUpvalue()
155+
local upvalue = (v.Type == "upvalue" and v) or (v.GetUpvalue and v:GetUpvalue())
154156

155157
if upvalue then return upvalue end
156158
end

test/helpers/lsp_client.lua

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
local json = require("language_server.json")
2+
local rpc_util = require("language_server.jsonrpc")
3+
local class = require("nattlua.other.class")
4+
local path_util = require("nattlua.other.path")
5+
local META = class.CreateTemplate("lsp_client")
6+
7+
function META.New()
8+
return META.NewObject(
9+
{
10+
last_id = 0,
11+
pending_requests = {},
12+
notifications = {},
13+
responses = {},
14+
working_directory = "/",
15+
}
16+
)
17+
end
18+
19+
function META:SetWorkingDirectory(dir)
20+
self.working_directory = path_util.Normalize(dir)
21+
end
22+
23+
function META:ToLSPPath(path)
24+
return path_util.PathToUrlScheme(path_util.Normalize(path))
25+
end
26+
27+
function META:ToFSPath(url)
28+
return path_util.UrlSchemeToPath(url, self.working_directory)
29+
end
30+
31+
function META:Call(lsp, method, params)
32+
self.last_id = self.last_id + 1
33+
local id = self.last_id
34+
local request = {
35+
jsonrpc = "2.0",
36+
id = id,
37+
method = method,
38+
params = params,
39+
}
40+
self.pending_requests[id] = request
41+
-- Mock lsp.Call to capture output
42+
local old_call = lsp.Call
43+
lsp.Call = function(response)
44+
if response.id then
45+
self.responses[response.id] = response
46+
else
47+
table.insert(self.notifications, response)
48+
end
49+
end
50+
-- Simple direct call to the method handler
51+
local res = lsp.methods[method](params)
52+
-- Restore old call
53+
lsp.Call = old_call
54+
55+
if res then return res end
56+
57+
return self.responses[id] and self.responses[id].result
58+
end
59+
60+
function META:Notify(lsp, method, params)
61+
local notification = {
62+
jsonrpc = "2.0",
63+
method = method,
64+
params = params,
65+
}
66+
-- Mock lsp.Call to capture side-effects (like publishDiagnostics)
67+
local old_call = lsp.Call
68+
lsp.Call = function(response)
69+
if not response.id then table.insert(self.notifications, response) end
70+
end
71+
72+
if lsp.methods[method] then lsp.methods[method](params) end
73+
74+
lsp.Call = old_call
75+
end
76+
77+
function META:GetNotifications(method)
78+
local found = {}
79+
80+
for _, n in ipairs(self.notifications) do
81+
if n.method == method then table.insert(found, n) end
82+
end
83+
84+
return found
85+
end
86+
87+
function META:ClearNotifications()
88+
self.notifications = {}
89+
end
90+
91+
function META:Initialize(lsp, root_uri)
92+
return self:Call(
93+
lsp,
94+
"initialize",
95+
{
96+
workspaceFolders = {{uri = root_uri, name = "test-workspace"}},
97+
capabilities = {},
98+
}
99+
)
100+
end
101+
102+
return META

0 commit comments

Comments
 (0)