Skip to content

Commit 2f3e250

Browse files
committed
actual lsp cancelation support by using coroutines
better split of analyzing and parsing mode add minimal lsp call output channel
1 parent e24776b commit 2f3e250

14 files changed

Lines changed: 1207 additions & 100 deletions

File tree

language_server/editor_helper.lua

Lines changed: 222 additions & 44 deletions
Large diffs are not rendered by default.

language_server/lsp.lua

Lines changed: 229 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,30 @@ local b64 = require("language_server.base64")
33
local EditorHelper = require("language_server.editor_helper")
44
local formating = require("nattlua.other.formating")
55
local path = require("nattlua.other.path")
6+
local coroutine_running = coroutine.running
7+
local coroutine_yield = coroutine.yield
68
local lsp = {}
79
lsp.methods = {}
10+
lsp.cancelled_requests = {}
11+
12+
function lsp.CancelRequest(id)
13+
if id ~= nil then lsp.cancelled_requests[id] = true end
14+
end
15+
16+
function lsp.ClearCancelledRequest(id)
17+
if id ~= nil then lsp.cancelled_requests[id] = nil end
18+
end
19+
20+
function lsp.IsRequestCancelled(id)
21+
return id ~= nil and lsp.cancelled_requests[id] == true or false
22+
end
23+
24+
function lsp.Checkpoint()
25+
local thread, is_main = coroutine_running()
26+
27+
if thread and not is_main then coroutine_yield() end
28+
end
29+
830
local TextDocumentSyncKind = {None = 0, Full = 1, Incremental = 2}
931
local SemanticTokenTypes = {
1032
-- identifiers or reference
@@ -66,6 +88,177 @@ end
6688
local editor_helper = EditorHelper.New()
6789
lsp.editor_helper = editor_helper
6890
editor_helper.debug = false
91+
editor_helper.OnYield = function()
92+
return lsp.Checkpoint()
93+
end
94+
local semantic_token_cache = {
95+
next_id = 0,
96+
by_path = {},
97+
by_result_id = {},
98+
}
99+
100+
local function copy_integer_array(data)
101+
local copy = {}
102+
103+
for i = 1, #data do
104+
copy[i] = data[i]
105+
end
106+
107+
return copy
108+
end
109+
110+
local function arrays_equal(a, b)
111+
if #a ~= #b then return false end
112+
113+
for i = 1, #a do
114+
if a[i] ~= b[i] then return false end
115+
end
116+
117+
return true
118+
end
119+
120+
local function next_semantic_result_id()
121+
semantic_token_cache.next_id = semantic_token_cache.next_id + 1
122+
return tostring(semantic_token_cache.next_id)
123+
end
124+
125+
local function store_semantic_tokens(fs_path, data)
126+
fs_path = path.Normalize(fs_path)
127+
local existing = semantic_token_cache.by_path[fs_path]
128+
129+
if existing and arrays_equal(existing.data, data) then return existing.result_id, existing.data end
130+
131+
local result_id = next_semantic_result_id()
132+
local stored = {
133+
result_id = result_id,
134+
data = copy_integer_array(data),
135+
}
136+
semantic_token_cache.by_path[fs_path] = stored
137+
semantic_token_cache.by_result_id[result_id] = {
138+
path = fs_path,
139+
result_id = result_id,
140+
data = stored.data,
141+
}
142+
return result_id, stored.data
143+
end
144+
145+
local function clear_semantic_tokens(fs_path)
146+
fs_path = path.Normalize(fs_path)
147+
local existing = semantic_token_cache.by_path[fs_path]
148+
149+
if existing then semantic_token_cache.by_result_id[existing.result_id] = nil end
150+
151+
semantic_token_cache.by_path[fs_path] = nil
152+
end
153+
154+
local function compute_semantic_token_edit(old_data, new_data)
155+
local prefix = 0
156+
local old_len = #old_data
157+
local new_len = #new_data
158+
local max_prefix = math.min(old_len, new_len)
159+
160+
while prefix < max_prefix and old_data[prefix + 1] == new_data[prefix + 1] do
161+
prefix = prefix + 1
162+
end
163+
164+
local suffix = 0
165+
local max_suffix = math.min(old_len - prefix, new_len - prefix)
166+
167+
while suffix < max_suffix and old_data[old_len - suffix] == new_data[new_len - suffix] do
168+
suffix = suffix + 1
169+
end
170+
171+
local delete_count = old_len - prefix - suffix
172+
local inserted = {}
173+
174+
for i = prefix + 1, new_len - suffix do
175+
inserted[#inserted + 1] = new_data[i]
176+
end
177+
178+
return {
179+
start = prefix,
180+
deleteCount = delete_count,
181+
data = inserted,
182+
}
183+
end
184+
185+
local function build_semantic_tokens_full(fs_path)
186+
if editor_helper:ShouldDeferInteractiveRefresh(fs_path) then
187+
local normalized_path = path.Normalize(fs_path)
188+
local existing = semantic_token_cache.by_path[normalized_path]
189+
190+
if existing then
191+
return {
192+
data = existing.data,
193+
resultId = existing.result_id,
194+
}
195+
end
196+
197+
return {data = {}}
198+
end
199+
200+
if not editor_helper:EnsureParsed(fs_path) then return {data = {}} end
201+
local result_id, data = store_semantic_tokens(fs_path, editor_helper:GetSemanticTokens(fs_path))
202+
return {
203+
data = data,
204+
resultId = result_id,
205+
}
206+
end
207+
208+
local function build_semantic_tokens_delta(fs_path, previous_result_id)
209+
if editor_helper:ShouldDeferInteractiveRefresh(fs_path) then
210+
local normalized_path = path.Normalize(fs_path)
211+
local previous = semantic_token_cache.by_result_id[previous_result_id]
212+
local existing = semantic_token_cache.by_path[normalized_path]
213+
214+
if previous and existing and previous.path == normalized_path then
215+
if previous.result_id == existing.result_id or arrays_equal(previous.data, existing.data) then
216+
return {
217+
edits = {},
218+
resultId = existing.result_id,
219+
}
220+
end
221+
222+
return {
223+
edits = {compute_semantic_token_edit(previous.data, existing.data)},
224+
resultId = existing.result_id,
225+
}
226+
end
227+
228+
if existing then
229+
return {
230+
data = existing.data,
231+
resultId = existing.result_id,
232+
}
233+
end
234+
235+
return previous and {
236+
edits = {},
237+
resultId = previous_result_id,
238+
} or {data = {}}
239+
end
240+
241+
if not editor_helper:EnsureParsed(fs_path) then return {data = {}} end
242+
243+
local previous = semantic_token_cache.by_result_id[previous_result_id]
244+
245+
if not previous or previous.path ~= fs_path then return build_semantic_tokens_full(fs_path) end
246+
247+
local latest_data = editor_helper:GetSemanticTokens(fs_path)
248+
local result_id, stored_data = store_semantic_tokens(fs_path, latest_data)
249+
250+
if previous_result_id == result_id or arrays_equal(previous.data, stored_data) then
251+
return {
252+
edits = {},
253+
resultId = result_id,
254+
}
255+
end
256+
257+
return {
258+
edits = {compute_semantic_token_edit(previous.data, stored_data)},
259+
resultId = result_id,
260+
}
261+
end
69262

70263
local function to_fs_path(url)
71264
return path.UrlSchemeToPath(url, editor_helper:GetWorkingDirectory())
@@ -167,7 +360,7 @@ lsp.methods["initialize"] = function(params)
167360
tokenTypes = SemanticTokenTypes,
168361
tokenModifiers = SemanticTokenModifiers,
169362
},
170-
full = true,
363+
full = {delta = true},
171364
range = false,
172365
},
173366
hoverProvider = true,
@@ -282,18 +475,14 @@ lsp.methods["shutdown"] = function(params)
282475
end
283476
lsp.methods["textDocument/semanticTokens/full"] = function(params)
284477
local path = to_fs_path(params.textDocument.uri)
285-
-- this is not the right place to do this I guess, but it's more reliable and simple
286-
lsp.PublishDecorations(path)
287-
if not editor_helper:EnsureLoaded(path) then return {data = {}} end
288-
return {data = editor_helper:GetSemanticTokens(path)}
478+
return build_semantic_tokens_full(path)
479+
end
480+
lsp.methods["textDocument/semanticTokens/full/delta"] = function(params)
481+
local path = to_fs_path(params.textDocument.uri)
482+
return build_semantic_tokens_delta(path, params.previousResultId)
289483
end
290484
lsp.methods["$/cancelRequest"] = function(params)
291-
do
292-
return
293-
end
294-
295-
print("cancelRequest")
296-
table.print(params)
485+
if params and params.id ~= nil then lsp.CancelRequest(params.id) end
297486
end
298487
lsp.methods["workspace/didChangeConfiguration"] = function(params)
299488
if params.settings and params.settings.nattlua then
@@ -322,7 +511,9 @@ lsp.methods["textDocument/didOpen"] = function(params)
322511
editor_helper:OpenFile(path, params.textDocument.text)
323512
end
324513
lsp.methods["textDocument/didClose"] = function(params)
325-
editor_helper:CloseFile(to_fs_path(params.textDocument.uri))
514+
local path = to_fs_path(params.textDocument.uri)
515+
clear_semantic_tokens(path)
516+
editor_helper:CloseFile(path)
326517
end
327518
lsp.methods["textDocument/didChange"] = function(params)
328519
editor_helper:UpdateFile(to_fs_path(params.textDocument.uri), params.contentChanges[1].text)
@@ -334,7 +525,7 @@ end
334525
lsp.methods["textDocument/references"] = function(params)
335526
local path = to_fs_path(params.textDocument.uri)
336527

337-
if not editor_helper:EnsureLoaded(path) then return {} end
528+
if not editor_helper:EnsureAnalyzed(path) then return {} end
338529

339530
local items = editor_helper:GetReferences(path, params.position.line, params.position.character)
340531

@@ -400,7 +591,7 @@ do
400591
lsp.methods["textDocument/completion"] = function(params)
401592
local path = to_fs_path(params.textDocument.uri)
402593

403-
if not editor_helper:EnsureLoaded(path) then
594+
if not editor_helper:EnsureAnalyzed(path) then
404595
return {isIncomplete = false, items = {}}
405596
end
406597

@@ -442,7 +633,9 @@ end
442633
lsp.methods["textDocument/inlayHint"] = function(params)
443634
local path = to_fs_path(params.textDocument.uri)
444635

445-
if not editor_helper:EnsureLoaded(path) then return {} end
636+
if editor_helper:ShouldDeferInteractiveRefresh(path) then return {} end
637+
if not editor_helper:EnsureParsed(path) then return {} end
638+
if not editor_helper:IsAnalyzed(path) then return {} end
446639

447640
local result = {}
448641

@@ -522,7 +715,19 @@ do
522715
lsp.methods["textDocument/documentSymbol"] = function(params)
523716
local path = to_fs_path(params.textDocument.uri)
524717

525-
if not editor_helper:EnsureLoaded(path) then return {} end
718+
if editor_helper:ShouldDeferInteractiveRefresh(path) then
719+
if not editor_helper:IsParsed(path) then return {} end
720+
721+
local nodes = editor_helper:GetSymbolTree(path)
722+
723+
for _, node in ipairs(nodes) do
724+
translate(node, path)
725+
end
726+
727+
return nodes
728+
end
729+
730+
if not editor_helper:EnsureParsed(path) then return {} end
526731

527732
local nodes = editor_helper:GetSymbolTree(path)
528733

@@ -582,7 +787,9 @@ end
582787
lsp.methods["textDocument/documentHighlight"] = function(params)
583788
local path = to_fs_path(params.textDocument.uri)
584789

585-
if not editor_helper:EnsureLoaded(path) then return {} end
790+
if editor_helper:ShouldDeferInteractiveRefresh(path) then return {} end
791+
if not editor_helper:EnsureParsed(path) then return {} end
792+
if not editor_helper:IsAnalyzed(path) then return {} end
586793

587794
local highlights = editor_helper:GetUpvalueHighlightRanges(path, params.position.line, params.position.character)
588795

@@ -612,15 +819,15 @@ end
612819
lsp.methods["textDocument/signatureHelp"] = function(params)
613820
local path = to_fs_path(params.textDocument.uri)
614821

615-
if not editor_helper:EnsureLoaded(path) then return {} end
822+
if not editor_helper:EnsureAnalyzed(path) then return {} end
616823

617824
local result = editor_helper:GetSignatureHelp(path, params.position.line, params.position.character)
618825
return result or {signatures = {}, activeSignature = 0, activeParameter = 0}
619826
end
620827
lsp.methods["textDocument/rename"] = function(params)
621828
local fs_path = to_fs_path(params.textDocument.uri)
622829

623-
if not editor_helper:EnsureLoaded(fs_path) then return {} end
830+
if not editor_helper:EnsureAnalyzed(fs_path) then return {} end
624831

625832
local lsp_path = to_lsp_path(params.textDocument.uri)
626833
local edits = {}
@@ -647,7 +854,7 @@ end
647854
lsp.methods["textDocument/definition"] = function(params)
648855
local path = to_fs_path(params.textDocument.uri)
649856

650-
if not editor_helper:EnsureLoaded(path) then return {} end
857+
if not editor_helper:EnsureAnalyzed(path) then return {} end
651858

652859
local node = editor_helper:GetDefinition(path, params.position.line, params.position.character)
653860

@@ -656,7 +863,7 @@ lsp.methods["textDocument/definition"] = function(params)
656863
local path = node:GetSourcePath() or path
657864
path = to_fs_path(path)
658865
editor_helper:OpenFile(path, node.Code:GetString())
659-
if not editor_helper:EnsureLoaded(path) then return {} end
866+
if not editor_helper:EnsureAnalyzed(path) then return {} end
660867
return {
661868
uri = to_lsp_path(path),
662869
range = get_range(editor_helper:GetCode(path), start, stop),
@@ -668,7 +875,7 @@ end
668875
lsp.methods["textDocument/hover"] = function(params)
669876
local path = to_fs_path(params.textDocument.uri)
670877

671-
if not editor_helper:EnsureLoaded(path) then return {} end
878+
if not editor_helper:EnsureAnalyzed(path) then return {} end
672879

673880
local data = editor_helper:GetHover(path, params.position.line, params.position.character)
674881

0 commit comments

Comments
 (0)