diff --git a/meson.build b/meson.build index 100d774772..30efc6fe23 100644 --- a/meson.build +++ b/meson.build @@ -780,6 +780,7 @@ install_headers( 'src/toolbar.h', 'src/ui_utils.h', 'src/utils.h', + 'src/lsp.h', subdir: 'geany' ) @@ -819,6 +820,8 @@ libgeany = shared_library('geany', 'src/keyfile.h', 'src/log.c', 'src/log.h', + 'src/lsp.c', + 'src/lsp.h', 'src/libmain.c', 'src/main.h', 'src/geany.h', diff --git a/src/Makefile.am b/src/Makefile.am index c94bcfbbba..58f0d06bf5 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -47,6 +47,7 @@ geany_include_HEADERS = \ gtkcompat.h \ highlighting.h \ keybindings.h \ + lsp.h \ main.h \ msgwindow.h \ navqueue.h \ @@ -86,6 +87,7 @@ libgeany_la_SOURCES = \ keyfile.c keyfile.h \ log.c log.h \ libmain.c main.h geany.h \ + lsp.c lsp.h \ msgwindow.c msgwindow.h \ navqueue.c navqueue.h \ notebook.c notebook.h \ diff --git a/src/document.c b/src/document.c index 4cf74105b1..81de8aab16 100644 --- a/src/document.c +++ b/src/document.c @@ -40,6 +40,7 @@ #include "geanyobject.h" #include "geanywraplabel.h" #include "highlighting.h" +#include "lsp.h" #include "main.h" #include "msgwindow.h" #include "navqueue.h" @@ -96,6 +97,15 @@ typedef struct * it contains the new value */ } undo_action; + +/* Data used for LSP highlighting request callback */ +typedef struct +{ + GeanyDocument *doc; + gint keyword_idx; +} LspHighlightData; + + /* Custom document info bar response IDs */ enum { @@ -2713,6 +2723,40 @@ void document_update_tags(GeanyDocument *doc) } +static void document_highlight_keywords(GeanyDocument *doc, const gchar *keywords, gint keyword_idx) +{ + guint hash = g_str_hash(keywords); + + if (hash != doc->priv->keyword_hash) + { + sci_set_keywords(doc->editor->sci, keyword_idx, keywords); + queue_colourise(doc); /* force re-highlighting the entire document */ + doc->priv->keyword_hash = hash; + } +} + + +static void lsp_highlight_cb(gpointer user_data) +{ + LspHighlightData *data = user_data; + guint i; + + foreach_document(i) + { + /* the document could have been closed before the callback fires, check + * if it's still valid */ + if (documents[i] == data->doc) + { + const gchar *keywords = lsp_symbol_highlight_get_cached(data->doc); + document_highlight_keywords(data->doc, keywords ? keywords : "", data->keyword_idx); + break; + } + } + + g_free(data); +} + + /* Re-highlights type keywords without re-parsing the whole document. */ void document_highlight_tags(GeanyDocument *doc) { @@ -2748,21 +2792,27 @@ void document_highlight_tags(GeanyDocument *doc) if (!app->tm_workspace->tags_array) return; - /* get any type keywords and tell scintilla about them - * this will cause the type keywords to be colourized in scintilla */ - keywords_str = symbols_find_typenames_as_string(doc->file_type->lang, FALSE); - if (keywords_str) + if (lsp_symbol_highlight_available(doc)) { - gchar *keywords = g_string_free(keywords_str, FALSE); - guint hash = g_str_hash(keywords); + LspHighlightData *data = g_new0(LspHighlightData, 1); + const gchar *keywords = lsp_symbol_highlight_get_cached(doc); - if (hash != doc->priv->keyword_hash) + data->doc = doc; + data->keyword_idx = keyword_idx; + document_highlight_keywords(doc, keywords ? keywords : "", keyword_idx); + lsp_symbol_highlight_request(doc, lsp_highlight_cb, data); + } + else + { + /* get any type keywords and tell scintilla about them + * this will cause the type keywords to be colourized in scintilla */ + keywords_str = symbols_find_typenames_as_string(doc->file_type->lang, FALSE); + if (keywords_str) { - sci_set_keywords(doc->editor->sci, keyword_idx, keywords); - queue_colourise(doc); /* force re-highlighting the entire document */ - doc->priv->keyword_hash = hash; + gchar *keywords = g_string_free(keywords_str, FALSE); + document_highlight_keywords(doc, keywords, keyword_idx); + g_free(keywords); } - g_free(keywords); } } diff --git a/src/editor.c b/src/editor.c index 0dd1e0a62d..062f5e517d 100644 --- a/src/editor.c +++ b/src/editor.c @@ -45,6 +45,7 @@ #include "geanyobject.h" #include "highlighting.h" #include "keybindings.h" +#include "lsp.h" #include "main.h" #include "prefs.h" #include "projectprivate.h" @@ -319,7 +320,7 @@ static gboolean on_editor_button_press_event(GtkWidget *widget, GdkEventButton * editor_find_current_word(editor, editor_info.click_pos, current_word, sizeof current_word, NULL); if (*current_word) - return symbols_goto_tag(current_word, TRUE); + return symbols_goto_tag(current_word, editor_info.click_pos, TRUE); else keybindings_send_command(GEANY_KEY_GROUP_GOTO, GEANY_KEYS_GOTO_MATCHINGBRACE); return TRUE; @@ -671,9 +672,13 @@ static gboolean reshow_calltip(gpointer data) g_return_val_if_fail(calltip.sci != NULL, FALSE); - SSM(calltip.sci, SCI_CALLTIPCANCEL, 0, 0); doc = document_get_current(); + if (lsp_calltips_available(doc)) + return FALSE; + + SSM(calltip.sci, SCI_CALLTIPCANCEL, 0, 0); + if (doc && doc->editor->sci == calltip.sci) { /* we use the position where the calltip was previously started as SCI_GETCURRENTPOS @@ -821,13 +826,15 @@ static void on_char_added(GeanyEditor *editor, SCNotification *nt) case '(': { auto_close_chars(sci, pos, nt->ch); - /* show calltips */ - editor_show_calltip(editor, --pos); + if (!lsp_calltips_available(editor->document)) + /* show calltips */ + editor_show_calltip(editor, --pos); break; } case ')': { /* hide calltips */ - if (SSM(sci, SCI_CALLTIPACTIVE, 0, 0)) + if (SSM(sci, SCI_CALLTIPACTIVE, 0, 0) && + !lsp_calltips_available(editor->document)) { SSM(sci, SCI_CALLTIPCANCEL, 0, 0); } @@ -857,13 +864,15 @@ static void on_char_added(GeanyEditor *editor, SCNotification *nt) case ':': /* C/C++ class:: syntax */ /* tag autocompletion */ default: -#if 0 - if (! editor_start_auto_complete(editor, pos, FALSE)) - request_reshowing_calltip(nt); -#else editor_start_auto_complete(editor, pos, FALSE); -#endif } + + if (lsp_calltips_available(editor->document)) + lsp_calltips_show(editor->document); + + if (lsp_autocomplete_available(editor->document)) + lsp_autocomplete_perform(editor->document); + check_line_breaking(editor, pos); } @@ -1171,7 +1180,7 @@ static gboolean on_editor_notify(G_GNUC_UNUSED GObject *object, GeanyEditor *edi break; case SCN_CALLTIPCLICK: - if (nt->position > 0) + if (!lsp_calltips_available(doc) && nt->position > 0) { switch (nt->position) { @@ -2222,6 +2231,9 @@ gboolean editor_start_auto_complete(GeanyEditor *editor, gint pos, gboolean forc g_return_val_if_fail(editor != NULL, FALSE); + if (lsp_autocomplete_available(editor->document)) + return FALSE; + if (! editor_prefs.auto_complete_symbols && ! force) return FALSE; diff --git a/src/keybindings.c b/src/keybindings.c index 5cf755ab57..2e9a5fe29c 100644 --- a/src/keybindings.c +++ b/src/keybindings.c @@ -39,6 +39,7 @@ #include "filetypes.h" #include "geanyobject.h" #include "keybindingsprivate.h" +#include "lsp.h" #include "main.h" #include "msgwindow.h" #include "navqueue.h" @@ -1974,7 +1975,7 @@ static void goto_tag(GeanyDocument *doc, gboolean definition) gchar *text = get_current_word_or_sel(doc, FALSE); if (text) - symbols_goto_tag(text, definition); + symbols_goto_tag(text, sci_get_current_position(doc->editor->sci), definition); else utils_beep(); @@ -2151,10 +2152,16 @@ static gboolean cb_func_editor_action(guint key_id) sci_send_command(doc->editor->sci, SCI_LINETRANSPOSE); break; case GEANY_KEYS_EDITOR_AUTOCOMPLETE: - editor_start_auto_complete(doc->editor, sci_get_current_position(doc->editor->sci), TRUE); + if (lsp_autocomplete_available(doc)) + lsp_autocomplete_perform(doc); + else + editor_start_auto_complete(doc->editor, sci_get_current_position(doc->editor->sci), TRUE); break; case GEANY_KEYS_EDITOR_CALLTIP: - editor_show_calltip(doc->editor, -1); + if (lsp_calltips_available(doc)) + lsp_calltips_show(doc); + else + editor_show_calltip(doc->editor, -1); break; case GEANY_KEYS_EDITOR_CONTEXTACTION: if (check_current_word(doc, FALSE)) diff --git a/src/lsp.c b/src/lsp.c new file mode 100644 index 0000000000..5b7db3aef5 --- /dev/null +++ b/src/lsp.c @@ -0,0 +1,237 @@ +/* + * lsp.c - this file is part of Geany, a fast and lightweight IDE + * + * Copyright 2023 The Geany contributors + * + * 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. + */ + +#include "lsp.h" + + +typedef enum { + LspKindFile = 1, + LspKindModule, + LspKindNamespace, + LspKindPackage, + LspKindClass, + LspKindMethod, + LspKindProperty, + LspKindField, + LspKindConstructor, + LspKindEnum, + LspKindInterface, + LspKindFunction, + LspKindVariable, + LspKindConstant, + LspKindString, + LspKindNumber, + LspKindBoolean, + LspKindArray, + LspKindObject, + LspKindKey, + LspKindNull, + LspKindEnumMember, + LspKindStruct, + LspKindEvent, + LspKindOperator, + LspKindTypeParameter, + LSP_KIND_NUM = LspKindTypeParameter +} LspSymbolKind; /* note: enums different than in LspCompletionItemKind */ + + +static LspSymbolKind kind_icons[LSP_KIND_NUM] = { + TM_ICON_NAMESPACE, /* LspKindFile */ + TM_ICON_NAMESPACE, /* LspKindModule */ + TM_ICON_NAMESPACE, /* LspKindNamespace */ + TM_ICON_NAMESPACE, /* LspKindPackage */ + TM_ICON_CLASS, /* LspKindClass */ + TM_ICON_METHOD, /* LspKindMethod */ + TM_ICON_MEMBER, /* LspKindProperty */ + TM_ICON_MEMBER, /* LspKindField */ + TM_ICON_METHOD, /* LspKindConstructor */ + TM_ICON_STRUCT, /* LspKindEnum */ + TM_ICON_CLASS, /* LspKindInterface */ + TM_ICON_METHOD, /* LspKindFunction */ + TM_ICON_VAR, /* LspKindVariable */ + TM_ICON_MACRO, /* LspKindConstant */ + TM_ICON_OTHER, /* LspKindString */ + TM_ICON_OTHER, /* LspKindNumber */ + TM_ICON_OTHER, /* LspKindBoolean */ + TM_ICON_OTHER, /* LspKindArray */ + TM_ICON_OTHER, /* LspKindObject */ + TM_ICON_OTHER, /* LspKindKey */ + TM_ICON_OTHER, /* LspKindNull */ + TM_ICON_MEMBER, /* LspKindEnumMember */ + TM_ICON_STRUCT, /* LspKindStruct */ + TM_ICON_OTHER, /* LspKindEvent */ + TM_ICON_METHOD, /* LspKindOperator */ + TM_ICON_OTHER /* LspKindTypeParameter */ +}; + + +gboolean func_return_false(GeanyDocument *doc) +{ + return FALSE; +} + + +GPtrArray *func_return_ptrarr(GeanyDocument *doc) +{ + return NULL; +} + + +const gchar *func_return_str(GeanyDocument *doc) +{ + return NULL; +} + + +void func_args_doc(GeanyDocument *doc) +{ +} + + +void func_args_doc_int_bool(GeanyDocument *doc, gint dummy1, gboolean dummy2) +{ +} + + +void func_args_doc_symcallback_ptr(GeanyDocument *doc, LspSymbolRequestCallback callback, gpointer user_data) +{ +} + + +static Lsp dummy_lsp = { + .autocomplete_available = func_return_false, + .autocomplete_perform = func_args_doc, + + .calltips_available = func_return_false, + .calltips_show = func_args_doc, + + .goto_available = func_return_false, + .goto_perform = func_args_doc_int_bool, + + .doc_symbols_available = func_return_false, + .doc_symbols_request = func_args_doc_symcallback_ptr, + .doc_symbols_get_cached = func_return_ptrarr, + + .symbol_highlight_available = func_return_false, + .symbol_highlight_request = func_args_doc_symcallback_ptr, + .symbol_highlight_get_cached = func_return_str +}; + +static Lsp *current_lsp = &dummy_lsp; + + +GEANY_API_SYMBOL +void lsp_register(Lsp *lsp) +{ + /* possibly, in the future if there's a need for multiple LSP plugins, + * have a list of LSP clients and add/remove to/from the list */ + current_lsp = lsp; +} + + +GEANY_API_SYMBOL +void lsp_unregister(Lsp *lsp) +{ + current_lsp = &dummy_lsp; +} + + +guint lsp_get_symbols_icon_id(guint kind) +{ + if (kind >= LspKindFile && kind <= LSP_KIND_NUM) + return kind_icons[kind - 1]; + return TM_ICON_STRUCT; +} + + +/* allow LSP plugins not to implement all the functions (might happen if we + * add more in the future) and fall back to the dummy implementation */ +#define CALL_IF_EXISTS(f) (current_lsp->f ? current_lsp->f : dummy_lsp.f) + +gboolean lsp_autocomplete_available(GeanyDocument *doc) +{ + return CALL_IF_EXISTS(autocomplete_available)(doc); +} + + +void lsp_autocomplete_perform(GeanyDocument *doc) +{ + CALL_IF_EXISTS(autocomplete_perform)(doc); +} + + +gboolean lsp_calltips_available(GeanyDocument *doc) +{ + return CALL_IF_EXISTS(calltips_available)(doc); +} + + +void lsp_calltips_show(GeanyDocument *doc) +{ + CALL_IF_EXISTS(calltips_show)(doc); +} + + +gboolean lsp_goto_available(GeanyDocument *doc) +{ + return CALL_IF_EXISTS(goto_available)(doc); +} + + +void lsp_goto_perform(GeanyDocument *doc, gint pos, gboolean definition) +{ + CALL_IF_EXISTS(goto_perform)(doc, pos, definition); +} + + +gboolean lsp_doc_symbols_available(GeanyDocument *doc) +{ + return CALL_IF_EXISTS(doc_symbols_available)(doc); +} + + +void lsp_doc_symbols_request(GeanyDocument *doc, LspSymbolRequestCallback callback, gpointer user_data) +{ + CALL_IF_EXISTS(doc_symbols_request)(doc, callback, user_data); +} + + +GPtrArray *lsp_doc_symbols_get_cached(GeanyDocument *doc) +{ + return CALL_IF_EXISTS(doc_symbols_get_cached)(doc); +} + + +gboolean lsp_symbol_highlight_available(GeanyDocument *doc) +{ + return CALL_IF_EXISTS(doc_symbols_available)(doc); +} + + +void lsp_symbol_highlight_request(GeanyDocument *doc, LspSymbolRequestCallback callback, gpointer user_data) +{ + CALL_IF_EXISTS(symbol_highlight_request)(doc, callback, user_data); +} + + +const gchar *lsp_symbol_highlight_get_cached(GeanyDocument *doc) +{ + return CALL_IF_EXISTS(symbol_highlight_get_cached)(doc); +} diff --git a/src/lsp.h b/src/lsp.h new file mode 100644 index 0000000000..981f20dac3 --- /dev/null +++ b/src/lsp.h @@ -0,0 +1,81 @@ +/* + * lsp.h - this file is part of Geany, a fast and lightweight IDE + * + * Copyright 2023 The Geany contributors + * + * 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 GEANY_LSP_H +#define GEANY_LSP_H 1 + +#include "document.h" + +G_BEGIN_DECLS + +typedef void (*LspSymbolRequestCallback) (gpointer user_data); + +typedef struct { + gboolean (*autocomplete_available)(GeanyDocument *doc); + void (*autocomplete_perform)(GeanyDocument *doc); + + gboolean (*calltips_available)(GeanyDocument *doc); + void (*calltips_show)(GeanyDocument *doc); + + gboolean (*goto_available)(GeanyDocument *doc); + void (*goto_perform)(GeanyDocument *doc, gint pos, gboolean definition); + + gboolean (*doc_symbols_available)(GeanyDocument *doc); + void (*doc_symbols_request)(GeanyDocument *doc, LspSymbolRequestCallback callback, gpointer user_data); + GPtrArray *(*doc_symbols_get_cached)(GeanyDocument *doc); + + gboolean (*symbol_highlight_available)(GeanyDocument *doc); + void (*symbol_highlight_request)(GeanyDocument *doc, LspSymbolRequestCallback callback, gpointer user_data); + const gchar *(*symbol_highlight_get_cached)(GeanyDocument *doc); + + gchar _dummy[1024]; +} Lsp; + + +void lsp_register(Lsp *lsp); +void lsp_unregister(Lsp *lsp); + + +#ifdef GEANY_PRIVATE + +guint lsp_get_symbols_icon_id(guint kind); + +gboolean lsp_autocomplete_available(GeanyDocument *doc); +void lsp_autocomplete_perform(GeanyDocument *doc); + +gboolean lsp_calltips_available(GeanyDocument *doc); +void lsp_calltips_show(GeanyDocument *doc); + +gboolean lsp_goto_available(GeanyDocument *doc); +void lsp_goto_perform(GeanyDocument *doc, gint pos, gboolean definition); + +gboolean lsp_doc_symbols_available(GeanyDocument *doc); +void lsp_doc_symbols_request(GeanyDocument *doc, LspSymbolRequestCallback callback, gpointer user_data); +GPtrArray *lsp_doc_symbols_get_cached(GeanyDocument *doc); + +gboolean lsp_symbol_highlight_available(GeanyDocument *doc); +void lsp_symbol_highlight_request(GeanyDocument *doc, LspSymbolRequestCallback callback, gpointer user_data); +const gchar *lsp_symbol_highlight_get_cached(GeanyDocument *doc); + +#endif /* GEANY_PRIVATE */ + +G_END_DECLS + +#endif /* GEANY_LSP_H */ diff --git a/src/sidebar.c b/src/sidebar.c index 9d004f392f..21f118b5af 100644 --- a/src/sidebar.c +++ b/src/sidebar.c @@ -34,6 +34,7 @@ #include "filetypesprivate.h" #include "geanyobject.h" #include "keyfile.h" +#include "lsp.h" #include "navqueue.h" #include "stash.h" #include "support.h" @@ -172,11 +173,37 @@ static void create_default_tag_tree(void) } -/* update = rescan the tags for doc->filename */ -void sidebar_update_tag_list(GeanyDocument *doc, gboolean update) +/* changes the tree view to the given one, trying not to do useless changes */ +static void change_tree(GeanyDocument *doc, GtkWidget *new_child) { GtkWidget *child = gtk_bin_get_child(GTK_BIN(tag_window)); + /* only change the tag tree if it's actually not the same (to avoid flickering) and if + * it's the one of the current document (to avoid problems when e.g. reloading + * configuration files */ + if (child != new_child && doc == document_get_current()) + { + if (child) + gtk_container_remove(GTK_CONTAINER(tag_window), child); + gtk_container_add(GTK_CONTAINER(tag_window), new_child); + } +} + + +static void lsp_symbol_request_cb(gpointer user_data) +{ + GeanyDocument *doc = user_data; + + if (doc == document_get_current()) + doc->has_tags = symbols_recreate_tag_list(doc, SYMBOLS_SORT_USE_PREVIOUS); + + change_tree(doc, doc->has_tags ? doc->priv->tag_tree : tv.default_tag_tree); +} + + +/* update = rescan the tags for doc->filename */ +void sidebar_update_tag_list(GeanyDocument *doc, gboolean update) +{ g_return_if_fail(doc == NULL || doc->is_valid); if (update && doc != NULL) @@ -185,56 +212,36 @@ void sidebar_update_tag_list(GeanyDocument *doc, gboolean update) if (gtk_notebook_get_current_page(GTK_NOTEBOOK(main_widgets.sidebar_notebook)) != TREEVIEW_SYMBOL) return; /* don't bother updating symbol tree if we don't see it */ - /* changes the tree view to the given one, trying not to do useless changes */ - #define CHANGE_TREE(new_child) \ - G_STMT_START { \ - /* only change the tag tree if it's actually not the same (to avoid flickering) and if \ - * it's the one of the current document (to avoid problems when e.g. reloading \ - * configuration files */ \ - if (child != new_child && doc == document_get_current()) \ - { \ - if (child) \ - gtk_container_remove(GTK_CONTAINER(tag_window), child); \ - gtk_container_add(GTK_CONTAINER(tag_window), new_child); \ - } \ - } G_STMT_END - if (tv.default_tag_tree == NULL) create_default_tag_tree(); /* show default empty tag tree if there are no tags */ if (doc == NULL || doc->file_type == NULL || ! filetype_has_tags(doc->file_type)) { - CHANGE_TREE(tv.default_tag_tree); + change_tree(doc, tv.default_tag_tree); return; } + if (doc->priv->tag_tree == NULL) + { + doc->priv->tag_store = gtk_tree_store_new( + SYMBOLS_N_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING, TM_TYPE_TAG, G_TYPE_STRING); + doc->priv->tag_tree = gtk_tree_view_new(); + prepare_taglist(doc->priv->tag_tree, doc->priv->tag_store); + gtk_widget_show(doc->priv->tag_tree); + g_object_ref((gpointer)doc->priv->tag_tree); /* to hold it after removing */ + } + if (doc->priv->tag_tree_dirty) - { /* updating the tag list in the left tag window */ - if (doc->priv->tag_tree == NULL) - { - doc->priv->tag_store = gtk_tree_store_new( - SYMBOLS_N_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING, TM_TYPE_TAG, G_TYPE_STRING); - doc->priv->tag_tree = gtk_tree_view_new(); - prepare_taglist(doc->priv->tag_tree, doc->priv->tag_store); - gtk_widget_show(doc->priv->tag_tree); - g_object_ref((gpointer)doc->priv->tag_tree); /* to hold it after removing */ - } + { + if (lsp_doc_symbols_available(doc)) + lsp_doc_symbols_request(doc, lsp_symbol_request_cb, doc); doc->has_tags = symbols_recreate_tag_list(doc, SYMBOLS_SORT_USE_PREVIOUS); doc->priv->tag_tree_dirty = FALSE; } - if (doc->has_tags) - { - CHANGE_TREE(doc->priv->tag_tree); - } - else - { - CHANGE_TREE(tv.default_tag_tree); - } - - #undef CHANGE_TREE + change_tree(doc, doc->has_tags ? doc->priv->tag_tree : tv.default_tag_tree); } diff --git a/src/symbols.c b/src/symbols.c index 567a9f62ca..4c73cd0103 100644 --- a/src/symbols.c +++ b/src/symbols.c @@ -44,6 +44,7 @@ #include "filetypesprivate.h" #include "geanyobject.h" #include "highlighting.h" +#include "lsp.h" #include "main.h" #include "navqueue.h" #include "sciwrappers.h" @@ -305,22 +306,20 @@ static gint compare_symbol_lines(gconstpointer a, gconstpointer b) } -static GList *get_tag_list(GeanyDocument *doc, TMTagType tag_types) +static GList *get_tag_list(GPtrArray *tags_array, gchar *tag_filter, TMTagType tag_types) { GList *tag_names = NULL; guint i; gchar **tf_strv; - g_return_val_if_fail(doc, NULL); - - if (! doc->tm_file || ! doc->tm_file->tags_array) + if (!tags_array) return NULL; - tf_strv = g_strsplit_set(doc->priv->tag_filter, " ", -1); + tf_strv = g_strsplit_set(tag_filter, " ", -1); - for (i = 0; i < doc->tm_file->tags_array->len; ++i) + for (i = 0; i < tags_array->len; ++i) { - TMTag *tag = TM_TAG(doc->tm_file->tags_array->pdata[i]); + TMTag *tag = TM_TAG(tags_array->pdata[i]); if (tag->type & tag_types) { @@ -486,17 +485,18 @@ static void hide_empty_rows(GtkTreeStore *store) } -static const gchar *get_symbol_name(GeanyDocument *doc, const TMTag *tag, gboolean include_scope, - gboolean include_line) +static gchar *get_symbol_name(GeanyDocument *doc, const TMTag *tag, gboolean include_scope, + gboolean include_line, gboolean use_lsp) { gchar *utf8_name; const gchar *scope = tag->scope; - static GString *buffer = NULL; /* buffer will be small so we can keep it for reuse */ + GString *buffer = NULL; gboolean doc_is_utf8 = FALSE; /* encodings_convert_to_utf8_from_charset() fails with charset "None", so skip conversion * for None at this point completely */ - if (utils_str_equal(doc->encoding, "UTF-8") || + if (use_lsp || + utils_str_equal(doc->encoding, "UTF-8") || utils_str_equal(doc->encoding, "None")) doc_is_utf8 = TRUE; else /* normally the tags will always be in UTF-8 since we parse from our buffer, but a @@ -512,10 +512,7 @@ static const gchar *get_symbol_name(GeanyDocument *doc, const TMTag *tag, gboole if (utf8_name == NULL) return NULL; - if (! buffer) - buffer = g_string_new(NULL); - else - g_string_truncate(buffer, 0); + buffer = g_string_new(NULL); /* check first char of scope is a wordchar */ if (include_scope && scope && @@ -534,14 +531,24 @@ static const gchar *get_symbol_name(GeanyDocument *doc, const TMTag *tag, gboole if (include_line) g_string_append_printf(buffer, " [%lu]", tag->line); - return buffer->str; + return g_string_free(buffer, FALSE); } // Returns NULL if the tag is not a variable or callable -static gchar *get_symbol_tooltip(GeanyDocument *doc, const TMTag *tag, gboolean include_scope) +static gchar *get_symbol_tooltip(GeanyDocument *doc, const TMTag *tag, gboolean include_scope, + gboolean use_lsp) { - gchar *utf8_name = tm_parser_format_function(tag->lang, tag->name, + gchar *utf8_name; + + if (use_lsp) + { + if (tag->arglist) + return g_strdup(tag->arglist); + return get_symbol_name(doc, tag, include_scope, FALSE, TRUE); + } + + utf8_name = tm_parser_format_function(tag->lang, tag->name, tag->arglist, tag->var_type, tag->scope); if (!utf8_name && tag->var_type && @@ -888,6 +895,7 @@ static void tags_table_value_free(gpointer data) */ static void update_tree_tags(GeanyDocument *doc, GList **tags) { + gboolean use_lsp = lsp_doc_symbols_available(doc); GtkTreeStore *store = doc->priv->tag_store; GtkTreeModel *model = GTK_TREE_MODEL(store); GHashTable *parents_table; @@ -947,18 +955,18 @@ static void update_tree_tags(GeanyDocument *doc, GList **tags) if (!tm_tags_equal(tag, found)) { - const gchar *name; - gchar *tooltip; + gchar *name, *tooltip; /* only update fields that (can) have changed (name that holds line * number, tooltip, and the tag itself) */ - name = get_symbol_name(doc, found, parent_name == NULL, TRUE); - tooltip = get_symbol_tooltip(doc, found, FALSE); + name = get_symbol_name(doc, found, parent_name == NULL, TRUE, use_lsp); + tooltip = get_symbol_tooltip(doc, found, FALSE, use_lsp); gtk_tree_store_set(store, &iter, SYMBOLS_COLUMN_NAME, name, SYMBOLS_COLUMN_TOOLTIP, tooltip, SYMBOLS_COLUMN_TAG, found, -1); + g_free(name); g_free(tooltip); } @@ -982,16 +990,20 @@ static void update_tree_tags(GeanyDocument *doc, GList **tags) TMTag *tag = item->data; GtkTreeIter *parent, *parent_group; - parent_group = get_tag_type_iter(tag->lang, tag->type); + parent_group = use_lsp ? NULL : get_tag_type_iter(tag->lang, tag->type); /* tv_iters[0] is reserved for the "Symbols" group */ - parent = ui_prefs.symbols_group_by_type ? parent_group : &tv_iters[0]; - if (parent_group) + parent = (ui_prefs.symbols_group_by_type && !use_lsp) ? parent_group : &tv_iters[0]; + if (use_lsp || parent_group) { gboolean expand; - const gchar *name; + gchar *name, *tooltip; const gchar *parent_name; - gchar *tooltip; - GdkPixbuf *icon = get_child_icon(store, parent_group); + GdkPixbuf *icon; + + if (use_lsp) + icon = g_object_ref(symbols_icons[lsp_get_symbols_icon_id(tag->type)].pixbuf); + else + icon = get_child_icon(store, parent_group); parent_name = get_parent_name(tag); if (parent_name) @@ -1009,14 +1021,15 @@ static void update_tree_tags(GeanyDocument *doc, GList **tags) expand = ! gtk_tree_model_iter_has_child(model, parent); /* insert the new element */ - name = get_symbol_name(doc, tag, parent_name == NULL, TRUE); - tooltip = get_symbol_tooltip(doc, tag, FALSE); + name = get_symbol_name(doc, tag, parent_name == NULL, TRUE, use_lsp); + tooltip = get_symbol_tooltip(doc, tag, FALSE, use_lsp); gtk_tree_store_insert_with_values(store, &iter, parent, 0, SYMBOLS_COLUMN_NAME, name, SYMBOLS_COLUMN_TOOLTIP, tooltip, SYMBOLS_COLUMN_ICON, icon, SYMBOLS_COLUMN_TAG, tag, -1); + g_free(name); g_free(tooltip); if (G_LIKELY(icon)) g_object_unref(icon); @@ -1136,18 +1149,28 @@ static void sort_tree(GtkTreeStore *store, gboolean sort_by_name) gboolean symbols_recreate_tag_list(GeanyDocument *doc, gint sort_mode) { - GList *tags; + gboolean use_lsp; + GList *tags = NULL; g_return_val_if_fail(DOC_VALID(doc), FALSE); - tags = get_tag_list(doc, ~(tm_tag_local_var_t | tm_tag_include_t)); + use_lsp = lsp_doc_symbols_available(doc); + + if (use_lsp) + tags = get_tag_list(lsp_doc_symbols_get_cached(doc), doc->priv->tag_filter, tm_tag_max_t); + else + { + if (doc->tm_file && doc->tm_file->tags_array) + tags = get_tag_list(doc->tm_file->tags_array, doc->priv->tag_filter, + ~(tm_tag_local_var_t | tm_tag_include_t)); + } if (tags == NULL) return FALSE; - if (doc->priv->symbols_group_by_type != ui_prefs.symbols_group_by_type) + if (doc->priv->symbols_group_by_type != (ui_prefs.symbols_group_by_type && !use_lsp)) gtk_tree_store_clear(doc->priv->tag_store); - doc->priv->symbols_group_by_type = ui_prefs.symbols_group_by_type; + doc->priv->symbols_group_by_type = ui_prefs.symbols_group_by_type && !use_lsp; /* FIXME: Not sure why we detached the model here? */ @@ -1455,10 +1478,10 @@ static void show_goto_popup(GeanyDocument *doc, GPtrArray *tags, gboolean have_b gchar *fname = short_names[i]; gchar *text; gchar *tooltip; - gchar *sym = get_symbol_tooltip(doc, tmtag, TRUE); + gchar *sym = get_symbol_tooltip(doc, tmtag, TRUE, FALSE); if (!sym) - sym = g_strdup(get_symbol_name(doc, tmtag, TRUE, FALSE)); + sym = get_symbol_name(doc, tmtag, TRUE, FALSE, FALSE); if (!sym) sym = g_strdup(""); @@ -1686,8 +1709,16 @@ static gboolean goto_tag(const gchar *name, gboolean definition) } -gboolean symbols_goto_tag(const gchar *name, gboolean definition) +gboolean symbols_goto_tag(const gchar *name, gint pos, gboolean definition) { + GeanyDocument *doc = document_get_current(); + + if (lsp_goto_available(doc)) + { + lsp_goto_perform(doc, pos, definition); + return TRUE; + } + if (goto_tag(name, definition)) return TRUE; diff --git a/src/symbols.h b/src/symbols.h index d455d5c142..25a9d91db6 100644 --- a/src/symbols.h +++ b/src/symbols.h @@ -57,7 +57,7 @@ gint symbols_generate_global_tags(gint argc, gchar **argv, gboolean want_preproc void symbols_show_load_tags_dialog(void); -gboolean symbols_goto_tag(const gchar *name, gboolean definition); +gboolean symbols_goto_tag(const gchar *name, gint pos, gboolean definition); gint symbols_get_current_function(GeanyDocument *doc, const gchar **tagname);