Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Sync to latest version from geany-lsp
Browse files Browse the repository at this point in the history
techee committed Nov 27, 2024

Unverified

This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
1 parent 55232c5 commit ad5f3eb
Showing 17 changed files with 1,184 additions and 284 deletions.
22 changes: 21 additions & 1 deletion lsp/README
Original file line number Diff line number Diff line change
@@ -205,6 +205,13 @@ When you click on a symbol in the document, this feature highlights all its
occurrences in the document. You can customize the highlighting style to your
preference by configuring it in the configuration file.

Smart selection expanding/shrinking
-----------------------------------

This feature allows to expand the current text selection to contain the next
upper syntactic element such as a parent block in programming languages or a
parent tag in XML. Selection shrinking works in the opposite direction.

Document symbol renaming
------------------------

@@ -224,7 +231,20 @@ servers may not be completely reliable when performing the rename so be very
cautious when using it. The plugin does not perform any additional
checks and does not show any preview of the changes so it is best to use this
feature only after committing all modified files so you can
easily revert to a working state if needed.
easily revert to a working state if needed. Since this is potentially a
dangerous operation, to prevent accidental renames, the "Rename" button in the
dialog is not selected by defalut and simply pressing enter just cancels the
dialog.

Limitations
===========

By design, the plugin communicates over stdin/stdout only, is responsible
for launching and terminating the language server process, and supports only
a single language server per file type.

All of these limitations are addressed by the LSP proxy project available at
https://github.com/techee/lsp-proxy and related issues should be directed there.

License
=======
62 changes: 60 additions & 2 deletions lsp/data/lsp.conf
Original file line number Diff line number Diff line change
@@ -133,6 +133,11 @@ autocomplete_trigger_sequences=
# 'begin' and 'end' which are typically followed by a newline where typing enter
# after these words might select some unwanted word from the autocompletion list.
autocomplete_hide_after_words=
# Whether to perform autocompletion inside strings
autocomplete_in_strings=false
# Show documentation (if available) of selected item in autocompletion popup
# in Geany status bar
autocomplete_show_documentation=true

# Whether LSP should be used to display diagnostic messages. Typically these are
# compiler errors or warnings
@@ -237,12 +242,16 @@ format_on_save=false
# when servers do not correctly terminate progress notifications.
progress_bar_enable=true

# Enable non-standard clangd extension allowing to swap between C/C++ headers
# and sources. Only usable for clangd, it does not work with other servers.
swap_header_source_enable=false


# This is a dummy language server configuration describing the available
# language-specific options. Most of the configuration options from the [all]
# section can be used here as well.
# For an extensive list of various servers and their configurations, check
# https://github.com/neovim/nvim-lspconfig/blob/master/doc/server_configurations.md
# https://github.com/neovim/nvim-lspconfig/blob/master/doc/configs.md
# While the configuration options names of neovim differ from Geany, the
# general concepts are similar and applicable here.
[DummyLanguage]
@@ -281,6 +290,8 @@ lang_id_mappings=dummylanguage;*.dummy
# first). The compile_commands.json file has to be manually regenerated when
# the build is modified in any way, such as a file is added/removed.
cmd=clangd
swap_header_source_enable=true
autocomplete_in_strings=true
autocomplete_use_label=false
semantic_tokens_enable=true
#initialization_options={"compilationDatabasePath": "/home/some_user/my_project/my_builddir"}
@@ -297,6 +308,28 @@ command_1_regex=Apply fix:.*
use=C


[CSS]
cmd=vscode-css-language-server --stdio
extra_identifier_characters=-
send_did_change_configuration=true
autocomplete_use_snippets=true
use_without_project=true
use_outside_project_dir=true
#rpc_log=stdout
#rpc_log_full=true
#show_server_stderr=true


[Dart]
cmd=dart language-server --protocol=lsp
# everything except ( which conflicts with signature help
autocomplete_trigger_sequences=.;=;$;";';{;/;:
semantic_tokens_enable=true
#rpc_log=stdout
#rpc_log_full=true
#show_server_stderr=true


[Go]
cmd=gopls
autocomplete_apply_additional_edits=true
@@ -324,6 +357,18 @@ semantic_tokens_enable=false
#show_server_stderr=true


[HTML]
cmd=vscode-html-language-server --stdio
extra_identifier_characters=&
send_did_change_configuration=true
autocomplete_use_snippets=true
use_without_project=true
use_outside_project_dir=true
#rpc_log=stdout
#rpc_log_full=true
#show_server_stderr=true


[Java]
cmd=jdtls
autocomplete_use_label=false
@@ -391,8 +436,11 @@ extra_identifier_characters=$
[Python]
# pip install pyright (or: pipx install pyright)
cmd=pyright-langserver --stdio
# alternatively pylsp
cmd=pyright-langserver --stdio
# alternatively pylsp, jedi, ruff
#cmd=pylsp
#cmd=jedi-language-server
#cmd=ruff server
use_outside_project_dir=true
use_without_project=true
#rpc_log=stdout
@@ -446,6 +494,7 @@ autocomplete_use_snippets=true
diagnostics_statusbar_severity=4
use_without_project=true
use_outside_project_dir=true
autocomplete_in_strings=true
# see https://github.com/eclipse/lemminx/blob/main/docs/Configuration.md
#initialization_options_file=/home/some_user/init_options.json
#formatting_options={ "tabSize": 4, "insertSpaces": true }
@@ -465,6 +514,15 @@ use_outside_project_dir=true
#show_server_stderr=true


[Zig]
cmd=zls
semantic_tokens_enable=true
#autocomplete_use_snippets=true
#rpc_log=stdout
#rpc_log_full=true
#show_server_stderr=true


# TODO: help needed! Only the above defined language servers have been tested
# (lightly). If you know some other working language server or find a problem
# with the settings above, please open an issue report or a pull request
2 changes: 2 additions & 0 deletions lsp/src/Makefile.am
Original file line number Diff line number Diff line change
@@ -37,6 +37,8 @@ lsp_la_SOURCES = \
lsp-rpc.h \
lsp-semtokens.c \
lsp-semtokens.h \
lsp-selection-range.c \
lsp-selection-range.h \
lsp-server.c \
lsp-server.h \
lsp-signature.c \
282 changes: 252 additions & 30 deletions lsp/src/lsp-autocomplete.c

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions lsp/src/lsp-autocomplete.h
Original file line number Diff line number Diff line change
@@ -27,6 +27,8 @@ void lsp_autocomplete_completion(LspServer *server, GeanyDocument *doc, gboolean

void lsp_autocomplete_set_displayed_symbols(GPtrArray *symbols);
void lsp_autocomplete_item_selected(LspServer *server, GeanyDocument *doc, guint index);
void lsp_autocomplete_selection_changed(GeanyDocument *doc, const gchar *text);
void lsp_autocomplete_discard_pending_requests();
void lsp_autocomplete_clear_statusbar(void);

#endif /* LSP_AUTOCOMPLETE_H */
200 changes: 139 additions & 61 deletions lsp/src/lsp-command.c
Original file line number Diff line number Diff line change
@@ -32,6 +32,7 @@ typedef struct
{
LspCallback callback;
gpointer user_data;
GeanyDocument *doc;
} CommandData;


@@ -50,10 +51,140 @@ void lsp_command_free(LspCommand *cmd)
g_variant_unref(cmd->arguments);
if (cmd->edit)
g_variant_unref(cmd->edit);
if (cmd->data)
g_variant_unref(cmd->data);
g_free(cmd);
}


static LspCommand *parse_code_action(GVariant *code_action)
{
const gchar *title = NULL;
const gchar *command = NULL;
GVariant *arguments = NULL;
GVariant *edit = NULL;
GVariant *data = NULL;
gboolean is_command;
LspCommand *cmd;

// Can either be Command or CodeAction:
// Command {title: string; command: string; arguments?: LSPAny[];}
// CodeAction {title: string; edit?: WorkspaceEdit; command?: Command; data?: LSPAny[];}

JSONRPC_MESSAGE_PARSE(code_action,
"title", JSONRPC_MESSAGE_GET_STRING(&title)
);

if (!title)
return NULL;

is_command = JSONRPC_MESSAGE_PARSE(code_action,
"command", JSONRPC_MESSAGE_GET_STRING(&command)
);

if (is_command)
{
JSONRPC_MESSAGE_PARSE(code_action,
"arguments", JSONRPC_MESSAGE_GET_VARIANT(&arguments)
);
}
else
{
JSONRPC_MESSAGE_PARSE(code_action,
"command", "{",
"command", JSONRPC_MESSAGE_GET_STRING(&command),
"}"
);

JSONRPC_MESSAGE_PARSE(code_action,
"command", "{",
"arguments", JSONRPC_MESSAGE_GET_VARIANT(&arguments),
"}"
);

JSONRPC_MESSAGE_PARSE(code_action,
"edit", JSONRPC_MESSAGE_GET_VARIANT(&edit)
);

JSONRPC_MESSAGE_PARSE(code_action,
"data", JSONRPC_MESSAGE_GET_VARIANT(&data)
);
}

cmd = g_new0(LspCommand, 1);
cmd->title = g_strdup(title);
cmd->command = g_strdup(command);
cmd->arguments = arguments;
cmd->edit = edit;
cmd->data = data;

return cmd;
}


static void resolve_cb(GVariant *return_value, GError *error, gpointer user_data)
{
CommandData *data = user_data;
gboolean performed = FALSE;

if (!error && data->doc == document_get_current())
{
LspServer *server = lsp_server_get_if_running(data->doc);

if (server)
{
LspCommand *cmd = parse_code_action(return_value);

if (cmd && (cmd->command || cmd->edit))
{
lsp_command_perform(server, cmd, data->callback, data->user_data);
performed = TRUE;
}
}

//printf("%s\n\n\n", lsp_utils_json_pretty_print(return_value));
}

if (!performed)
data->callback(data->user_data);

g_free(data);
}


static void resolve_code_action(LspServer *server, LspCommand *cmd, LspCallback callback, gpointer user_data)
{
GVariant *node;
CommandData *data;

if (cmd->data)
{
GVariantDict dict;

g_variant_dict_init(&dict, NULL);
g_variant_dict_insert_value(&dict, "title", g_variant_new_string(cmd->title));
g_variant_dict_insert_value(&dict, "data", cmd->data);
node = g_variant_take_ref(g_variant_dict_end(&dict));
}
else
{
node = JSONRPC_MESSAGE_NEW(
"title", JSONRPC_MESSAGE_PUT_STRING(cmd->title)
);
}

//printf("%s\n\n\n", lsp_utils_json_pretty_print(node));

data = g_new0(CommandData, 1);
data->callback = callback;
data->user_data = user_data;
data->doc = document_get_current();
lsp_rpc_call(server, "codeAction/resolve", node, resolve_cb, data);

g_variant_unref(node);
}


static void command_cb(GVariant *return_value, GError *error, gpointer user_data)
{
CommandData *data = user_data;
@@ -66,6 +197,12 @@ static void command_cb(GVariant *return_value, GError *error, gpointer user_data

void lsp_command_perform(LspServer *server, LspCommand *cmd, LspCallback callback, gpointer user_data)
{
if (!cmd->command && !cmd->edit)
{
resolve_code_action(server, cmd, callback, user_data);
return;
}

if (cmd->edit)
lsp_utils_apply_workspace_edit(cmd->edit);

@@ -123,69 +260,10 @@ static void code_action_cb(GVariant *return_value, GError *error, gpointer user_

while (g_variant_iter_loop(&iter, "v", &code_action))
{
const gchar *title = NULL;
const gchar *command = NULL;
GVariant *edit = NULL;
LspCommand *cmd;
gboolean is_command;

// Can either be Command or CodeAction:
// Command {title: string; command: string; arguments?: LSPAny[];}
// CodeAction {title: string; edit?: WorkspaceEdit; command?: Command;}

JSONRPC_MESSAGE_PARSE(code_action,
"title", JSONRPC_MESSAGE_GET_STRING(&title)
);

is_command = JSONRPC_MESSAGE_PARSE(code_action,
"command", JSONRPC_MESSAGE_GET_STRING(&command)
);

if (!is_command)
{
JSONRPC_MESSAGE_PARSE(code_action,
"command", "{",
"command", JSONRPC_MESSAGE_GET_STRING(&command),
"}"
);

JSONRPC_MESSAGE_PARSE(code_action,
"edit", JSONRPC_MESSAGE_GET_VARIANT(&edit)
);
}

if (title && (command || edit))
{
GVariant *arguments = NULL;

if (is_command)
{
JSONRPC_MESSAGE_PARSE(code_action,
"arguments", JSONRPC_MESSAGE_GET_VARIANT(&arguments)
);
}
else
{
JSONRPC_MESSAGE_PARSE(code_action,
"command", "{",
"arguments", JSONRPC_MESSAGE_GET_VARIANT(&arguments),
"}"
);
}

cmd = g_new0(LspCommand, 1);
cmd->title = g_strdup(title);
cmd->command = g_strdup(command);
cmd->arguments = arguments;
cmd->edit = edit;
LspCommand *cmd = parse_code_action(code_action);

if (cmd)
g_ptr_array_add(code_actions, cmd);
}
else
{
if (edit)
g_variant_unref(edit);
}
}
}
}
1 change: 1 addition & 0 deletions lsp/src/lsp-command.h
Original file line number Diff line number Diff line change
@@ -30,6 +30,7 @@ typedef struct
gchar *command;
GVariant *arguments;
GVariant *edit;
GVariant *data;
} LspCommand;

typedef gboolean (*CodeActionCallback) (GPtrArray *actions, gpointer user_data);
36 changes: 20 additions & 16 deletions lsp/src/lsp-goto.c
Original file line number Diff line number Diff line change
@@ -143,8 +143,27 @@ static void goto_cb(GVariant *return_value, GError *error, gpointer user_data)
msgwin_switch_tab(MSG_MESSAGE, TRUE);
}

// single location

/* check G_VARIANT_TYPE_DICTIONARY ("a{?*}") before
G_VARIANT_TYPE_ARRAY ("a*") as dictionary is apparently a
subset of array :-( */
if (g_variant_is_of_type(return_value, G_VARIANT_TYPE_DICTIONARY))
{
LspLocation *loc = lsp_utils_parse_location(return_value);

if (loc)
{
if (data->show_in_msgwin)
show_in_msgwin(loc, NULL);
else
goto_location(data->doc, loc);
}

lsp_utils_free_lsp_location(loc);
}
// array of locations
if (g_variant_is_of_type(return_value, G_VARIANT_TYPE_ARRAY))
else if (g_variant_is_of_type(return_value, G_VARIANT_TYPE_ARRAY))
{
GPtrArray *locations = NULL;
GVariantIter iter;
@@ -207,21 +226,6 @@ static void goto_cb(GVariant *return_value, GError *error, gpointer user_data)

g_ptr_array_free(locations, TRUE);
}
//single location
else if (g_variant_is_of_type(return_value, G_VARIANT_TYPE_DICTIONARY))
{
LspLocation *loc = lsp_utils_parse_location(return_value);

if (loc)
{
if (data->show_in_msgwin)
show_in_msgwin(loc, NULL);
else
goto_location(data->doc, loc);
}

lsp_utils_free_lsp_location(loc);
}
}

//printf("%s\n\n\n", lsp_utils_json_pretty_print(return_value));
64 changes: 57 additions & 7 deletions lsp/src/lsp-highlight.c
Original file line number Diff line number Diff line change
@@ -26,6 +26,8 @@

#include <jsonrpc-glib.h>

#define HIGHLIGHT_DIRTY "lsp_highlight_dirty"


typedef struct {
GeanyDocument *doc;
@@ -35,20 +37,25 @@ typedef struct {
} LspHighlightData;


extern GeanyPlugin *geany_plugin;
extern GeanyData *geany_data;

static gint indicator;
static gboolean dirty;
static gint64 last_request_time;
static gint request_source;


void lsp_highlight_clear(GeanyDocument *doc)
{
gboolean dirty = GPOINTER_TO_UINT(plugin_get_document_data(geany_plugin, doc, HIGHLIGHT_DIRTY));
if (dirty)
{
ScintillaObject *sci = doc->editor->sci;

if (indicator > 0)
sci_indicator_set(sci, indicator);
sci_indicator_clear(sci, 0, sci_get_length(sci));
dirty = FALSE;
plugin_set_document_data(geany_plugin, doc, HIGHLIGHT_DIRTY, GUINT_TO_POINTER(FALSE));
}
}

@@ -65,7 +72,7 @@ void lsp_highlight_style_init(GeanyDocument *doc)

if (indicator > 0)
{
dirty = TRUE;
plugin_set_document_data(geany_plugin, doc, HIGHLIGHT_DIRTY, GUINT_TO_POINTER(TRUE));
lsp_highlight_clear(doc);
}
indicator = lsp_utils_set_indicator_style(sci, srv->config.highlighting_style);
@@ -82,7 +89,7 @@ static void highlight_range(GeanyDocument *doc, LspRange range)

if (indicator > 0)
editor_indicator_set_on_range(doc->editor, indicator, start_pos, end_pos);
dirty = TRUE;
plugin_set_document_data(geany_plugin, doc, HIGHLIGHT_DIRTY, GUINT_TO_POINTER(TRUE));
}


@@ -193,6 +200,7 @@ static void send_request(LspServer *server, GeanyDocument *doc, gint pos, gboole
data->highlight = highlight;
lsp_rpc_call(server, "textDocument/documentHighlight", node,
highlight_cb, data);
last_request_time = g_get_monotonic_time();
}
else
lsp_highlight_clear(doc);
@@ -204,14 +212,56 @@ static void send_request(LspServer *server, GeanyDocument *doc, gint pos, gboole
}


void lsp_highlight_send_request(LspServer *server, GeanyDocument *doc)
static gboolean request_idle(gpointer data)
{
GeanyDocument *doc = document_get_current();
LspServer *srv;
gint pos;

request_source = 0;

srv = lsp_server_get_if_running(doc);
if (!srv)
return G_SOURCE_REMOVE;

pos = sci_get_current_position(doc->editor->sci);

send_request(srv, doc, pos, TRUE);

return G_SOURCE_REMOVE;
}


void lsp_highlight_schedule_request(GeanyDocument *doc)
{
gint pos = sci_get_current_position(doc->editor->sci);
LspServer *srv = lsp_server_get_if_running(doc);
gchar *iden;

if (!srv)
return;

if (!doc || !doc->real_path)
iden = lsp_utils_get_current_iden(doc, pos, srv->config.word_chars);
if (!iden)
{
lsp_highlight_clear(doc);
// cancel request because we have an up-to-date information there's nothing
// to highlight
if (request_source != 0)
g_source_remove(request_source);
request_source = 0;
return;
}
g_free(iden);

if (request_source != 0)
g_source_remove(request_source);
request_source = 0;

send_request(server, doc, pos, TRUE);
if (last_request_time == 0 || g_get_monotonic_time() > last_request_time + 300000)
request_idle(NULL);
else
request_source = plugin_timeout_add(geany_plugin, 300, request_idle, NULL);
}


2 changes: 1 addition & 1 deletion lsp/src/lsp-highlight.h
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@

void lsp_highlight_style_init(GeanyDocument *doc);

void lsp_highlight_send_request(LspServer *server, GeanyDocument *doc);
void lsp_highlight_schedule_request(GeanyDocument *doc);

void lsp_highlight_rename(gint pos);

173 changes: 144 additions & 29 deletions lsp/src/lsp-main.c
Original file line number Diff line number Diff line change
@@ -40,6 +40,7 @@
#include "lsp-extension.h"
#include "lsp-workspace-folders.h"
#include "lsp-symbol-tree.h"
#include "lsp-selection-range.h"

#include <sys/time.h>
#include <string.h>
@@ -61,6 +62,7 @@ LspProjectConfigurationType project_configuration_type = UserConfigurationType;
gchar *project_configuration_file;

static gint last_click_pos;
static gboolean session_loaded;


PLUGIN_VERSION_CHECK(248)
@@ -76,32 +78,37 @@ PLUGIN_SET_TRANSLATABLE_INFO(
#define CODE_ACTIONS_PERFORMED "lsp_code_actions_performed"

enum {
KB_GOTO_DEFINITION,
KB_GOTO_DECLARATION,
KB_GOTO_TYPE_DEFINITION,
KB_GOTO_DEFINITION,
KB_GOTO_DECLARATION,
KB_GOTO_TYPE_DEFINITION,

KB_GOTO_ANYWHERE,
KB_GOTO_DOC_SYMBOL,
KB_GOTO_WORKSPACE_SYMBOL,
KB_GOTO_LINE,
KB_GOTO_ANYWHERE,
KB_GOTO_DOC_SYMBOL,
KB_GOTO_WORKSPACE_SYMBOL,
KB_GOTO_LINE,

KB_GOTO_NEXT_DIAG,
KB_GOTO_PREV_DIAG,
KB_SHOW_DIAG,
KB_GOTO_NEXT_DIAG,
KB_GOTO_PREV_DIAG,
KB_SHOW_DIAG,

KB_FIND_IMPLEMENTATIONS,
KB_FIND_REFERENCES,
KB_FIND_IMPLEMENTATIONS,
KB_FIND_REFERENCES,

KB_SHOW_HOVER_POPUP,
KB_SWAP_HEADER_SOURCE,
KB_EXPAND_SELECTION,
KB_SHRINK_SELECTION,

KB_RENAME_IN_FILE,
KB_RENAME_IN_PROJECT,
KB_FORMAT_CODE,
KB_SHOW_HOVER_POPUP,
KB_SHOW_CODE_ACTIONS,

KB_RESTART_SERVERS,
KB_SWAP_HEADER_SOURCE,

KB_COUNT
KB_RENAME_IN_FILE,
KB_RENAME_IN_PROJECT,
KB_FORMAT_CODE,

KB_RESTART_SERVERS,

KB_COUNT
};


@@ -127,7 +134,12 @@ struct
GtkWidget *rename_in_project;
GtkWidget *format_code;

GtkWidget *expand_selection;
GtkWidget *shrink_selection;

GtkWidget *hover_popup;
GtkWidget *code_action_popup;

GtkWidget *header_source;
} menu_items;

@@ -271,6 +283,7 @@ static void update_menu(GeanyDocument *doc)
{
LspServer *srv = lsp_server_get_if_running(doc);
gboolean goto_definition_enable = srv && srv->config.goto_definition_enable;
gboolean selection_range_enable = srv && srv->config.selection_range_enable;
gboolean goto_references_enable = srv && srv->config.goto_references_enable;
gboolean goto_type_definition_enable = srv && srv->config.goto_type_definition_enable;
gboolean document_formatting_enable = srv && srv->config.document_formatting_enable;
@@ -281,6 +294,8 @@ static void update_menu(GeanyDocument *doc)
gboolean goto_implementation_enable = srv && srv->config.goto_implementation_enable;
gboolean diagnostics_enable = srv && srv->config.diagnostics_enable;
gboolean hover_popup_enable = srv && srv->config.hover_available;
gboolean code_action_enable = srv && (srv->config.code_action_enable || srv->config.code_lens_enable);
gboolean swap_header_source_enable = srv && srv->config.swap_header_source_enable;

if (!menu_items.parent_item)
return;
@@ -300,7 +315,13 @@ static void update_menu(GeanyDocument *doc)
gtk_widget_set_sensitive(menu_items.rename_in_project, rename_enable);
gtk_widget_set_sensitive(menu_items.format_code, document_formatting_enable || range_formatting_enable);

gtk_widget_set_sensitive(menu_items.expand_selection, selection_range_enable);
gtk_widget_set_sensitive(menu_items.shrink_selection, selection_range_enable);

gtk_widget_set_sensitive(menu_items.header_source, swap_header_source_enable);

gtk_widget_set_sensitive(menu_items.hover_popup, hover_popup_enable);
gtk_widget_set_sensitive(menu_items.code_action_popup, code_action_enable);
}


@@ -309,11 +330,11 @@ static gboolean on_update_idle(gpointer data)
GeanyDocument *doc = data;
LspServer *srv;

plugin_set_document_data(geany_plugin, doc, UPDATE_SOURCE_DOC_DATA, GUINT_TO_POINTER(0));

if (!DOC_VALID(doc))
return G_SOURCE_REMOVE;

plugin_set_document_data(geany_plugin, doc, UPDATE_SOURCE_DOC_DATA, GUINT_TO_POINTER(0));

srv = lsp_server_get_if_running(doc);
if (!srv)
return G_SOURCE_REMOVE;
@@ -332,11 +353,16 @@ static void on_document_visible(GeanyDocument *doc)
{
LspServer *srv = lsp_server_get(doc);

session_loaded = TRUE;

update_menu(doc);

// quick synchronous refresh with the last value without server request
lsp_symbol_tree_refresh();

// perform also without server - to revert to default Geany behavior
lsp_autocomplete_style_init(doc);

if (!srv)
return;

@@ -345,7 +371,8 @@ static void on_document_visible(GeanyDocument *doc)
lsp_highlight_style_init(doc);
lsp_semtokens_style_init(doc);
lsp_code_lens_style_init(doc);
lsp_autocomplete_style_init(doc);

lsp_selection_clear_selections();

// just in case we didn't get some callback from the server
on_save_finish(doc);
@@ -397,6 +424,7 @@ static void destroy_all(void)
static void stop_and_init_all_servers(void)
{
lsp_server_stop_all(FALSE);
session_loaded = FALSE;
lsp_server_init_all();

destroy_all();
@@ -585,7 +613,7 @@ static void on_document_filetype_set(G_GNUC_UNUSED GObject *obj, GeanyDocument *

// called also when opening documents - without this it would start servers
// unnecessarily
if (!lsp_sync_is_document_open(doc))
if (!session_loaded)
return;

srv_old = lsp_server_get_for_ft(filetype_old);
@@ -626,7 +654,7 @@ static void on_document_activate(G_GNUC_UNUSED GObject *obj, GeanyDocument *doc,
static gboolean on_editor_notify(G_GNUC_UNUSED GObject *obj, GeanyEditor *editor, SCNotification *nt,
G_GNUC_UNUSED gpointer user_data)
{
static gboolean ignore_selection_change = FALSE; // static!
static gboolean perform_highlight = TRUE; // static!
GeanyDocument *doc = editor->document;
ScintillaObject *sci = editor->sci;

@@ -642,7 +670,7 @@ static gboolean on_editor_notify(G_GNUC_UNUSED GObject *obj, GeanyEditor *editor
return FALSE;

// ignore cursor position change as a result of autocomplete (for highlighting)
ignore_selection_change = TRUE;
perform_highlight = FALSE;

sci_start_undo_action(editor->sci);
lsp_autocomplete_item_selected(srv, doc, SSM(sci, SCI_AUTOCGETCURRENT, 0, 0));
@@ -657,8 +685,14 @@ static gboolean on_editor_notify(G_GNUC_UNUSED GObject *obj, GeanyEditor *editor
{
lsp_autocomplete_set_displayed_symbols(NULL);
lsp_autocomplete_discard_pending_requests();
lsp_autocomplete_clear_statusbar();
return FALSE;
}
else if (nt->nmhdr.code == SCN_AUTOCSELECTIONCHANGE &&
plugin_extension_autocomplete_provided(doc, &extension))
{
lsp_autocomplete_selection_changed(doc, nt->text);
}
else if (nt->nmhdr.code == SCN_CALLTIPCLICK &&
plugin_extension_calltips_provided(doc, &extension))
{
@@ -727,6 +761,12 @@ static gboolean on_editor_notify(G_GNUC_UNUSED GObject *obj, GeanyEditor *editor
if (!srv || !doc->real_path)
return FALSE;

if (nt->modificationType & (SC_MOD_BEFOREINSERT | SC_MOD_BEFOREDELETE))
{
perform_highlight = FALSE;
lsp_highlight_clear(doc);
}

// BEFORE insert, BEFORE delete - send the original document
if (!lsp_sync_is_document_open(doc) &&
nt->modificationType & (SC_MOD_BEFOREINSERT | SC_MOD_BEFOREDELETE))
@@ -802,20 +842,24 @@ static gboolean on_editor_notify(G_GNUC_UNUSED GObject *obj, GeanyEditor *editor
lsp_diagnostics_hide_calltip(doc);

SSM(sci, SCI_AUTOCCANCEL, 0, 0);

if ((nt->updated & SC_UPDATE_SELECTION) && !sci_has_selection(sci))
lsp_selection_clear_selections();
}

if (srv->config.highlighting_enable && !ignore_selection_change &&
if (srv->config.highlighting_enable && perform_highlight &&
(nt->updated & SC_UPDATE_SELECTION))
{
lsp_highlight_send_request(srv, doc);
lsp_highlight_schedule_request(doc);
}
ignore_selection_change = FALSE;
perform_highlight = TRUE;
}
else if (nt->nmhdr.code == SCN_CHARADDED)
{
// don't hightlight while typing
lsp_highlight_clear(doc);

lsp_hover_hide_calltip(doc);
lsp_diagnostics_hide_calltip(doc);
}

return FALSE;
@@ -1062,6 +1106,43 @@ static gboolean update_command_menu_items(GPtrArray *code_action_commands, gpoin
}


// stolen from Geany
static void show_menu_at_caret(GtkMenu* menu, ScintillaObject *sci)
{
GdkWindow *window = gtk_widget_get_window(GTK_WIDGET(sci));
gint pos = sci_get_current_position(sci);
gint line = sci_get_line_from_position(sci, pos);
gint line_height = SSM(sci, SCI_TEXTHEIGHT, line, 0);
gint x = SSM(sci, SCI_POINTXFROMPOSITION, 0, pos);
gint y = SSM(sci, SCI_POINTYFROMPOSITION, 0, pos);
gint pos_next = SSM(sci, SCI_POSITIONAFTER, pos, 0);
gint char_width = 0;
/* if next pos is on the same Y (same line and not after wrapping), diff the X */
if (pos_next > pos && SSM(sci, SCI_POINTYFROMPOSITION, 0, pos_next) == y)
char_width = SSM(sci, SCI_POINTXFROMPOSITION, 0, pos_next) - x;
GdkRectangle rect = {x, y, char_width, line_height};
gtk_menu_popup_at_rect(GTK_MENU(menu), window, &rect, GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST, NULL);
}


static gboolean show_code_action_popup(GPtrArray *code_action_commands, gpointer user_data)
{
GPtrArray *code_lens_commands = lsp_code_lens_get_commands();

if (code_action_commands->len > 0 || code_lens_commands->len > 0)
{
GeanyDocument *doc = user_data;
GtkWidget *menu;

update_command_menu_items(code_action_commands, user_data);
menu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(context_menu_items.command_item));
show_menu_at_caret(GTK_MENU(menu), doc->editor->sci);
gtk_menu_shell_select_first(GTK_MENU_SHELL(menu), FALSE);
}
return FALSE;
}


static gboolean on_update_editor_menu(G_GNUC_UNUSED GObject *obj,
const gchar *word, gint pos, GeanyDocument *doc, gpointer user_data)
{
@@ -1317,6 +1398,13 @@ static void invoke_kb(guint key_id, gint pos)
lsp_goto_implementations(pos);
break;

case KB_EXPAND_SELECTION:
lsp_selection_range_expand();
break;
case KB_SHRINK_SELECTION:
lsp_selection_range_shrink();
break;

case KB_SHOW_HOVER_POPUP:
show_hover_popup();
break;
@@ -1341,6 +1429,10 @@ static void invoke_kb(guint key_id, gint pos)
restart_all_servers();
break;

case KB_SHOW_CODE_ACTIONS:
lsp_command_send_code_action_request(doc, pos, show_code_action_popup, doc);
break;

default:
break;
}
@@ -1505,6 +1597,29 @@ static void create_menu_items()
keybindings_set_item(group, KB_SHOW_HOVER_POPUP, NULL, 0, 0, "show_hover_popup",
_("Show hover popup"), menu_items.hover_popup);

menu_items.code_action_popup = gtk_menu_item_new_with_mnemonic(_("Show Code Action Popup"));
gtk_container_add(GTK_CONTAINER(menu), menu_items.code_action_popup);
g_signal_connect(menu_items.code_action_popup, "activate", G_CALLBACK(on_menu_invoked),
GUINT_TO_POINTER(KB_SHOW_CODE_ACTIONS));
keybindings_set_item(group, KB_SHOW_CODE_ACTIONS, NULL, 0, 0, "show_code_action_popup",
_("Show code action popup"), menu_items.code_action_popup);

gtk_container_add(GTK_CONTAINER(menu), gtk_separator_menu_item_new());

menu_items.expand_selection = gtk_menu_item_new_with_mnemonic(_("Expand Selection"));
gtk_container_add(GTK_CONTAINER(menu), menu_items.expand_selection);
g_signal_connect(menu_items.expand_selection, "activate", G_CALLBACK(on_menu_invoked),
GUINT_TO_POINTER(KB_EXPAND_SELECTION));
keybindings_set_item(group, KB_EXPAND_SELECTION, NULL, 0, 0, "expand_selection",
_("Expand Selection"), menu_items.expand_selection);

menu_items.shrink_selection = gtk_menu_item_new_with_mnemonic(_("Shrink Selection"));
gtk_container_add(GTK_CONTAINER(menu), menu_items.shrink_selection);
g_signal_connect(menu_items.shrink_selection, "activate", G_CALLBACK(on_menu_invoked),
GUINT_TO_POINTER(KB_SHRINK_SELECTION));
keybindings_set_item(group, KB_SHRINK_SELECTION, NULL, 0, 0, "shrink_selection",
_("Shrink Selection"), menu_items.shrink_selection);

gtk_container_add(GTK_CONTAINER(menu), gtk_separator_menu_item_new());

menu_items.header_source = gtk_menu_item_new_with_mnemonic(_("Swap Header/Source"));
21 changes: 18 additions & 3 deletions lsp/src/lsp-rpc.c
Original file line number Diff line number Diff line change
@@ -349,14 +349,29 @@ static gboolean handle_call(JsonrpcClient *client, gchar* method, GVariant *id,
msg = apply_edit(srv, params);
handled = TRUE;
}
// we officially don't support this (not advertised in initialize's
// workspace/configuration) but some servers ask for it anyway so do our
// best in this case
else if (g_strcmp0(method, "workspace/configuration") == 0)
{
// we officially don't support this (not advertised in initialize's
// workspace/configuration) but some servers ask for it anyway so do our
// best in this case
msg = workspace_configuration(srv, params);
handled = TRUE;
}
else if (g_strcmp0(method, "client/registerCapability") == 0)
{
// not supported at all, HTML/CSS servers sending the request despite
// no indication of support from the client. Just to suppress warnings
// from the servers
msg = NULL;
handled = TRUE;
}
else if (g_strcmp0(method, "workspace/semanticTokens/refresh") == 0)
{
// not supported at all - Kate seems to do the same as some servers
// require a successful reply
msg = NULL;
handled = TRUE;
}
else if (g_strcmp0(method, "window/showDocument") == 0)
{
msg = show_document(srv, params);
251 changes: 251 additions & 0 deletions lsp/src/lsp-selection-range.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
/*
* Copyright 2024 Jiri Techet <techet@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include "lsp-selection-range.h"
#include "lsp-utils.h"
#include "lsp-rpc.h"

#include <jsonrpc-glib.h>


extern GeanyData *geany_data;

typedef struct {
GeanyDocument *doc;
gboolean expand;
} SelectionRangeData;


GPtrArray *selections = NULL;


static gboolean is_within_range(ScintillaObject *sci, LspRange parent, LspRange child)
{
gint parent_start_pos = lsp_utils_lsp_pos_to_scintilla(sci, parent.start);
gint parent_end_pos = lsp_utils_lsp_pos_to_scintilla(sci, parent.end);
gint child_start_pos = lsp_utils_lsp_pos_to_scintilla(sci, child.start);
gint child_end_pos = lsp_utils_lsp_pos_to_scintilla(sci, child.end);

return (parent_start_pos < child_start_pos && parent_end_pos >= child_end_pos) ||
(parent_start_pos <= child_start_pos && parent_end_pos > child_end_pos);
}


static LspRange get_current_selection(ScintillaObject *sci)
{
LspRange selection;
selection.start = lsp_utils_scintilla_pos_to_lsp(sci, sci_get_selection_start(sci));
selection.end = lsp_utils_scintilla_pos_to_lsp(sci, sci_get_selection_end(sci));
return selection;
}


static gboolean is_max_selection(ScintillaObject *sci)
{
LspRange selection = get_current_selection(sci);
LspRange *max_selection;

if (!selections || selections->len == 0)
return FALSE;

max_selection = selections->pdata[selections->len-1];

return max_selection->start.character == selection.start.character &&
max_selection->start.line == selection.start.line &&
max_selection->end.character == selection.end.character &&
max_selection->end.line == selection.end.line;
}


static void parse_selection(GVariant *val, ScintillaObject *sci, LspRange selection)
{
GVariant *range_variant = NULL;
GVariant *parent = NULL;

JSONRPC_MESSAGE_PARSE(val,
"range", JSONRPC_MESSAGE_GET_VARIANT(&range_variant));

if (range_variant)
{
LspRange parsed_range = lsp_utils_parse_range(range_variant);

if (is_within_range(sci, parsed_range, selection))
{
LspRange *range = g_new0(LspRange, 1);
*range = parsed_range;
g_ptr_array_add(selections, range);
}

g_variant_unref(range_variant);
}

JSONRPC_MESSAGE_PARSE(val,
"parent", JSONRPC_MESSAGE_GET_VARIANT(&parent));

if (parent)
{
parse_selection(parent, sci, selection);
g_variant_unref(parent);
}
}


static LspRange *find_selection_range(ScintillaObject *sci, gboolean expand)
{
LspRange selection_range = get_current_selection(sci);;
LspRange *found_range = NULL;
LspRange *range;
gint i;

// sorted from the smallest to the biggest
foreach_ptr_array(range, i, selections)
{
if (expand && is_within_range(sci, *range, selection_range))
{
found_range = range;
break;
}
else if (!expand && is_within_range(sci, selection_range, *range))
found_range = range;
}

return found_range;
}


static void find_and_select(ScintillaObject *sci, gboolean expand)
{
LspRange *found_range = find_selection_range(sci, expand);

if (found_range)
{
gint start = lsp_utils_lsp_pos_to_scintilla(sci, found_range->start);
gint end = lsp_utils_lsp_pos_to_scintilla(sci, found_range->end);
SSM(sci, SCI_SETSELECTION, start, end);
}
}


static void goto_cb(GVariant *return_value, GError *error, gpointer user_data)
{
SelectionRangeData *data = user_data;

if (!error)
{
GeanyDocument *doc = data->doc;

if (DOC_VALID(doc) && g_variant_is_of_type(return_value, G_VARIANT_TYPE_ARRAY))
{
GVariant *val = NULL;
GVariantIter iter;

g_variant_iter_init(&iter, return_value);

while (g_variant_iter_loop(&iter, "v", &val))
{
LspRange selection = get_current_selection(doc->editor->sci);
LspRange *existing_range = g_new0(LspRange, 1);

*existing_range = selection;
g_ptr_array_add(selections, existing_range);

parse_selection(val, doc->editor->sci, selection);
break; // for single query just a single result
}

find_and_select(doc->editor->sci, data->expand);
}

//printf("%s\n\n\n", lsp_utils_json_pretty_print(return_value));
}

g_free(user_data);
}


void lsp_selection_clear_selections(void)
{
if (selections)
g_ptr_array_free(selections, TRUE);
selections = NULL;
}


static void selection_range_request(gboolean expand)
{
GeanyDocument *doc = document_get_current();
LspServer *server = lsp_server_get(doc);
SelectionRangeData *data;
LspPosition lsp_pos;
gchar *doc_uri;
GVariant *node;
gint pos;

if (!server || !server->config.selection_range_enable)
return;

if (expand && is_max_selection(doc->editor->sci))
return;
else if (sci_has_selection(doc->editor->sci) && selections &&
find_selection_range(doc->editor->sci, expand))
{
find_and_select(doc->editor->sci, expand);
return;
}

pos = sci_get_current_position(doc->editor->sci);
lsp_pos = lsp_utils_scintilla_pos_to_lsp(doc->editor->sci, pos);
doc_uri = lsp_utils_get_doc_uri(doc);

lsp_selection_clear_selections();
selections = g_ptr_array_new_full(1, g_free);

node = JSONRPC_MESSAGE_NEW (
"textDocument", "{",
"uri", JSONRPC_MESSAGE_PUT_STRING(doc_uri),
"}",
"positions", "[", "{", // TODO: support multiple ranges for multiple selections
"line", JSONRPC_MESSAGE_PUT_INT32(lsp_pos.line),
"character", JSONRPC_MESSAGE_PUT_INT32(lsp_pos.character),
"}", "]"
);

data = g_new0(SelectionRangeData, 1);
data->doc = doc;
data->expand = expand;
lsp_rpc_call(server, "textDocument/selectionRange", node, goto_cb, data);

g_free(doc_uri);
g_variant_unref(node);
}


void lsp_selection_range_expand(void)
{
selection_range_request(TRUE);
}


void lsp_selection_range_shrink(void)
{
selection_range_request(FALSE);
}
33 changes: 33 additions & 0 deletions lsp/src/lsp-selection-range.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2024 Jiri Techet <techet@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

#ifndef LSP_SELECTION_RANGE_H
#define LSP_SELECTION_RANGE_H 1

#include "lsp-server.h"

#include <glib.h>


void lsp_selection_range_expand(void);
void lsp_selection_range_shrink(void);

void lsp_selection_clear_selections(void);


#endif /* LSP_SELECTION_RANGE_H */
250 changes: 143 additions & 107 deletions lsp/src/lsp-server.c
Original file line number Diff line number Diff line change
@@ -159,8 +159,12 @@ static gboolean kill_cb(gpointer user_data)

if (g_ptr_array_find(servers_in_shutdown, srv, NULL))
{
msgwin_status_add(_("Killing LSP server %s"), srv->config.cmd);
msgwin_status_add(_("Force terminating LSP server %s"), srv->config.cmd);
#ifdef G_OS_WIN32
g_subprocess_force_exit(srv->process);
#else
g_subprocess_send_signal(srv->process, SIGTERM);
#endif
}

return G_SOURCE_REMOVE;
@@ -206,7 +210,7 @@ static void stop_process(LspServer *s)
lsp_rpc_call_startup_shutdown(s, "shutdown", NULL, shutdown_cb, s);

// should not be performed if server behaves correctly
plugin_timeout_add(geany_plugin, 5000, kill_cb, s);
plugin_timeout_add(geany_plugin, 4000, kill_cb, s);
}


@@ -243,38 +247,6 @@ static gchar *get_autocomplete_trigger_chars(GVariant *node)
}


static gboolean supports_full_semantic_tokens(GVariant *node)
{
gboolean val = FALSE;

JSONRPC_MESSAGE_PARSE(node,
"capabilities", "{",
"semanticTokensProvider", "{",
"full", JSONRPC_MESSAGE_GET_BOOLEAN(&val),
"}",
"}");

return val;
}


static gboolean supports_delta_semantic_tokens(GVariant *node)
{
gboolean val = FALSE;

JSONRPC_MESSAGE_PARSE(node,
"capabilities", "{",
"semanticTokensProvider", "{",
"full", "{",
"delta", JSONRPC_MESSAGE_GET_BOOLEAN(&val),
"}",
"}",
"}");

return val;
}


static guint64 get_semantic_token_mask(LspServer *srv, GVariant *node)
{
guint64 mask = 0;
@@ -319,6 +291,7 @@ static guint64 get_semantic_token_mask(LspServer *srv, GVariant *node)
static gchar *get_signature_trigger_chars(GVariant *node)
{
GVariantIter *iter = NULL;
GVariantIter *iter2 = NULL;
GString *str = g_string_new("");

JSONRPC_MESSAGE_PARSE(node,
@@ -328,6 +301,13 @@ static gchar *get_signature_trigger_chars(GVariant *node)
"}",
"}");

JSONRPC_MESSAGE_PARSE(node,
"capabilities", "{",
"signatureHelpProvider", "{",
"retriggerCharacters", JSONRPC_MESSAGE_GET_ITER(&iter2),
"}",
"}");

if (iter)
{
GVariant *val = NULL;
@@ -336,6 +316,18 @@ static gchar *get_signature_trigger_chars(GVariant *node)
g_variant_iter_free(iter);
}

if (iter2)
{
GVariant *val = NULL;
while (g_variant_iter_loop(iter2, "v", &val))
{
const gchar *chr = g_variant_get_string(val, NULL);
if (!strstr(str->str, chr))
g_string_append(str, chr);
}
g_variant_iter_free(iter2);
}

return g_string_free(str, FALSE);
}

@@ -365,52 +357,6 @@ 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;
@@ -455,33 +401,74 @@ static gboolean use_workspace_folders(GVariant *node)
}


static void update_config(GVariant *variant, gboolean *option, const gchar *key)
static gboolean has_capability(GVariant *variant, const gchar *key1, const gchar *key2, const gchar *key3)
{
gboolean val = FALSE;
gboolean success = JSONRPC_MESSAGE_PARSE(variant,
"capabilities", "{",
key, JSONRPC_MESSAGE_GET_BOOLEAN(&val),
"}");
GVariant *var = NULL;
gboolean success;

if (success) // explicit TRUE, FALSE
{
if (!val)
*option = FALSE;
}
else // dict (possibly just empty) which also indicates TRUE
{
GVariant *var = NULL;
if (key2 && key3)
success = JSONRPC_MESSAGE_PARSE(variant,
"capabilities", "{",
key1, "{",
key2, "{",
key3, JSONRPC_MESSAGE_GET_BOOLEAN(&val),
"}",
"}",
"}");
else if (key2)
success = JSONRPC_MESSAGE_PARSE(variant,
"capabilities", "{",
key1, "{",
key2, JSONRPC_MESSAGE_GET_BOOLEAN(&val),
"}",
"}");
else
success = JSONRPC_MESSAGE_PARSE(variant,
"capabilities", "{",
key1, JSONRPC_MESSAGE_GET_BOOLEAN(&val),
"}");

// explicit TRUE, FALSE
if (success)
return val;

// dict (possibly just empty) which also indicates TRUE
if (key2 && key3)
JSONRPC_MESSAGE_PARSE(variant,
"capabilities", "{",
key1, "{",
key2, "{",
key3, JSONRPC_MESSAGE_GET_VARIANT(&var),
"}",
"}",
"}");
else if (key2)
JSONRPC_MESSAGE_PARSE(variant,
"capabilities", "{",
key1, "{",
key2, JSONRPC_MESSAGE_GET_VARIANT(&var),
"}",
"}");
else
JSONRPC_MESSAGE_PARSE(variant,
"capabilities", "{",
key, JSONRPC_MESSAGE_GET_VARIANT(&var),
key1, JSONRPC_MESSAGE_GET_VARIANT(&var),
"}");

if (var)
g_variant_unref(var);
else
*option = FALSE;
if (var)
{
g_variant_unref(var);
return TRUE;
}

return FALSE;
}


static void update_config(GVariant *variant, gboolean *option, const gchar *key)
{
*option = *option && has_capability(variant, key, NULL, NULL);
}


@@ -516,6 +503,7 @@ static void initialize_cb(GVariant *return_value, GError *error, gpointer user_d
if (EMPTY(s->signature_trigger_chars))
s->config.signature_enable = FALSE;

update_config(return_value, &s->config.autocomplete_enable, "completionProvider");
update_config(return_value, &s->config.hover_enable, "hoverProvider");
update_config(return_value, &s->config.hover_available, "hoverProvider");
update_config(return_value, &s->config.goto_enable, "definitionProvider");
@@ -533,20 +521,24 @@ static void initialize_cb(GVariant *return_value, GError *error, gpointer user_d
update_config(return_value, &s->config.execute_command_enable, "executeCommandProvider");
update_config(return_value, &s->config.code_action_enable, "codeActionProvider");
update_config(return_value, &s->config.rename_enable, "renameProvider");
update_config(return_value, &s->config.selection_range_enable, "selectionRangeProvider");

s->supports_completion_resolve = has_capability(return_value, "completionProvider", "resolveProvider", NULL);

s->supports_workspace_symbols = TRUE;
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->send_did_save = has_capability(return_value, "textDocumentSync", "save", NULL);
s->include_text_on_save = has_capability(return_value, "textDocumentSync", "save", "includeText");
s->use_workspace_folders = use_workspace_folders(return_value);

s->initialize_response = lsp_utils_json_pretty_print(return_value);

s->config.semantic_tokens_supports_delta = supports_delta_semantic_tokens(return_value);
if (!supports_full_semantic_tokens(return_value) && !s->config.semantic_tokens_supports_delta)
s->config.semantic_tokens_enable = FALSE;
s->config.semantic_tokens_enable = s->config.semantic_tokens_enable &&
has_capability(return_value, "semanticTokensProvider", "full", NULL);
s->config.semantic_tokens_supports_delta = has_capability(return_value,
"semanticTokensProvider", "full", "delta");
s->semantic_token_mask = get_semantic_token_mask(s, return_value);

msgwin_status_add(_("LSP server %s initialized"), s->config.cmd);
@@ -572,12 +564,16 @@ static void initialize_cb(GVariant *return_value, GError *error, gpointer user_d
}
else
{
msgwin_status_add(_("LSP initialize request failed for LSP server, killing %s"), s->config.cmd);
msgwin_status_add(_("LSP initialize request failed for LSP server, terminating %s"), s->config.cmd);

// force exit the server - since the handshake didn't perform, the
// server may be in some strange state and normal "exit" may not work
// (happens with haskell server)
#ifdef G_OS_WIN32
g_subprocess_force_exit(s->process);
#else
g_subprocess_send_signal(s->process, SIGTERM);
#endif
}
}

@@ -616,7 +612,7 @@ static void perform_initialize(LspServer *server)
LSP_COMPLETION_KINDS,
"]",
"}",
"contxtSupport", JSONRPC_MESSAGE_PUT_BOOLEAN(TRUE),
"contextSupport", JSONRPC_MESSAGE_PUT_BOOLEAN(TRUE),
"}",
"hover", "{",
"contentFormat", "[",
@@ -631,6 +627,31 @@ static void perform_initialize(LspServer *server)
"}",
"hierarchicalDocumentSymbolSupport", JSONRPC_MESSAGE_PUT_BOOLEAN(TRUE),
"}",
"publishDiagnostics", "{", // zls requires this to publish diagnostics
"}",
"codeAction", "{",
"resolveSupport", "{",
"properties", "[",
"edit", "command",
"]",
"}",
"dataSupport", JSONRPC_MESSAGE_PUT_BOOLEAN(TRUE),
"codeActionLiteralSupport", "{",
"codeActionKind", "{",
"valueSet", "[",
"",
"quickfix",
"refactor",
"refactor.extract",
"refactor.inline",
"refactor.rewrite",
"source",
"source.organizeImports",
"source.fixAll",
"]",
"}",
"}",
"}",
"semanticTokens", "{",
"requests", "{",
"full", "{",
@@ -883,6 +904,8 @@ static void load_config(GKeyFile *kf, const gchar *section, LspServer *s)
get_bool(&s->config.autocomplete_apply_additional_edits, kf, section, "autocomplete_apply_additional_edits");
get_bool(&s->config.diagnostics_enable, kf, section, "diagnostics_enable");
get_bool(&s->config.autocomplete_use_snippets, kf, section, "autocomplete_use_snippets");
get_bool(&s->config.autocomplete_in_strings, kf, section, "autocomplete_in_strings");
get_bool(&s->config.autocomplete_show_documentation, kf, section, "autocomplete_show_documentation");
get_int(&s->config.diagnostics_statusbar_severity, kf, section, "diagnostics_statusbar_severity");
get_str(&s->config.diagnostics_disable_for, kf, section, "diagnostics_disable_for");

@@ -919,6 +942,7 @@ static void load_config(GKeyFile *kf, const gchar *section, LspServer *s)
get_str(&s->config.command_on_save_regex, kf, section, "command_on_save_regex");

get_bool(&s->config.progress_bar_enable, kf, section, "progress_bar_enable");
get_bool(&s->config.swap_header_source_enable, kf, section, "swap_header_source_enable");

// create for the first time, then just update
if (!s->config.command_regexes)
@@ -947,6 +971,7 @@ static void load_config(GKeyFile *kf, const gchar *section, LspServer *s)
s->config.execute_command_enable = TRUE;
s->config.code_action_enable = TRUE;
s->config.rename_enable = TRUE;
s->config.selection_range_enable = TRUE;

s->config.hover_available = TRUE;
s->config.document_symbols_available = TRUE;
@@ -966,9 +991,20 @@ static void load_all_section_only_config(GKeyFile *kf, const gchar *section, Lsp

static void load_filetype_only_config(GKeyFile *kf, const gchar *section, LspServer *s)
{
get_str(&s->config.cmd, kf, section, "cmd");
gchar *cmd = NULL;
gchar *use = NULL;

get_str(&cmd, kf, section, "cmd");
get_str(&use, kf, section, "use");
if (!EMPTY(cmd) || !EMPTY(use))
{
// make sure 'use' from global config file gets overridden by 'cmd' from user config file
// and that not both of them are set
SETPTR(s->config.cmd, cmd);
SETPTR(s->config.ref_lang, use);
}

get_strv(&s->config.env, kf, section, "env");
get_str(&s->config.ref_lang, kf, section, "use");
get_str(&s->config.rpc_log, kf, section, "rpc_log");
get_str(&s->config.initialization_options_file, kf, section, "initialization_options_file");
get_str(&s->config.initialization_options, kf, section, "initialization_options");
5 changes: 5 additions & 0 deletions lsp/src/lsp-server.h
Original file line number Diff line number Diff line change
@@ -57,6 +57,8 @@ typedef struct LspServerConfig
gint autocomplete_window_max_width;
gboolean autocomplete_use_snippets;
gchar *autocomplete_hide_after_words;
gboolean autocomplete_in_strings;
gboolean autocomplete_show_documentation;

gboolean diagnostics_enable;
gint diagnostics_statusbar_severity;
@@ -109,6 +111,8 @@ typedef struct LspServerConfig

gboolean execute_command_enable;
gboolean code_action_enable;
gboolean selection_range_enable;
gboolean swap_header_source_enable;
gchar *command_on_save_regex;
gint command_keybinding_num;
GPtrArray *command_regexes;
@@ -150,6 +154,7 @@ typedef struct LspServer
gboolean include_text_on_save;
gboolean use_workspace_folders;
gboolean supports_workspace_symbols;
gboolean supports_completion_resolve;

guint64 semantic_token_mask;
} LspServer;
62 changes: 35 additions & 27 deletions lsp/src/lsp-signature.c
Original file line number Diff line number Diff line change
@@ -75,44 +75,52 @@ static void signature_cb(GVariant *return_value, GError *error, gpointer user_da

//printf("%s\n", lsp_utils_json_pretty_print(return_value));

if (current_doc == data->doc &&
sci_get_current_position(current_doc->editor->sci) < data->pos + 10 &&
(data->force || (!data->force && !SSM(current_doc->editor->sci, SCI_AUTOCACTIVE, 0, 0))))
if (current_doc == data->doc)
{
GVariantIter *iter = NULL;
gint64 active = 1;

JSONRPC_MESSAGE_PARSE(return_value, "signatures", JSONRPC_MESSAGE_GET_ITER(&iter));
JSONRPC_MESSAGE_PARSE(return_value, "activeSignature", JSONRPC_MESSAGE_GET_INT64(&active));
if (!g_variant_is_of_type(return_value, G_VARIANT_TYPE_DICTIONARY) &&
lsp_signature_showing_calltip(current_doc))
{
// null response
lsp_signature_hide_calltip(current_doc);
}
else if (sci_get_current_position(current_doc->editor->sci) < data->pos + 10 &&
(data->force || (!data->force && !SSM(current_doc->editor->sci, SCI_AUTOCACTIVE, 0, 0))))
{
GVariantIter *iter = NULL;
gint64 active = 1;

if (signatures)
g_ptr_array_free(signatures, TRUE);
signatures = g_ptr_array_new_full(1, g_free);
JSONRPC_MESSAGE_PARSE(return_value, "signatures", JSONRPC_MESSAGE_GET_ITER(&iter));
JSONRPC_MESSAGE_PARSE(return_value, "activeSignature", JSONRPC_MESSAGE_GET_INT64(&active));

if (iter)
{
GVariant *member = NULL;
if (signatures)
g_ptr_array_free(signatures, TRUE);
signatures = g_ptr_array_new_full(1, g_free);

while (g_variant_iter_loop(iter, "v", &member))
if (iter)
{
const gchar *label = NULL;
GVariant *member = NULL;

while (g_variant_iter_loop(iter, "v", &member))
{
const gchar *label = NULL;

JSONRPC_MESSAGE_PARSE(member, "label", JSONRPC_MESSAGE_GET_STRING(&label));
JSONRPC_MESSAGE_PARSE(member, "label", JSONRPC_MESSAGE_GET_STRING(&label));

if (label)
g_ptr_array_add(signatures, g_strdup(label));
if (label)
g_ptr_array_add(signatures, g_strdup(label));
}
}
}

displayed_signature = CLAMP(active, 1, signatures->len) - 1;
displayed_signature = CLAMP(active, 1, signatures->len) - 1;

if (signatures->len == 0)
SSM(current_doc->editor->sci, SCI_CALLTIPCANCEL, 0, 0);
else
show_signature(current_doc->editor->sci);
if (signatures->len == 0)
SSM(current_doc->editor->sci, SCI_CALLTIPCANCEL, 0, 0);
else
show_signature(current_doc->editor->sci);

if (iter)
g_variant_iter_free(iter);
if (iter)
g_variant_iter_free(iter);
}
}
}

0 comments on commit ad5f3eb

Please sign in to comment.