@@ -3,8 +3,30 @@ local b64 = require("language_server.base64")
33local EditorHelper = require (" language_server.editor_helper" )
44local formating = require (" nattlua.other.formating" )
55local path = require (" nattlua.other.path" )
6+ local coroutine_running = coroutine.running
7+ local coroutine_yield = coroutine.yield
68local lsp = {}
79lsp .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+
830local TextDocumentSyncKind = {None = 0 , Full = 1 , Incremental = 2 }
931local SemanticTokenTypes = {
1032 -- identifiers or reference
6688local editor_helper = EditorHelper .New ()
6789lsp .editor_helper = editor_helper
6890editor_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
70263local 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)
282475end
283476lsp .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 )
289483end
290484lsp .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
297486end
298487lsp .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 )
323512end
324513lsp .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 )
326517end
327518lsp .methods [" textDocument/didChange" ] = function (params )
328519 editor_helper :UpdateFile (to_fs_path (params .textDocument .uri ), params .contentChanges [1 ].text )
334525lsp .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
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
442633lsp .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
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
582787lsp .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
612819lsp .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 }
619826end
620827lsp .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 = {}
647854lsp .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 ),
668875lsp .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