Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Possible plugin extension interface for symbol tree #3910

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

techee
Copy link
Member

@techee techee commented Jun 17, 2024

This is one more attempt on unifying TMTags and externally provided symbols for the symbol tree before giving up and making the plugins create the symbol tree by themselves (which leads to quite a significant code duplication). This time (unlike the previous attempts) I'm quite happy with the result though. I called the new structure GeanySymbol - could be renamed to something else if someone has better idea.

The idea is to make GeanySymbol operate in two modes:

  • either it is backed by a TMTag and created using GeanySymbol *geany_symbol_new_from_tag(TMTag *tag)
  • or it is created by some external source such as the LSP plugin using
GeanySymbol *geany_symbol_new(gchar *name, gchar *detail, gchar *scope, gchar *file,
	TMParserType lang, glong kind, gulong line, gulong pos, guint icon)

The corresponding structure looks this way:

typedef struct GeanySymbol
{
	gchar *name;
	gchar *detail;
	gchar *scope;
	gchar *file;
	TMParserType lang;
	gulong line;
	gulong pos;
	glong kind;
	guint icon;

	TMTag *tag;

	gint refcount; /* the reference count of the symbol */
} GeanySymbol;

where either tag != NULL when using geany_symbol_new_from_tag(), or tag == NULL for geany_symbol_new().

The important part is that the members of GeanySymbol cannot be accessed directly from other files but only using getters that either use the tag or the member of GeanySymbol such as

const gchar *geany_symbol_get_name(const GeanySymbol *sym)
{
	if (sym->tag)
		return sym->tag->name;
	return sym->name;
}

By this interface, all callers like the symbol tree implementation are completely shielded from the details of where the symbol comes from. The bulk of the diffs inside symbols.c is pretty much just using this interface instead of using TMTag directly and renaming tag to symbol. get_symbol_name() and get_symbol_tooltip() were moved to GeanySymbol implementation, and also (previously inlined) geany_symbol_get_name_with_scope() was moved there too.

The additional PluginExtension interface for using these symbols would be more or less identical to what I proposed in #3850, i.e.

	gboolean (*doc_symbols_provided)(GeanyDocument *doc);
 	GPtrArray *(*doc_symbols_get)(GeanyDocument *doc);

and

  • either additional void (*doc_symbols_request)(GeanyDocument *doc, GCallback on_done); in this interface making Geany request the extension for symbols (asynchronously)
  • or some symbol_tree_reload() function the plugin could call which would make Geany re-request symbols using doc_symbols_get() after the plugin gets them e.g. from the LSP server

@b4n How does something like this sound? The implementation here is incomplete (only tested with the TMTag "backend" of GeanySymbol) but I don't want to continue in case you have some reservations regarding this approach.

@techee techee added work in progress lsp Related to LSP API and plugins labels Jun 17, 2024
@kugel-
Copy link
Member

kugel- commented Jun 17, 2024

If it has a refcount, why not make it a GObject so that it's easy to create from non-C plugins (python via peasy for example).

@techee
Copy link
Member Author

techee commented Jun 17, 2024

If it has a refcount, why not make it a GObject so that it's easy to create from non-C plugins (python via peasy for example).

I just copied what was in TMTag to implement it quickly but good point, it could be a GObject. I'm not so familiar with what might be necessary for non-C plugins but how does #3849 look from this perspective? Could anything be improved there?

@kugel-
Copy link
Member

kugel- commented Jun 17, 2024

Would need to check in detail. Have you looked the gtkdoc that's generated from the new interfaces?

@techee
Copy link
Member Author

techee commented Jun 17, 2024

Have you looked the gtkdoc that's generated from the new interfaces?

What should I look for exactly? My main worry is

typedef struct
{
	gboolean (*autocomplete_provided)(GeanyDocument *doc, gpointer data);
	void (*autocomplete_perform)(GeanyDocument *doc, gboolean force, gpointer data);
	gboolean (*calltips_provided)(GeanyDocument *doc, gpointer data);
	void (*calltips_show)(GeanyDocument *doc, gboolean force, gpointer data);
	gboolean (*goto_provided)(GeanyDocument *doc, gpointer data);
	gboolean (*goto_perform)(GeanyDocument *doc, gint pos, gboolean definition, gpointer data);
	gboolean (*symbol_highlight_provided)(GeanyDocument *doc, gpointer data);
} PluginExtension;

which in C you would use this way:

static gboolean autocomplete_provided(GeanyDocument *doc, gpointer user_data)
{
}

static void autocomplete_perform(GeanyDocument *doc, gboolean force, gpointer user_data)
{
}

static PluginExtension extension = {
	.autocomplete_provided = autocomplete_provided,
	.autocomplete_perform = autocomplete_perform
};

void plugin_init(G_GNUC_UNUSED GeanyData * data)
{
	plugin_extension_register(&extension, 100, NULL);
}

PluginExtension can also be allocated dynamically which would probably be necessary for other languages. Is such an interface OK for other languages?

@kugel-
Copy link
Member

kugel- commented Jun 18, 2024

Generally speaking, non-C is supported well only for GObject-based interfaces. A python plugin can easily define a GObject-derived type purely implemented in Python.

For anything else you need at least some supporting C code around. This is essentially what the "lib"-part of peasy does. It defines GObject-derived wrappers around existing Geany interfaces so that plugins make instances and inherit properly. Not all Geany interfaces need wrappers. We declare some of them as "GBoxed" such that the gobject-introspection magic makes them available to non-C languages automatically. We feed the gobject-introspection machinery mostly by converting the doxygen output to gtkdoc, and then have it generate the typelib from that.

So for PluiginExtension we would need to extend peasy for sure.

I asked specifically for GeanySymbol. At the very least it needs to be a GBoxed, but if it has a refcount and getters/setters for anything anyway it could also be a real GObject. Then peasy wouldn't need to wrap this type much.

@b4n
Copy link
Member

b4n commented Jun 18, 2024

(no time for this right now, but…)
GObjects are nice for language inteop with GI indeed, but they aren't as cheap as e.g. a boxed type (just think about all the bells and whistles like properties, signals, private data, etc) and AFAIK a boxed type that has its own refcount works fine (just lies in copy() which actually just refs, and free() which just unrefs), and it's what we have for TMTag currently IIRC. I don't expect much custom code to support a proper GBoxed in Peasy, am I mistaken?

And for PluginExtensions I guess the worse part is how to annotate the data, I'm not sure which scope to give it. And yeah it might need some custom code there (or adjust the API if there's a better way that is reasonably easy and we agree on… :D).

@kugel-
Copy link
Member

kugel- commented Jun 18, 2024

GBoxed works for non-C. You just have to add a non-C-friendly allocation/constructor because there's no generic g_boxed_new().

@techee
Copy link
Member Author

techee commented Jun 19, 2024

GObjects are nice for language inteop with GI indeed, but they aren't as cheap

That's a good point, it should be cheap because for the symbol tree code, all the TMTags have to be wrapped by GeanySymbol first and it would be best to have the overhead as small as possible. If we wanted, we could change GeanySymbol to something like

typedef struct GeanySymbol
{
	ExternalSymbol *ext;
	TMTag *tag;

	gint refcount;
} GeanySymbol;

where ExternalSymbol would contain all the other members so GeanySymbol with the TMTag "backend" would be smaller and would not have to zero-fill so many members.

GBoxed works for non-C. You just have to add a non-C-friendly allocation/constructor because there's no generic g_boxed_new().

For GeanySymbol, this is hidden behind the two constructors:

GeanySymbol *geany_symbol_new_from_tag(TMTag *tag);
GeanySymbol *geany_symbol_new(gchar *name, gchar *detail, gchar *scope, gchar *file,
	TMParserType lang, glong kind, gulong line, gulong pos, guint icon);

so I believe it should be satisfied already.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
lsp Related to LSP API and plugins work in progress
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants