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