From b24c22dd15c0b02847d105b12e2fde72cb301d7d Mon Sep 17 00:00:00 2001 From: Robin Haberkorn Date: Thu, 13 Aug 2026 00:29:23 +0200 Subject: LSP: support ctags-lsp * It sends `null` instead of empty lists, even though this is violating the LSP specs. * Filter out duplicates from the auto-completion lists. This may benefit other servers as well. * ctags-lsp does not fuzzy search for workspace/symbol, so auto-completions aren't really a thing. The only special case is sending the empty string, which results in all symbols being returned. This can be slow, though. * ctags-lsp does not support textDocument/references, so n:FT$ won't be supported. --- src/lsp.c | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/lsp.c b/src/lsp.c index 72ce4d3..2c1f89b 100644 --- a/src/lsp.c +++ b/src/lsp.c @@ -918,7 +918,7 @@ teco_lsp_lookup_symbol(teco_string_t str, gboolean match_exact, GError **error) while (sj_iter_object(&reader, obj, &key, &val) && !teco_json_eq(key, "result")); sj_Value arr = val; - if (arr.type != SJ_ARRAY) { + if (arr.type != SJ_NULL && arr.type != SJ_ARRAY) { g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, "Expected array in response (at byte %zu)", arr.start - reader.data); @@ -962,7 +962,7 @@ teco_lsp_lookup_symbol(teco_string_t str, gboolean match_exact, GError **error) results++; } if (results > 1) - teco_interface_msg(TECO_MSG_INFO, "Found %u references", results); + teco_interface_msg(TECO_MSG_INFO, "Found %u symbols", results); teco_undo_ptr(teco_lsp.current) = (teco_lsp_result_t *)teco_lsp.list.first; return TRUE; @@ -1013,6 +1013,7 @@ teco_lsp_symbol_auto_complete(const gchar *symbol, teco_string_t *insert) !teco_json_eq(key, "result")); sj_Value arr = val; if (arr.type != SJ_ARRAY) + /* can also be `null` in some LSPs */ return FALSE; GSList *list = NULL; @@ -1057,9 +1058,13 @@ teco_lsp_symbol_auto_complete(const gchar *symbol, teco_string_t *insert) } else if (list_len > 1) { list = g_slist_sort(list, (GCompareFunc)strcmp); - for (GSList *entry = list; entry != NULL; entry = g_slist_next(entry)) - teco_interface_popup_add(TECO_POPUP_PLAIN, entry->data, - strlen(entry->data), FALSE); + for (GSList *entry = list; entry != NULL; entry = g_slist_next(entry)) { + const gchar *name = entry->data; + if (g_slist_next(entry) && !strcmp(name, g_slist_next(entry)->data)) + /* filter out duplicates */ + continue; + teco_interface_popup_add(TECO_POPUP_PLAIN, name, strlen(name), FALSE); + } teco_interface_popup_show(symbol_len); } @@ -1111,12 +1116,16 @@ teco_lsp_lookup_definition(teco_buffer_t *buffer, teco_int_t pos, GError **error sj_Value key, val = {0}; while (sj_iter_object(&reader, obj, &key, &val) && !teco_json_eq(key, "result")); + /* + * Some servers can return `null` for an empty list + * or an object for a single result. + */ if (val.type == SJ_OBJECT) { teco_lsp_result_t *result = teco_lsp_parse_location(reader, val, error); if (!result) return FALSE; teco_stailq_insert_tail(&teco_lsp.list, &result->entry); - } else if (val.type != SJ_ARRAY) { + } else if (val.type != SJ_NULL && val.type != SJ_ARRAY) { g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, "Expected array in response (at byte %zu)", val.start - reader.data); @@ -1186,7 +1195,7 @@ teco_lsp_lookup_references(teco_buffer_t *buffer, teco_int_t pos, GError **error while (sj_iter_object(&reader, obj, &key, &val) && !teco_json_eq(key, "result")); sj_Value arr = val; - if (arr.type != SJ_ARRAY) { + if (arr.type != SJ_NULL && arr.type != SJ_ARRAY) { g_set_error(error, TECO_ERROR, TECO_ERROR_FAILED, "Expected array in response (at byte %zu)", arr.start - reader.data); -- cgit v1.2.3