1 /* ide-buffer-addin.c
2  *
3  * Copyright 2017-2019 Christian Hergert <chergert@redhat.com>
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  * SPDX-License-Identifier: GPL-3.0-or-later
19  */
20 
21 #define G_LOG_DOMAIN "ide-buffer-addin"
22 
23 #include "config.h"
24 
25 #include <libide-threading.h>
26 #include <libpeas/peas.h>
27 
28 #include "ide-buffer.h"
29 #include "ide-buffer-addin.h"
30 #include "ide-buffer-addin-private.h"
31 #include "ide-buffer-private.h"
32 
33 /**
34  * SECTION:ide-buffer-addin
35  * @title: IdeBufferAddin
36  * @short_description: addins for #IdeBuffer
37  *
38  * The #IdeBufferAddin allows a plugin to register an object that will be
39  * created with every #IdeBuffer. It can register extra features with the
40  * buffer or extend it as necessary.
41  *
42  * Once use of #IdeBufferAddin is to add a spellchecker to the buffer that
43  * may be used by views to show the misspelled words. This is preferrable
44  * to adding a spellchecker in each view because it allows for multiple
45  * views to share one spellcheker on the underlying buffer.
46  *
47  * Since: 3.32
48  */
49 
G_DEFINE_INTERFACE(IdeBufferAddin,ide_buffer_addin,G_TYPE_OBJECT)50 G_DEFINE_INTERFACE (IdeBufferAddin, ide_buffer_addin, G_TYPE_OBJECT)
51 
52 static void
53 ide_buffer_addin_real_settle_async (IdeBufferAddin      *self,
54                                     GCancellable        *cancellable,
55                                     GAsyncReadyCallback  callback,
56                                     gpointer             user_data)
57 {
58   g_autoptr(IdeTask) task = NULL;
59 
60   task = ide_task_new (self, cancellable, callback, user_data);
61   ide_task_set_source_tag (task, ide_buffer_addin_real_settle_async);
62   ide_task_set_priority (task, G_PRIORITY_HIGH);
63   ide_task_return_boolean (task, TRUE);
64 }
65 
66 static gboolean
ide_buffer_addin_real_settle_finish(IdeBufferAddin * self,GAsyncResult * result,GError ** error)67 ide_buffer_addin_real_settle_finish (IdeBufferAddin  *self,
68                                      GAsyncResult    *result,
69                                      GError         **error)
70 {
71   return ide_task_propagate_boolean (IDE_TASK (result), error);
72 }
73 
74 static void
ide_buffer_addin_default_init(IdeBufferAddinInterface * iface)75 ide_buffer_addin_default_init (IdeBufferAddinInterface *iface)
76 {
77   iface->settle_async = ide_buffer_addin_real_settle_async;
78   iface->settle_finish = ide_buffer_addin_real_settle_finish;
79 }
80 
81 /**
82  * ide_buffer_addin_load:
83  * @self: an #IdeBufferAddin
84  * @buffer: an #IdeBuffer
85  *
86  * This calls the load virtual function of #IdeBufferAddin to request
87  * that the addin load itself.
88  *
89  * Since: 3.32
90  */
91 void
ide_buffer_addin_load(IdeBufferAddin * self,IdeBuffer * buffer)92 ide_buffer_addin_load (IdeBufferAddin *self,
93                        IdeBuffer      *buffer)
94 {
95   g_return_if_fail (IDE_IS_MAIN_THREAD ());
96   g_return_if_fail (IDE_IS_BUFFER_ADDIN (self));
97   g_return_if_fail (IDE_IS_BUFFER (buffer));
98 
99   if (IDE_BUFFER_ADDIN_GET_IFACE (self)->load)
100     IDE_BUFFER_ADDIN_GET_IFACE (self)->load (self, buffer);
101 }
102 
103 /**
104  * ide_buffer_addin_unload:
105  * @self: an #IdeBufferAddin
106  * @buffer: an #IdeBuffer
107  *
108  * This calls the unload virtual function of #IdeBufferAddin to request
109  * that the addin unload itself.
110  *
111  * The addin should cancel any in-flight operations and attempt to drop
112  * references to the buffer or any other machinery as soon as possible.
113  *
114  * Since: 3.32
115  */
116 void
ide_buffer_addin_unload(IdeBufferAddin * self,IdeBuffer * buffer)117 ide_buffer_addin_unload (IdeBufferAddin *self,
118                          IdeBuffer      *buffer)
119 {
120   g_return_if_fail (IDE_IS_MAIN_THREAD ());
121   g_return_if_fail (IDE_IS_BUFFER_ADDIN (self));
122   g_return_if_fail (IDE_IS_BUFFER (buffer));
123 
124   if (IDE_BUFFER_ADDIN_GET_IFACE (self)->unload)
125     IDE_BUFFER_ADDIN_GET_IFACE (self)->unload (self, buffer);
126 }
127 
128 /**
129  * ide_buffer_addin_file_loaded:
130  * @self: a #IdeBufferAddin
131  * @buffer: an #IdeBuffer
132  * @file: a #GFile
133  *
134  * This function is called for an addin after a file has been loaded from disk.
135  *
136  * It is not guaranteed that this function will be called for addins that were
137  * loaded after the buffer already loaded a file.
138  *
139  * Since: 3.32
140  */
141 void
ide_buffer_addin_file_loaded(IdeBufferAddin * self,IdeBuffer * buffer,GFile * file)142 ide_buffer_addin_file_loaded (IdeBufferAddin *self,
143                               IdeBuffer      *buffer,
144                               GFile          *file)
145 {
146   g_return_if_fail (IDE_IS_MAIN_THREAD ());
147   g_return_if_fail (IDE_IS_BUFFER_ADDIN (self));
148   g_return_if_fail (IDE_IS_BUFFER (buffer));
149   g_return_if_fail (G_IS_FILE (file));
150 
151   if (IDE_BUFFER_ADDIN_GET_IFACE (self)->file_loaded)
152     IDE_BUFFER_ADDIN_GET_IFACE (self)->file_loaded (self, buffer, file);
153 }
154 
155 /**
156  * ide_buffer_addin_save_file:
157  * @self: a #IdeBufferAddin
158  * @buffer: an #IdeBuffer
159  * @file: a #GFile
160  *
161  * This function gives a chance for plugins to modify the buffer right before
162  * writing to disk.
163  *
164  * Since: 3.32
165  */
166 void
ide_buffer_addin_save_file(IdeBufferAddin * self,IdeBuffer * buffer,GFile * file)167 ide_buffer_addin_save_file (IdeBufferAddin *self,
168                             IdeBuffer      *buffer,
169                             GFile          *file)
170 {
171   g_return_if_fail (IDE_IS_MAIN_THREAD ());
172   g_return_if_fail (IDE_IS_BUFFER_ADDIN (self));
173   g_return_if_fail (IDE_IS_BUFFER (buffer));
174   g_return_if_fail (G_IS_FILE (file));
175 
176   if (IDE_BUFFER_ADDIN_GET_IFACE (self)->save_file)
177     IDE_BUFFER_ADDIN_GET_IFACE (self)->save_file (self, buffer, file);
178 }
179 
180 /**
181  * ide_buffer_addin_file_saved:
182  * @self: a #IdeBufferAddin
183  * @buffer: an #IdeBuffer
184  * @file: a #GFile
185  *
186  * This function is called for an addin after a file has been saved to disk.
187  *
188  * Since: 3.32
189  */
190 void
ide_buffer_addin_file_saved(IdeBufferAddin * self,IdeBuffer * buffer,GFile * file)191 ide_buffer_addin_file_saved (IdeBufferAddin *self,
192                              IdeBuffer      *buffer,
193                              GFile          *file)
194 {
195   g_return_if_fail (IDE_IS_MAIN_THREAD ());
196   g_return_if_fail (IDE_IS_BUFFER_ADDIN (self));
197   g_return_if_fail (IDE_IS_BUFFER (buffer));
198   g_return_if_fail (G_IS_FILE (file));
199 
200   if (IDE_BUFFER_ADDIN_GET_IFACE (self)->file_saved)
201     IDE_BUFFER_ADDIN_GET_IFACE (self)->file_saved (self, buffer, file);
202 }
203 
204 /**
205  * ide_buffer_addin_language_set:
206  * @self: an #IdeBufferAddin
207  * @buffer: an #IdeBuffer
208  * @language_id: the GtkSourceView language identifier
209  *
210  * This vfunc is called when the source language in the buffer changes. This
211  * will only be delivered to addins that support multiple languages.
212  *
213  * Since: 3.32
214  */
215 void
ide_buffer_addin_language_set(IdeBufferAddin * self,IdeBuffer * buffer,const gchar * language_id)216 ide_buffer_addin_language_set (IdeBufferAddin *self,
217                                IdeBuffer      *buffer,
218                                const gchar    *language_id)
219 {
220   g_return_if_fail (IDE_IS_BUFFER_ADDIN (self));
221   g_return_if_fail (IDE_IS_BUFFER (buffer));
222 
223   if (IDE_BUFFER_ADDIN_GET_IFACE (self)->language_set)
224     IDE_BUFFER_ADDIN_GET_IFACE (self)->language_set (self, buffer, language_id);
225 }
226 
227 /**
228  * ide_buffer_addin_change_settled:
229  * @self: an #IdeBufferAddin
230  * @buffer: an #ideBuffer
231  *
232  * This function is called when the buffer has settled after a number of
233  * changes provided by the user. It is a convenient way to know when you
234  * should perform more background work without having to coalesce work
235  * yourself.
236  *
237  * Since: 3.32
238  */
239 void
ide_buffer_addin_change_settled(IdeBufferAddin * self,IdeBuffer * buffer)240 ide_buffer_addin_change_settled (IdeBufferAddin *self,
241                                  IdeBuffer      *buffer)
242 {
243   g_return_if_fail (IDE_IS_BUFFER_ADDIN (self));
244   g_return_if_fail (IDE_IS_BUFFER (buffer));
245 
246   if (IDE_BUFFER_ADDIN_GET_IFACE (self)->change_settled)
247     IDE_BUFFER_ADDIN_GET_IFACE (self)->change_settled (self, buffer);
248 }
249 
250 /**
251  * ide_buffer_addin_style_scheme_changed:
252  * @self: an #IdeBufferAddin
253  * @buffer: an #IdeBuffer
254  *
255  * This function is called when the #GtkSourceStyleScheme of the #IdeBuffer
256  * has changed.
257  *
258  * Since: 3.32
259  */
260 void
ide_buffer_addin_style_scheme_changed(IdeBufferAddin * self,IdeBuffer * buffer)261 ide_buffer_addin_style_scheme_changed (IdeBufferAddin *self,
262                                        IdeBuffer      *buffer)
263 {
264   g_return_if_fail (IDE_IS_BUFFER_ADDIN (self));
265   g_return_if_fail (IDE_IS_BUFFER (buffer));
266 
267   if (IDE_BUFFER_ADDIN_GET_IFACE (self)->style_scheme_changed)
268     IDE_BUFFER_ADDIN_GET_IFACE (self)->style_scheme_changed (self, buffer);
269 }
270 
271 /**
272  * ide_buffer_addin_find_by_module_name:
273  * @buffer: an #IdeBuffer
274  * @module_name: the module name of the addin
275  *
276  * Locates an addin attached to the #IdeBuffer by the name of the module
277  * that provides the addin.
278  *
279  * Returns: (transfer none) (nullable): An #IdeBufferAddin or %NULL
280  *
281  * Since: 3.32
282  */
283 IdeBufferAddin *
ide_buffer_addin_find_by_module_name(IdeBuffer * buffer,const gchar * module_name)284 ide_buffer_addin_find_by_module_name (IdeBuffer   *buffer,
285                                       const gchar *module_name)
286 {
287   PeasPluginInfo *plugin_info;
288   IdeExtensionSetAdapter *set;
289   PeasExtension *ret = NULL;
290 
291   g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
292   g_return_val_if_fail (IDE_IS_BUFFER (buffer), NULL);
293   g_return_val_if_fail (module_name != NULL, NULL);
294 
295   set = _ide_buffer_get_addins (buffer);
296 
297   /* Addins might not be loaded */
298   if (set == NULL)
299     return NULL;
300 
301   plugin_info = peas_engine_get_plugin_info (peas_engine_get_default (), module_name);
302 
303   if (plugin_info != NULL)
304     ret = ide_extension_set_adapter_get_extension (set, plugin_info);
305   else
306     g_warning ("Failed to locate addin named %s", module_name);
307 
308   return ret ? IDE_BUFFER_ADDIN (ret) : NULL;
309 }
310 
311 void
_ide_buffer_addin_load_cb(IdeExtensionSetAdapter * set,PeasPluginInfo * plugin_info,PeasExtension * exten,gpointer user_data)312 _ide_buffer_addin_load_cb (IdeExtensionSetAdapter *set,
313                            PeasPluginInfo         *plugin_info,
314                            PeasExtension          *exten,
315                            gpointer                user_data)
316 {
317   IdeBuffer *buffer = user_data;
318 
319   g_return_if_fail (IDE_IS_EXTENSION_SET_ADAPTER (set));
320   g_return_if_fail (plugin_info != NULL);
321   g_return_if_fail (IDE_IS_BUFFER_ADDIN (exten));
322   g_return_if_fail (IDE_IS_BUFFER (user_data));
323 
324   ide_buffer_addin_load (IDE_BUFFER_ADDIN (exten), buffer);
325 
326   if (ide_buffer_get_state (buffer) == IDE_BUFFER_STATE_READY &&
327       !ide_buffer_get_is_temporary (buffer))
328     {
329       IdeBufferFileLoad closure = {
330         .buffer = buffer,
331         .file = ide_buffer_get_file (buffer),
332       };
333 
334       _ide_buffer_addin_file_loaded_cb (set, plugin_info, exten, &closure);
335     }
336 
337 }
338 
339 void
_ide_buffer_addin_unload_cb(IdeExtensionSetAdapter * set,PeasPluginInfo * plugin_info,PeasExtension * exten,gpointer user_data)340 _ide_buffer_addin_unload_cb (IdeExtensionSetAdapter *set,
341                              PeasPluginInfo         *plugin_info,
342                              PeasExtension          *exten,
343                              gpointer                user_data)
344 {
345   g_return_if_fail (IDE_IS_EXTENSION_SET_ADAPTER (set));
346   g_return_if_fail (plugin_info != NULL);
347   g_return_if_fail (IDE_IS_BUFFER_ADDIN (exten));
348   g_return_if_fail (IDE_IS_BUFFER (user_data));
349 
350   ide_buffer_addin_unload (IDE_BUFFER_ADDIN (exten), IDE_BUFFER (user_data));
351 }
352 
353 void
_ide_buffer_addin_file_loaded_cb(IdeExtensionSetAdapter * set,PeasPluginInfo * plugin_info,PeasExtension * exten,gpointer user_data)354 _ide_buffer_addin_file_loaded_cb (IdeExtensionSetAdapter *set,
355                                   PeasPluginInfo         *plugin_info,
356                                   PeasExtension          *exten,
357                                   gpointer                user_data)
358 {
359   IdeBufferFileLoad *load = user_data;
360 
361   g_return_if_fail (IDE_IS_EXTENSION_SET_ADAPTER (set));
362   g_return_if_fail (plugin_info != NULL);
363   g_return_if_fail (IDE_IS_BUFFER_ADDIN (exten));
364   g_return_if_fail (load != NULL);
365   g_return_if_fail (IDE_IS_BUFFER (load->buffer));
366   g_return_if_fail (G_IS_FILE (load->file));
367 
368   ide_buffer_addin_file_loaded (IDE_BUFFER_ADDIN (exten), load->buffer, load->file);
369 }
370 
371 void
_ide_buffer_addin_save_file_cb(IdeExtensionSetAdapter * set,PeasPluginInfo * plugin_info,PeasExtension * exten,gpointer user_data)372 _ide_buffer_addin_save_file_cb (IdeExtensionSetAdapter *set,
373                                 PeasPluginInfo         *plugin_info,
374                                 PeasExtension          *exten,
375                                 gpointer                user_data)
376 {
377   IdeBufferFileSave *save = user_data;
378 
379   g_return_if_fail (IDE_IS_EXTENSION_SET_ADAPTER (set));
380   g_return_if_fail (plugin_info != NULL);
381   g_return_if_fail (IDE_IS_BUFFER_ADDIN (exten));
382   g_return_if_fail (save != NULL);
383   g_return_if_fail (IDE_IS_BUFFER (save->buffer));
384   g_return_if_fail (G_IS_FILE (save->file));
385 
386   ide_buffer_addin_save_file (IDE_BUFFER_ADDIN (exten), save->buffer, save->file);
387 }
388 
389 void
_ide_buffer_addin_file_saved_cb(IdeExtensionSetAdapter * set,PeasPluginInfo * plugin_info,PeasExtension * exten,gpointer user_data)390 _ide_buffer_addin_file_saved_cb (IdeExtensionSetAdapter *set,
391                                  PeasPluginInfo         *plugin_info,
392                                  PeasExtension          *exten,
393                                  gpointer                user_data)
394 {
395   IdeBufferFileSave *save = user_data;
396 
397   g_return_if_fail (IDE_IS_EXTENSION_SET_ADAPTER (set));
398   g_return_if_fail (plugin_info != NULL);
399   g_return_if_fail (IDE_IS_BUFFER_ADDIN (exten));
400   g_return_if_fail (save != NULL);
401   g_return_if_fail (IDE_IS_BUFFER (save->buffer));
402   g_return_if_fail (G_IS_FILE (save->file));
403 
404   ide_buffer_addin_file_saved (IDE_BUFFER_ADDIN (exten), save->buffer, save->file);
405 }
406 
407 void
_ide_buffer_addin_language_set_cb(IdeExtensionSetAdapter * set,PeasPluginInfo * plugin_info,PeasExtension * exten,gpointer user_data)408 _ide_buffer_addin_language_set_cb (IdeExtensionSetAdapter *set,
409                                    PeasPluginInfo         *plugin_info,
410                                    PeasExtension          *exten,
411                                    gpointer                user_data)
412 {
413   IdeBufferLanguageSet *lang = user_data;
414 
415   g_return_if_fail (IDE_IS_EXTENSION_SET_ADAPTER (set));
416   g_return_if_fail (plugin_info != NULL);
417   g_return_if_fail (IDE_IS_BUFFER_ADDIN (exten));
418   g_return_if_fail (lang != NULL);
419   g_return_if_fail (IDE_IS_BUFFER (lang->buffer));
420 
421   ide_buffer_addin_language_set (IDE_BUFFER_ADDIN (exten), lang->buffer, lang->language_id);
422 }
423 
424 void
_ide_buffer_addin_change_settled_cb(IdeExtensionSetAdapter * set,PeasPluginInfo * plugin_info,PeasExtension * exten,gpointer user_data)425 _ide_buffer_addin_change_settled_cb (IdeExtensionSetAdapter *set,
426                                      PeasPluginInfo         *plugin_info,
427                                      PeasExtension          *exten,
428                                      gpointer                user_data)
429 {
430   g_return_if_fail (IDE_IS_EXTENSION_SET_ADAPTER (set));
431   g_return_if_fail (plugin_info != NULL);
432   g_return_if_fail (IDE_IS_BUFFER_ADDIN (exten));
433   g_return_if_fail (IDE_IS_BUFFER (user_data));
434 
435   ide_buffer_addin_change_settled (IDE_BUFFER_ADDIN (exten), IDE_BUFFER (user_data));
436 }
437 
438 void
_ide_buffer_addin_style_scheme_changed_cb(IdeExtensionSetAdapter * set,PeasPluginInfo * plugin_info,PeasExtension * exten,gpointer user_data)439 _ide_buffer_addin_style_scheme_changed_cb (IdeExtensionSetAdapter *set,
440                                            PeasPluginInfo         *plugin_info,
441                                            PeasExtension          *exten,
442                                            gpointer                user_data)
443 {
444   g_return_if_fail (IDE_IS_EXTENSION_SET_ADAPTER (set));
445   g_return_if_fail (plugin_info != NULL);
446   g_return_if_fail (IDE_IS_BUFFER_ADDIN (exten));
447   g_return_if_fail (IDE_IS_BUFFER (user_data));
448 
449   ide_buffer_addin_style_scheme_changed (IDE_BUFFER_ADDIN (exten), IDE_BUFFER (user_data));
450 }
451 
452 void
ide_buffer_addin_settle_async(IdeBufferAddin * self,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)453 ide_buffer_addin_settle_async (IdeBufferAddin      *self,
454                                GCancellable        *cancellable,
455                                GAsyncReadyCallback  callback,
456                                gpointer             user_data)
457 {
458   g_return_if_fail (IDE_IS_MAIN_THREAD ());
459   g_return_if_fail (IDE_IS_BUFFER_ADDIN (self));
460   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
461 
462   IDE_BUFFER_ADDIN_GET_IFACE (self)->settle_async (self, cancellable, callback, user_data);
463 }
464 
465 gboolean
ide_buffer_addin_settle_finish(IdeBufferAddin * self,GAsyncResult * result,GError ** error)466 ide_buffer_addin_settle_finish (IdeBufferAddin  *self,
467                                 GAsyncResult    *result,
468                                 GError         **error)
469 {
470   g_return_val_if_fail (IDE_IS_MAIN_THREAD (), FALSE);
471   g_return_val_if_fail (IDE_IS_BUFFER_ADDIN (self), FALSE);
472   g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
473 
474   return IDE_BUFFER_ADDIN_GET_IFACE (self)->settle_finish (self, result, error);
475 }
476