diff --git a/lsp/data/lsp.conf b/lsp/data/lsp.conf index e88276872..67486d94b 100644 --- a/lsp/data/lsp.conf +++ b/lsp/data/lsp.conf @@ -115,8 +115,8 @@ autocomplete_window_max_entries=20 # Maximum number of autocompletion entries shown without scrolling (defining the # actual height of the popup) autocomplete_window_max_displayed=8 -# The maximum width of the autocompletion popup in pixels -autocomplete_window_max_width=200 +# The maximum width of the autocompletion popup in displayed characters +autocomplete_window_max_width=60 # Whether to automatically apply additional edits defined for the autocompletion # entry. These are typically imports of the modules where the inserted symbol is # defined @@ -368,6 +368,16 @@ use_without_project=true #show_server_stderr=true +[Nix] +cmd=nil +use_without_project=true +use_outside_project_dir=true +extra_identifier_characters=-' +#rpc_log=stdout +#rpc_log_full=true +#show_server_stderr=true + + [PHP] # Note: Server returns highlighting indices off by 1 and highlighting doesn't work cmd=phpactor.phar language-server diff --git a/lsp/src/lsp-autocomplete.c b/lsp/src/lsp-autocomplete.c index 4eb30cc5a..322c41584 100644 --- a/lsp/src/lsp-autocomplete.c +++ b/lsp/src/lsp-autocomplete.c @@ -94,9 +94,9 @@ static void free_autocomplete_symbol(gpointer data) } -static const gchar *get_symbol_label(LspServer *server, LspAutocompleteSymbol *sym) +static const gchar *get_label(LspAutocompleteSymbol *sym, gboolean use_label) { - if (server->config.autocomplete_use_label && sym->label) + if (use_label && sym->label) return sym->label; if (sym->text_edit && sym->text_edit->new_text) @@ -110,6 +110,32 @@ static const gchar *get_symbol_label(LspServer *server, LspAutocompleteSymbol *s } +static gchar *get_symbol_label(LspServer *server, LspAutocompleteSymbol *sym) +{ + gchar *label = g_strdup(get_label(sym, server->config.autocomplete_use_label)); + gchar *pos; + + // remove stuff after newlines (we don't want them in the popup plus \n + // is used as the autocompletion list separator) + pos = strchr(label, '\n'); + if (pos) + *pos = '\0'; + pos = strchr(label, '\r'); + if (pos) + *pos = '\0'; + + // ? used by Scintilla for icon specification + pos = strchr(label, '?'); + if (pos) + *pos = ' '; + pos = strchr(label, '\t'); + if (pos) + *pos = ' '; + + return label; +} + + static guint get_ident_prefixlen(const gchar *word_chars, GeanyDocument *doc, gint pos) { ScintillaObject *sci = doc->editor->sci; @@ -150,9 +176,9 @@ void lsp_autocomplete_item_selected(LspServer *server, GeanyDocument *doc, guint if (sel_num == 1 && sym->text_edit && sent_request_id == received_request_id) { if (server->config.autocomplete_apply_additional_edits && sym->additional_edits) - lsp_utils_apply_text_edits(sci, sym->text_edit, sym->additional_edits); + lsp_utils_apply_text_edits(sci, sym->text_edit, sym->additional_edits, sym->is_snippet); else - lsp_utils_apply_text_edit(sci, sym->text_edit, TRUE); + lsp_utils_apply_text_edit(sci, sym->text_edit, sym->is_snippet); } else { @@ -172,7 +198,7 @@ void lsp_autocomplete_item_selected(LspServer *server, GeanyDocument *doc, guint text_edit.range.start = lsp_utils_scintilla_pos_to_lsp(sci, pos - rootlen); text_edit.range.end = lsp_utils_scintilla_pos_to_lsp(sci, pos); - lsp_utils_apply_text_edit(sci, &text_edit, sym->insert_text != NULL); + lsp_utils_apply_text_edit(sci, &text_edit, sym->is_snippet); } } @@ -211,7 +237,7 @@ static void show_tags_list(LspServer *server, GeanyDocument *doc, GPtrArray *sym ScintillaObject *sci = doc->editor->sci; gint pos = sci_get_current_position(sci); GString *words = g_string_sized_new(2000); - const gchar *label; + gchar *label; for (i = 0; i < symbols->len; i++) { @@ -230,6 +256,8 @@ static void show_tags_list(LspServer *server, GeanyDocument *doc, GPtrArray *sym sprintf(buf, "?%u", icon_id + 1); g_string_append(words, buf); + + g_free(label); } lsp_autocomplete_set_displayed_symbols(symbols); @@ -242,6 +270,7 @@ static void show_tags_list(LspServer *server, GeanyDocument *doc, GPtrArray *sym //make sure Scintilla selects the first item - see https://sourceforge.net/p/scintilla/bugs/2403/ label = get_symbol_label(server, symbols->pdata[0]); SSM(sci, SCI_AUTOCSELECT, 0, (sptr_t)label); + g_free(label); } #endif @@ -353,6 +382,27 @@ static gint sort_autocomplete_symbols(gconstpointer a, gconstpointer b, gpointer } +static gboolean should_add(GPtrArray *symbols, const gchar *prefix) +{ + LspAutocompleteSymbol *sym; + const gchar *label; + + if (symbols->len == 0) + return FALSE; + + if (symbols->len > 1) + return TRUE; + + // don't single value with what's already typed unless it's a snippet + sym = symbols->pdata[0]; + label = get_label(sym, FALSE); + if (g_strcmp0(label, prefix) != 0) + return TRUE; + + return sym->is_snippet || sym->kind == LspCompletionKindSnippet; +} + + static void process_response(LspServer *server, GVariant *response, GeanyDocument *doc) { //gboolean is_incomplete = FALSE; @@ -440,14 +490,17 @@ static void process_response(LspServer *server, GVariant *response, GeanyDocumen for (i = 0; i < symbols->len; i++) { LspAutocompleteSymbol *sym = symbols->pdata[i]; - const gchar *display_label = get_symbol_label(server, sym); + gchar *display_label = get_symbol_label(server, sym); if (g_hash_table_contains(entry_set, display_label)) + { free_autocomplete_symbol(sym); + g_free(display_label); + } else { g_ptr_array_add(symbols_filtered, sym); - g_hash_table_insert(entry_set, g_strdup(display_label), NULL); + g_hash_table_insert(entry_set, display_label, NULL); } } @@ -460,7 +513,7 @@ static void process_response(LspServer *server, GVariant *response, GeanyDocumen /* sort with symbols matching the typed prefix first */ g_ptr_array_sort_with_data(symbols, sort_autocomplete_symbols, &sort_data); - if (symbols->len > 0) + if (should_add(symbols, sort_data.prefix)) show_tags_list(server, doc, symbols); else { diff --git a/lsp/src/lsp-extension.c b/lsp/src/lsp-extension.c index 87326f642..1b0b30d0e 100644 --- a/lsp/src/lsp-extension.c +++ b/lsp/src/lsp-extension.c @@ -38,7 +38,8 @@ static void goto_cb(GVariant *return_value, GError *error, gpointer user_data) { gchar *fname = lsp_utils_get_real_path_from_uri_locale(str); - document_open_file(fname, FALSE, NULL, NULL); + if (fname) + document_open_file(fname, FALSE, NULL, NULL); g_free(fname); } } diff --git a/lsp/src/lsp-format.c b/lsp/src/lsp-format.c index d7640d6e4..e1cc4f63b 100644 --- a/lsp/src/lsp-format.c +++ b/lsp/src/lsp-format.c @@ -49,7 +49,7 @@ static void format_cb(GVariant *return_value, GError *error, gpointer user_data) edits = lsp_utils_parse_text_edits(&iter); sci_start_undo_action(doc->editor->sci); - lsp_utils_apply_text_edits(doc->editor->sci, NULL, edits); + lsp_utils_apply_text_edits(doc->editor->sci, NULL, edits, FALSE); sci_end_undo_action(doc->editor->sci); g_ptr_array_free(edits, TRUE); diff --git a/lsp/src/lsp-goto.c b/lsp/src/lsp-goto.c index 1805ef587..125c4a002 100644 --- a/lsp/src/lsp-goto.c +++ b/lsp/src/lsp-goto.c @@ -45,7 +45,10 @@ GPtrArray *last_result; static void goto_location(GeanyDocument *old_doc, LspLocation *loc) { gchar *fname = lsp_utils_get_real_path_from_uri_locale(loc->uri); - GeanyDocument *doc = document_open_file(fname, FALSE, NULL, NULL); + GeanyDocument *doc = NULL; + + if (fname) + doc = document_open_file(fname, FALSE, NULL, NULL); if (doc) navqueue_goto_line(old_doc, doc, loc->range.start.line + 1); @@ -70,13 +73,19 @@ static void filter_symbols(const gchar *filter) static void show_in_msgwin(LspLocation *loc, GHashTable *sci_table) { - gchar *fname = lsp_utils_get_real_path_from_uri_utf8(loc->uri); - gchar *base_path = lsp_utils_get_project_base_path(); - GeanyDocument *doc = document_find_by_filename(fname); ScintillaObject *sci = NULL; gint lineno = loc->range.start.line; + gchar *fname, *base_path; + GeanyDocument *doc; gchar *line_str; + fname = lsp_utils_get_real_path_from_uri_utf8(loc->uri); + if (!fname) + return; + + doc = document_find_by_filename(fname); + base_path = lsp_utils_get_project_base_path(); + if (doc) sci = doc->editor->sci; else @@ -178,6 +187,9 @@ static void goto_cb(GVariant *return_value, GError *error, gpointer user_data) LspSymbol *sym; file_name = lsp_utils_get_real_path_from_uri_utf8(loc->uri); + if (!file_name) + continue; + name = g_path_get_basename(file_name); sym = lsp_symbol_new(name, "", "", file_name, 0, 0, loc->range.start.line + 1, 0, diff --git a/lsp/src/lsp-rpc.c b/lsp/src/lsp-rpc.c index 86d8f85f3..c26d74bfe 100644 --- a/lsp/src/lsp-rpc.c +++ b/lsp/src/lsp-rpc.c @@ -268,9 +268,12 @@ static GVariant *show_document(LspServer *srv, GVariant *params) else if (g_str_has_prefix(uri, "file://")) { gchar *fname = lsp_utils_get_real_path_from_uri_locale(uri); - document_open_file(fname, FALSE, NULL, NULL); - g_free(fname); - success = TRUE; + if (fname) + { + document_open_file(fname, FALSE, NULL, NULL); + g_free(fname); + success = TRUE; + } } } @@ -487,16 +490,19 @@ void lsp_rpc_notify(LspServer *srv, const gchar *method, GVariant *params, method, params, NULL, NULL); /* Two hacks in one: - * 1. gopls requires that the params member is present (jsonrpc-glib removes - * it when there are no parameters which is jsonrpc compliant) - * 2. haskell-language-server also requires params present _unless_ it's the - * "exit" notifications, where, if "params" present, it fails to - * terminate + * 1. some servers (e.g. gopls) require that the params member is present + * (jsonrpc-glib removes it when there are no parameters which is jsonrpc + * compliant) + * 2. haskell-language-server or nil require that the "exit" notification + * has no params member */ - if (!params && !(srv->filetype == GEANY_FILETYPES_HASKELL && g_strcmp0(method, "exit") == 0)) + if (!params && g_strcmp0(method, "exit") != 0) { - params = JSONRPC_MESSAGE_NEW("gopls_bug_workarond", - JSONRPC_MESSAGE_PUT_STRING("https://github.com/golang/go/issues/57459")); + GVariantDict dict; + + g_variant_dict_init(&dict, NULL); + params = g_variant_take_ref(g_variant_dict_end(&dict)); + params_added = TRUE; } diff --git a/lsp/src/lsp-server.c b/lsp/src/lsp-server.c index bb12d3ad1..4e02397a8 100644 --- a/lsp/src/lsp-server.c +++ b/lsp/src/lsp-server.c @@ -365,6 +365,52 @@ static gboolean use_incremental_sync(GVariant *node) } +static gboolean send_did_save(GVariant *node) +{ + gboolean val; + gboolean success = JSONRPC_MESSAGE_PARSE(node, + "capabilities", "{", + "textDocumentSync", "{", + "save", JSONRPC_MESSAGE_GET_BOOLEAN(&val), + "}", + "}"); + + if (!success) + { + GVariant *var = NULL; + + JSONRPC_MESSAGE_PARSE(node, + "capabilities", "{", + "textDocumentSync", "{", + "save", JSONRPC_MESSAGE_GET_VARIANT(&var), + "}", + "}"); + + success = val = var != NULL; + if (var) + g_variant_unref(var); + } + + return success && val; +} + + +static gboolean include_text_on_save(GVariant *node) +{ + gboolean val; + gboolean success = JSONRPC_MESSAGE_PARSE(node, + "capabilities", "{", + "textDocumentSync", "{", + "save", "{", + "includeText", JSONRPC_MESSAGE_GET_BOOLEAN(&val), + "}", + "}", + "}"); + + return success && val; +} + + static gboolean use_workspace_folders(GVariant *node) { gboolean change_notifications = FALSE; @@ -492,6 +538,8 @@ static void initialize_cb(GVariant *return_value, GError *error, gpointer user_d update_config(return_value, &s->supports_workspace_symbols, "workspaceSymbolProvider"); s->use_incremental_sync = use_incremental_sync(return_value); + s->send_did_save = send_did_save(return_value); + s->include_text_on_save = include_text_on_save(return_value); s->use_workspace_folders = use_workspace_folders(return_value); s->initialize_response = lsp_utils_json_pretty_print(return_value); @@ -554,8 +602,6 @@ static void perform_initialize(LspServer *server) "}", "textDocument", "{", "synchronization", "{", - "willSave", JSONRPC_MESSAGE_PUT_BOOLEAN(FALSE), - "willSaveWaitUntil", JSONRPC_MESSAGE_PUT_BOOLEAN(FALSE), "didSave", JSONRPC_MESSAGE_PUT_BOOLEAN(TRUE), "}", "completion", "{", @@ -587,7 +633,6 @@ static void perform_initialize(LspServer *server) "}", "semanticTokens", "{", "requests", "{", - "range", JSONRPC_MESSAGE_PUT_BOOLEAN(FALSE), "full", "{", "delta", JSONRPC_MESSAGE_PUT_BOOLEAN(TRUE), "}", @@ -624,9 +669,6 @@ static void perform_initialize(LspServer *server) "formats", "[", "relative", "]", - "overlappingTokenSupport", JSONRPC_MESSAGE_PUT_BOOLEAN(FALSE), - "multilineTokenSupport", JSONRPC_MESSAGE_PUT_BOOLEAN(FALSE), - "serverCancelSupport", JSONRPC_MESSAGE_PUT_BOOLEAN(FALSE), "augmentsSyntaxTokens", JSONRPC_MESSAGE_PUT_BOOLEAN(TRUE), "}", "}", diff --git a/lsp/src/lsp-server.h b/lsp/src/lsp-server.h index 90e0e9325..e6b15e897 100644 --- a/lsp/src/lsp-server.h +++ b/lsp/src/lsp-server.h @@ -146,6 +146,8 @@ typedef struct LspServer gchar *signature_trigger_chars; gchar *initialize_response; gboolean use_incremental_sync; + gboolean send_did_save; + gboolean include_text_on_save; gboolean use_workspace_folders; gboolean supports_workspace_symbols; diff --git a/lsp/src/lsp-sync.c b/lsp/src/lsp-sync.c index 32276c5a3..24b6082a4 100644 --- a/lsp/src/lsp-sync.c +++ b/lsp/src/lsp-sync.c @@ -133,24 +133,39 @@ void lsp_sync_text_document_did_close(LspServer *server, GeanyDocument *doc) void lsp_sync_text_document_did_save(LspServer *server, GeanyDocument *doc) { + gchar *doc_uri; GVariant *node; - gchar *doc_uri = lsp_utils_get_doc_uri(doc); - gchar *doc_text = sci_get_contents(doc->editor->sci, -1); - node = JSONRPC_MESSAGE_NEW ( - "textDocument", "{", - "uri", JSONRPC_MESSAGE_PUT_STRING(doc_uri), - "}", - "text", JSONRPC_MESSAGE_PUT_STRING(doc_text) - ); + if (!server->send_did_save) + return; + + doc_uri = lsp_utils_get_doc_uri(doc); + + if (server->include_text_on_save) + { + gchar *doc_text = sci_get_contents(doc->editor->sci, -1); + node = JSONRPC_MESSAGE_NEW ( + "textDocument", "{", + "uri", JSONRPC_MESSAGE_PUT_STRING(doc_uri), + "}", + "text", JSONRPC_MESSAGE_PUT_STRING(doc_text) + ); + g_free(doc_text); + } + else + { + node = JSONRPC_MESSAGE_NEW ( + "textDocument", "{", + "uri", JSONRPC_MESSAGE_PUT_STRING(doc_uri), + "}" + ); + } //printf("%s\n\n\n", lsp_utils_json_pretty_print(node)); lsp_rpc_notify(server, "textDocument/didSave", node, NULL, NULL); g_free(doc_uri); - g_free(doc_text); - g_variant_unref(node); } diff --git a/lsp/src/lsp-utils.c b/lsp/src/lsp-utils.c index 292a822ce..8cbdf02f8 100644 --- a/lsp/src/lsp-utils.c +++ b/lsp/src/lsp-utils.c @@ -164,8 +164,6 @@ gchar *lsp_utils_get_real_path_from_uri_locale(const gchar *uri) SETPTR(fname, utils_get_real_path(fname)); - g_return_val_if_fail(fname, NULL); - return fname; } @@ -449,7 +447,8 @@ static gint sort_edits(gconstpointer a, gconstpointer b) } -void lsp_utils_apply_text_edits(ScintillaObject *sci, LspTextEdit *edit, GPtrArray *edits) +void lsp_utils_apply_text_edits(ScintillaObject *sci, LspTextEdit *edit, GPtrArray *edits, + gboolean process_snippets) { GPtrArray *arr; gint i; @@ -474,7 +473,7 @@ void lsp_utils_apply_text_edits(ScintillaObject *sci, LspTextEdit *edit, GPtrArr for (i = 0; i < arr->len; i++) { LspTextEdit *e = arr->pdata[i]; - lsp_utils_apply_text_edit(sci, e, e == edit); + lsp_utils_apply_text_edit(sci, e, process_snippets); } g_ptr_array_free(arr, TRUE); @@ -497,7 +496,7 @@ static void apply_edits_in_file(const gchar *uri, GPtrArray *edits) sci = lsp_utils_new_sci_from_file(fname); sci_start_undo_action(sci); - lsp_utils_apply_text_edits(sci, NULL, edits); + lsp_utils_apply_text_edits(sci, NULL, edits, FALSE); sci_end_undo_action(sci); if (!doc) @@ -1074,6 +1073,7 @@ gchar *lsp_utils_process_snippet(const gchar *snippet, GSList **positions) else // something invalid { state = SnippetOuter; + g_string_append_c(res, '$'); g_string_append_c(res, c); } break; diff --git a/lsp/src/lsp-utils.h b/lsp/src/lsp-utils.h index 25aeeea99..86bfe54ea 100644 --- a/lsp/src/lsp-utils.h +++ b/lsp/src/lsp-utils.h @@ -105,7 +105,8 @@ LspLocation *lsp_utils_parse_location(GVariant *variant); GPtrArray *lsp_utils_parse_locations(GVariantIter *iter); void lsp_utils_apply_text_edit(ScintillaObject *sci, LspTextEdit *e, gboolean process_snippets); -void lsp_utils_apply_text_edits(ScintillaObject *sci, LspTextEdit *edit, GPtrArray *edits); +void lsp_utils_apply_text_edits(ScintillaObject *sci, LspTextEdit *edit, GPtrArray *edits, + gboolean process_snippets); gboolean lsp_utils_apply_workspace_edit(GVariant *workspace_edit); gboolean lsp_utils_wrap_string(gchar *string, gint wrapstart);