Skip to content

Commit 498a8e6

Browse files
committed
fix(sources.lsp): error sorting LSP symbols with the same start position (#255)
1 parent 5c3b0af commit 498a8e6

File tree

2 files changed

+109
-1
lines changed

2 files changed

+109
-1
lines changed

lua/dropbar/sources/lsp.lua

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,14 @@ local function update_symbols(buf, ttl)
336336
s1.range.start.character,
337337
s2.range.start.character
338338

339-
return l1 < l2 or l1 == l2 and c1 <= c2
339+
-- Pitfall: don't use `l1 == l2 and c1 <= c2` here as sort algorithm is
340+
-- not stable and we shouldn't return `true` for two elements with equal
341+
-- total order, i.e. symbols with the same start position (both line &
342+
-- column), else lua will throw the error: 'invalid order function for
343+
-- sorting', see:
344+
-- - https://blog.csdn.net/twwk120120/article/details/102697411
345+
-- - https://www.lua.org/manual/5.4/manual.html#pdf-table.sort
346+
return l1 < l2 or l1 == l2 and c1 < c2
340347
end)
341348
end,
342349
buf

tests/sources/lsp_spec.lua

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,4 +138,105 @@ describe('[source][lsp]', function()
138138
end, symbols[1].siblings)
139139
)
140140
end)
141+
142+
it(
143+
'handles symbols with identical start positions without sorting error',
144+
function()
145+
-- Make sure function type is a valid LSP symbol type so that symbols in
146+
-- mock client response will be included in `get_symbols()` result
147+
dropbar.setup({
148+
sources = {
149+
lsp = {
150+
valid_symbols = {
151+
'Function', -- code = 12
152+
},
153+
},
154+
},
155+
})
156+
157+
-- Mock a client that returns symbols with identical start positions
158+
---@diagnostic disable-next-line: assign-type-mismatch
159+
mock_client.request = function(_, method, _, handler)
160+
if method ~= 'textDocument/documentSymbol' then
161+
return
162+
end
163+
164+
handler(nil, {
165+
{
166+
name = 's2',
167+
kind = 12,
168+
range = {
169+
start = { line = 5, character = 10 },
170+
['end'] = { line = 8, character = 0 },
171+
},
172+
},
173+
{
174+
name = 's3',
175+
kind = 12,
176+
range = {
177+
start = { line = 5, character = 10 }, -- Same position as `s2`
178+
['end'] = { line = 10, character = 0 },
179+
},
180+
},
181+
{
182+
name = 's4',
183+
kind = 12,
184+
range = {
185+
start = { line = 5, character = 10 }, -- Same position as `s2` and `s3`
186+
['end'] = { line = 12, character = 0 },
187+
},
188+
},
189+
{
190+
name = 's1',
191+
kind = 12,
192+
range = {
193+
start = { line = 3, character = 5 },
194+
['end'] = { line = 6, character = 0 },
195+
},
196+
},
197+
}, {
198+
method = 'textDocument/documentSymbol',
199+
client_id = mock_client.id,
200+
})
201+
202+
return true, 1
203+
end
204+
205+
-- Should not throw 'invalid order function for sorting' error
206+
assert.is_true(pcall(function()
207+
-- Trigger LSP update
208+
vim.api.nvim_exec_autocmds(configs.opts.bar.update_events.buf[1], {
209+
buffer = 0,
210+
})
211+
212+
lsp_source.get_symbols(
213+
vim.api.nvim_get_current_buf(),
214+
vim.api.nvim_get_current_win(),
215+
{ 6, 0 }
216+
)
217+
end))
218+
219+
-- Verify siblings are in correct order:
220+
-- `s1` should be the first, followed by `s2/3/4` in any order since
221+
-- they has identical positions
222+
local symbols = lsp_source.get_symbols(
223+
vim.api.nvim_get_current_buf(),
224+
vim.api.nvim_get_current_win(),
225+
{ 6, 0 }
226+
)
227+
local siblings = symbols[1].siblings ---@type dropbar_symbol_t[]
228+
assert.is_truthy(siblings)
229+
assert.are.equal(4, #siblings)
230+
assert.is.same('s1', siblings[1].name)
231+
assert.are.same({
232+
s2 = true,
233+
s3 = true,
234+
s4 = true,
235+
}, {
236+
[siblings[2].name] = true,
237+
[siblings[3].name] = true,
238+
[siblings[4].name] = true,
239+
})
240+
end
241+
)
141242
end)

0 commit comments

Comments
 (0)