Skip to content

Commit

Permalink
projectorganizer: Add popup panel for navigation
Browse files Browse the repository at this point in the history
This feature allows quick navigation do document/workspace symbols
based on their names, open files, and line numbers.

The panel's code is mostly stolen from the LSP plugin which in turn
stole it from the Colomban Wendling's Commander plugin.
  • Loading branch information
techee committed Apr 26, 2024
1 parent 644550b commit 60b4c81
Show file tree
Hide file tree
Showing 8 changed files with 977 additions and 6 deletions.
1 change: 1 addition & 0 deletions po/POTFILES.in
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ pretty-printer/src/PluginEntry.c
pretty-printer/src/ConfigUI.c

# ProjectOrganizer
projectorganizer/src/prjorg-goto-anywhere.c
projectorganizer/src/prjorg-main.c
projectorganizer/src/prjorg-menu.c
projectorganizer/src/prjorg-project.c
Expand Down
6 changes: 6 additions & 0 deletions projectorganizer/README
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,12 @@ Project Organizer adds some extra entries under the Project menu:
the properties, it opens a project file with the same base name (without extension)
matching header patterns (and vice versa). If the files are already open, it
just switches the document tabs. Nothing happens if no matching file is found.
* Go to anywhere, Go to document symbol, Go to workspace symbol, Go to line -
these items allow to perform jump to the entered destination. The popup window is
identical for all of these actions, the only difference is the pre-filled prefix
that determines the type of the go to. No prefix performs the search in open files,
@ performs the search in current document's symbols, # performs the search in
all workspace symbols, and : performs navigation to the specified line.

Each of these entries can be assigned a key binding under Edit->Preferences->Keybindings.

Expand Down
6 changes: 5 additions & 1 deletion projectorganizer/src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ projectorganizer_la_SOURCES = \
prjorg-utils.h \
prjorg-utils.c \
prjorg-menu.h \
prjorg-menu.c
prjorg-menu.c \
prjorg-goto-panel.h \
prjorg-goto-panel.c \
prjorg-goto-anywhere.h \
prjorg-goto-anywhere.c

projectorganizer_la_CPPFLAGS = $(AM_CPPFLAGS) \
-DG_LOG_DOMAIN=\"ProjectOrganizer\"
Expand Down
332 changes: 332 additions & 0 deletions projectorganizer/src/prjorg-goto-anywhere.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,332 @@
/*
* Copyright 2023 Jiri Techet <[email protected]>
*
* 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 "prjorg-goto-anywhere.h"
#include "prjorg-goto-panel.h"

#include <gtk/gtk.h>
#include <geanyplugin.h>


#define SSM(s, m, w, l) scintilla_send_message((s), (m), (w), (l))


typedef struct
{
GeanyDocument *doc;
gchar *query;
} DocQueryData;


extern GeanyData *geany_data;


static void goto_line(GeanyDocument *doc, const gchar *line_str)
{
GPtrArray *arr = g_ptr_array_new_full(0, (GDestroyNotify)prjorg_goto_symbol_free);
gint lineno = atoi(line_str);
gint linenum = sci_get_line_count(doc->editor->sci);
guint i;

for (i = 0; i < 4; i++)
{
PrjorgGotoSymbol *sym = g_new0(PrjorgGotoSymbol, 1);

sym->file_name = utils_get_utf8_from_locale(doc->real_path);
sym->icon = _ICON_OTHER;

switch (i)
{
case 0:
sym->name = g_strdup(_("line typed above"));
if (lineno == 0)
sym->line = sci_get_current_line(doc->editor->sci) + 1;
else if (lineno > linenum)
sym->line = linenum;
else
sym->line = lineno;
break;

case 1:
sym->name = g_strdup(_("start"));
sym->line = 1;
break;

case 2:
sym->name = g_strdup(_("middle"));
sym->line = linenum / 2;
break;

case 3:
sym->name = g_strdup(_("end"));
sym->line = linenum;
break;
}

g_ptr_array_add(arr, sym);
}

prjorg_goto_panel_fill(arr);

g_ptr_array_free(arr, TRUE);
}


static void goto_file(const gchar *file_str)
{
GPtrArray *arr = g_ptr_array_new_full(0, (GDestroyNotify)prjorg_goto_symbol_free);
GPtrArray *filtered;
guint i;

foreach_document(i)
{
GeanyDocument *doc = documents[i];
PrjorgGotoSymbol *sym;

if (!doc->real_path)
continue;

sym = g_new0(PrjorgGotoSymbol, 1);
sym->name = g_path_get_basename(doc->real_path);
sym->file_name = utils_get_utf8_from_locale(doc->real_path);
sym->icon = _ICON_OTHER;
g_ptr_array_add(arr, sym);
}

filtered = prjorg_goto_panel_filter(arr, file_str);
prjorg_goto_panel_fill(filtered);

g_ptr_array_free(filtered, TRUE);
g_ptr_array_free(arr, TRUE);
}


/* symplified hard-coded icons because we don't have access to Geany icon mappings */
static int get_icon(TMTagType type)
{
switch (type)
{
case tm_tag_class_t:
return _ICON_CLASS;
case tm_tag_macro_t:
case tm_tag_macro_with_arg_t:
case tm_tag_undef_t:
return _ICON_MACRO;
case tm_tag_enum_t:
case tm_tag_struct_t:
case tm_tag_typedef_t:
case tm_tag_union_t:
return _ICON_STRUCT;
case tm_tag_enumerator_t:
case tm_tag_field_t:
case tm_tag_member_t:
return _ICON_MEMBER;
case tm_tag_method_t:
case tm_tag_function_t:
case tm_tag_prototype_t:
return _ICON_METHOD;
case tm_tag_interface_t:
case tm_tag_namespace_t:
case tm_tag_package_t:
return _ICON_NAMESPACE;
case tm_tag_variable_t:
case tm_tag_externvar_t:
case tm_tag_local_var_t:
case tm_tag_include_t:
return _ICON_VAR;
case tm_tag_other_t:
return _ICON_OTHER;
default:
return _ICON_NONE;
}
}


/* stolen from Geany */
static gboolean langs_compatible(TMParserType lang, TMParserType other)
{
if (lang == other)
return TRUE;
/* Accept CPP tags for C lang and vice versa - we don't have acces to
* Geany's TM_PARSER_ constants so let's hard-code 0 and 1 here (not too
* terrible things will happen if Geany changes these to something else) */
else if (lang == 0 && other == 1)
return TRUE;
else if (lang == 1 && other == 0)
return TRUE;

return FALSE;
}


static void goto_tm_symbol(const gchar *query, GPtrArray *tags, TMParserType lang)
{
GPtrArray *converted = g_ptr_array_new_full(0, (GDestroyNotify)prjorg_goto_symbol_free);
GPtrArray *filtered;
TMTag *tag;
guint i;

if (tags)
{
foreach_ptr_array(tag, i, tags)
{
if (tag->file && langs_compatible(tag->lang, lang) &&
!(tag->type & (tm_tag_include_t | tm_tag_local_var_t)))
{
PrjorgGotoSymbol *sym = g_new0(PrjorgGotoSymbol, 1);
sym->name = g_strdup(tag->name);
sym->file_name = utils_get_utf8_from_locale(tag->file->file_name);
sym->line = tag->line;
sym->icon = get_icon(tag->type);

g_ptr_array_add(converted, sym);
}
}
}

filtered = prjorg_goto_panel_filter(converted, query);
prjorg_goto_panel_fill(filtered);

g_ptr_array_free(filtered, TRUE);
g_ptr_array_free(converted, TRUE);
}


static void perform_lookup(const gchar *query)
{
GeanyDocument *doc = document_get_current();
const gchar *query_str = query ? query : "";

if (g_str_has_prefix(query_str, "#"))
{
// TODO: possibly improve performance by binary searching the start and the end point
goto_tm_symbol(query_str+1, geany_data->app->tm_workspace->tags_array, doc->file_type->lang);
}
else if (g_str_has_prefix(query_str, "@"))
{
GPtrArray *tags = doc->tm_file ? doc->tm_file->tags_array : g_ptr_array_new();
goto_tm_symbol(query_str+1, tags, doc->file_type->lang);
if (!doc->tm_file)
g_ptr_array_free(tags, TRUE);
}
else if (g_str_has_prefix(query_str, ":"))
goto_line(doc, query_str+1);
else
goto_file(query_str);
}


static gchar *get_current_iden(GeanyDocument *doc, gint current_pos)
{
//TODO: use configured wordchars (also change in Geany)
const gchar *wordchars = GEANY_WORDCHARS;
GeanyFiletypeID ft = doc->file_type->id;
ScintillaObject *sci = doc->editor->sci;
gint start_pos, end_pos, pos;

if (ft == GEANY_FILETYPES_LATEX)
wordchars = GEANY_WORDCHARS"\\"; /* add \ to word chars if we are in a LaTeX file */
else if (ft == GEANY_FILETYPES_CSS)
wordchars = GEANY_WORDCHARS"-"; /* add - because they are part of property names */

pos = current_pos;
while (TRUE)
{
gint new_pos = SSM(sci, SCI_POSITIONBEFORE, pos, 0);
if (new_pos == pos)
break;
if (pos - new_pos == 1)
{
gchar c = sci_get_char_at(sci, new_pos);
if (!strchr(wordchars, c))
break;
}
pos = new_pos;
}
start_pos = pos;

pos = current_pos;
while (TRUE)
{
gint new_pos = SSM(sci, SCI_POSITIONAFTER, pos, 0);
if (new_pos == pos)
break;
if (new_pos - pos == 1)
{
gchar c = sci_get_char_at(sci, pos);
if (!strchr(wordchars, c))
break;
}
pos = new_pos;
}
end_pos = pos;

if (start_pos == end_pos)
return NULL;

return sci_get_contents_range(sci, start_pos, end_pos);
}


static void goto_panel_query(const gchar *query_type, gboolean prefill)
{
GeanyDocument *doc = document_get_current();
gint pos = sci_get_current_position(doc->editor->sci);
gchar *query = NULL;

if (!doc)
return;

if (prefill)
query = get_current_iden(doc, pos);
if (!query)
query = g_strdup("");
SETPTR(query, g_strconcat(query_type, query, NULL));

prjorg_goto_panel_show(query, perform_lookup);

g_free(query);
}


void prjorg_goto_anywhere_for_workspace(void)
{
goto_panel_query("#", TRUE);
}


void prjorg_goto_anywhere_for_doc(void)
{
goto_panel_query("@", TRUE);
}


void prjorg_goto_anywhere_for_line(void)
{
goto_panel_query(":", FALSE);
}


void prjorg_goto_anywhere_for_file(void)
{
goto_panel_query("", FALSE);
}
28 changes: 28 additions & 0 deletions projectorganizer/src/prjorg-goto-anywhere.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2023 Jiri Techet <[email protected]>
*
* 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 PRJORG_GOTO_ANYWHERE_H
#define PRJORG_GOTO_ANYWHERE_H 1


void prjorg_goto_anywhere_for_workspace(void);
void prjorg_goto_anywhere_for_doc(void);
void prjorg_goto_anywhere_for_line(void);
void prjorg_goto_anywhere_for_file(void);

#endif /* PRJORG_GOTO_ANYWHERE_H */
Loading

0 comments on commit 60b4c81

Please sign in to comment.