1 /* ide-buffer.c
2 *
3 * Copyright 2018-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"
22
23 #include "config.h"
24
25 #include <dazzle.h>
26 #include <glib/gi18n.h>
27 #include <libide-io.h>
28 #include <libide-plugins.h>
29 #include <libide-threading.h>
30
31 #include "ide-buffer.h"
32 #include "ide-buffer-addin.h"
33 #include "ide-buffer-addin-private.h"
34 #include "ide-buffer-manager.h"
35 #include "ide-buffer-private.h"
36 #include "ide-code-enums.h"
37 #include "ide-diagnostic.h"
38 #include "ide-diagnostics.h"
39 #include "ide-file-settings.h"
40 #include "ide-formatter.h"
41 #include "ide-formatter-options.h"
42 #include "ide-gfile-private.h"
43 #include "ide-highlight-engine.h"
44 #include "ide-location.h"
45 #include "ide-range.h"
46 #include "ide-source-iter.h"
47 #include "ide-source-style-scheme.h"
48 #include "ide-symbol-resolver.h"
49 #include "ide-unsaved-files.h"
50
51 #define SETTLING_DELAY_MSEC 333
52
53 #define TAG_ERROR "diagnostician::error"
54 #define TAG_WARNING "diagnostician::warning"
55 #define TAG_DEPRECATED "diagnostician::deprecated"
56 #define TAG_UNUSED "diagnostician::unused"
57 #define TAG_NOTE "diagnostician::note"
58 #define TAG_SNIPPET_TAB_STOP "snippet::tab-stop"
59 #define TAG_DEFINITION "action::hover-definition"
60 #define TAG_CURRENT_BKPT "debugger::current-breakpoint"
61
62 #define DEPRECATED_COLOR "#babdb6"
63 #define UNUSED_COLOR "#c17d11"
64 #define ERROR_COLOR "#ff0000"
65 #define NOTE_COLOR "#708090"
66 #define WARNING_COLOR "#fcaf3e"
67 #define CURRENT_BKPT_FG "#fffffe"
68 #define CURRENT_BKPT_BG "#fcaf3e"
69
70 struct _IdeBuffer
71 {
72 GtkSourceBuffer parent_instance;
73
74 /* Owned references */
75 IdeExtensionSetAdapter *addins;
76 IdeExtensionSetAdapter *symbol_resolvers;
77 IdeExtensionAdapter *rename_provider;
78 IdeExtensionAdapter *formatter;
79 IdeBufferManager *buffer_manager;
80 IdeBufferChangeMonitor *change_monitor;
81 GBytes *content;
82 IdeDiagnostics *diagnostics;
83 GError *failure;
84 IdeFileSettings *file_settings;
85 IdeHighlightEngine *highlight_engine;
86 GtkSourceFile *source_file;
87 GFile *readlink_file;
88
89 IdeTask *in_flight_symbol_at_location;
90 gint in_flight_symbol_at_location_pos;
91
92 /* Scalars */
93 guint change_count;
94 guint settling_source;
95 gint hold;
96
97 /* Bit-fields */
98 IdeBufferState state : 3;
99 guint can_restore_cursor : 1;
100 guint is_temporary : 1;
101 guint enable_addins : 1;
102 guint changed_on_volume : 1;
103 guint read_only : 1;
104 guint highlight_diagnostics : 1;
105 };
106
107 typedef struct
108 {
109 IdeNotification *notif;
110 GFile *file;
111 guint highlight_syntax : 1;
112 } LoadState;
113
114 typedef struct
115 {
116 GFile *file;
117 IdeNotification *notif;
118 GtkSourceFile *source_file;
119 } SaveState;
120
121 typedef struct
122 {
123 GPtrArray *resolvers;
124 IdeLocation *location;
125 IdeSymbol *symbol;
126 } LookUpSymbolData;
127
128 G_DEFINE_FINAL_TYPE (IdeBuffer, ide_buffer, GTK_SOURCE_TYPE_BUFFER)
129
130 enum {
131 PROP_0,
132 PROP_BUFFER_MANAGER,
133 PROP_CHANGE_MONITOR,
134 PROP_CHANGED_ON_VOLUME,
135 PROP_ENABLE_ADDINS,
136 PROP_DIAGNOSTICS,
137 PROP_FAILED,
138 PROP_FILE,
139 PROP_FILE_SETTINGS,
140 PROP_HAS_DIAGNOSTICS,
141 PROP_HAS_SYMBOL_RESOLVERS,
142 PROP_HIGHLIGHT_DIAGNOSTICS,
143 PROP_IS_TEMPORARY,
144 PROP_LANGUAGE_ID,
145 PROP_READ_ONLY,
146 PROP_STATE,
147 PROP_STYLE_SCHEME_NAME,
148 PROP_TITLE,
149 N_PROPS
150 };
151
152 enum {
153 CHANGE_SETTLED,
154 CURSOR_MOVED,
155 LINE_FLAGS_CHANGED,
156 LOADED,
157 REQUEST_SCROLL_TO_INSERT,
158 N_SIGNALS
159 };
160
161 static GParamSpec *properties [N_PROPS];
162 static guint signals [N_SIGNALS];
163
164 static void lookup_symbol_data_free (LookUpSymbolData *data);
165 static void apply_style (GtkTextTag *tag,
166 const gchar *first_property,
167 ...);
168 static void load_state_free (LoadState *state);
169 static void save_state_free (SaveState *state);
170 static void ide_buffer_save_file_cb (GObject *object,
171 GAsyncResult *result,
172 gpointer user_data);
173 static void ide_buffer_load_file_cb (GObject *object,
174 GAsyncResult *result,
175 gpointer user_data);
176 static void ide_buffer_progress_cb (goffset current_num_bytes,
177 goffset total_num_bytes,
178 gpointer user_data);
179 static void ide_buffer_get_property (GObject *object,
180 guint prop_id,
181 GValue *value,
182 GParamSpec *pspec);
183 static void ide_buffer_set_property (GObject *object,
184 guint prop_id,
185 const GValue *value,
186 GParamSpec *pspec);
187 static void ide_buffer_constructed (GObject *object);
188 static void ide_buffer_dispose (GObject *object);
189 static void ide_buffer_notify_language (IdeBuffer *self,
190 GParamSpec *pspec,
191 gpointer user_data);
192 static void ide_buffer_notify_style_scheme (IdeBuffer *self,
193 GParamSpec *pspec,
194 gpointer unused);
195 static void ide_buffer_reload_file_settings (IdeBuffer *self);
196 static void ide_buffer_set_file_settings (IdeBuffer *self,
197 IdeFileSettings *file_settings);
198 static void ide_buffer_emit_cursor_moved (IdeBuffer *self);
199 static void ide_buffer_changed (GtkTextBuffer *buffer);
200 static void ide_buffer_delete_range (GtkTextBuffer *buffer,
201 GtkTextIter *start,
202 GtkTextIter *end);
203 static void ide_buffer_insert_text (GtkTextBuffer *buffer,
204 GtkTextIter *location,
205 const gchar *text,
206 gint len);
207 static void ide_buffer_mark_set (GtkTextBuffer *buffer,
208 const GtkTextIter *iter,
209 GtkTextMark *mark);
210 static void ide_buffer_delay_settling (IdeBuffer *self);
211 static gboolean ide_buffer_settled_cb (gpointer user_data);
212 static void ide_buffer_apply_diagnostics (IdeBuffer *self);
213 static void ide_buffer_clear_diagnostics (IdeBuffer *self);
214 static void ide_buffer_apply_diagnostic (IdeBuffer *self,
215 IdeDiagnostic *diagnostics);
216 static void ide_buffer_init_tags (IdeBuffer *self);
217 static void ide_buffer_on_tag_added (IdeBuffer *self,
218 GtkTextTag *tag,
219 GtkTextTagTable *table);
220 static void ide_buffer_get_symbol_resolvers_cb (IdeExtensionSetAdapter *set,
221 PeasPluginInfo *plugin_info,
222 PeasExtension *exten,
223 gpointer user_data);
224 static void ide_buffer_symbol_resolver_removed (IdeExtensionSetAdapter *adapter,
225 PeasPluginInfo *plugin_info,
226 PeasExtension *extension,
227 gpointer user_data);
228 static void ide_buffer_symbol_resolver_added (IdeExtensionSetAdapter *adapter,
229 PeasPluginInfo *plugin_info,
230 PeasExtension *extension,
231 gpointer user_data);
232 static gboolean ide_buffer_can_do_newline_hack (IdeBuffer *self,
233 guint len);
234 static void ide_buffer_guess_language (IdeBuffer *self);
235 static void ide_buffer_real_loaded (IdeBuffer *self);
236 static void settle_async (IdeBuffer *self,
237 GCancellable *cancellable,
238 GAsyncReadyCallback callback,
239 gpointer user_data);
240 static gboolean settle_finish (IdeBuffer *self,
241 GAsyncResult *result,
242 GError **error);
243
244 static void
load_state_free(LoadState * state)245 load_state_free (LoadState *state)
246 {
247 g_assert (IDE_IS_MAIN_THREAD ());
248 g_assert (state != NULL);
249
250 g_clear_object (&state->notif);
251 g_clear_object (&state->file);
252 g_slice_free (LoadState, state);
253 }
254
255 static void
save_state_free(SaveState * state)256 save_state_free (SaveState *state)
257 {
258 g_assert (IDE_IS_MAIN_THREAD ());
259 g_assert (state != NULL);
260
261 g_clear_object (&state->notif);
262 g_clear_object (&state->file);
263 g_clear_object (&state->source_file);
264 g_slice_free (SaveState, state);
265 }
266
267 static void
lookup_symbol_data_free(LookUpSymbolData * data)268 lookup_symbol_data_free (LookUpSymbolData *data)
269 {
270 g_assert (IDE_IS_MAIN_THREAD ());
271
272 g_clear_pointer (&data->resolvers, g_ptr_array_unref);
273 g_clear_object (&data->location);
274 g_clear_object (&data->symbol);
275 g_slice_free (LookUpSymbolData, data);
276 }
277
278 IdeBuffer *
_ide_buffer_new(IdeBufferManager * buffer_manager,GFile * file,gboolean enable_addins,gboolean is_temporary)279 _ide_buffer_new (IdeBufferManager *buffer_manager,
280 GFile *file,
281 gboolean enable_addins,
282 gboolean is_temporary)
283 {
284 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
285 g_return_val_if_fail (IDE_IS_BUFFER_MANAGER (buffer_manager), NULL);
286 g_return_val_if_fail (G_IS_FILE (file), NULL);
287
288 return g_object_new (IDE_TYPE_BUFFER,
289 "buffer-manager", buffer_manager,
290 "file", file,
291 "enable-addins", enable_addins,
292 "is-temporary", is_temporary,
293 NULL);
294 }
295
296 void
_ide_buffer_set_file(IdeBuffer * self,GFile * file)297 _ide_buffer_set_file (IdeBuffer *self,
298 GFile *file)
299 {
300 GFile *location;
301
302 g_return_if_fail (IDE_IS_MAIN_THREAD ());
303 g_return_if_fail (IDE_IS_BUFFER (self));
304 g_return_if_fail (G_IS_FILE (file));
305
306 location = gtk_source_file_get_location (self->source_file);
307
308 if (location == NULL || !g_file_equal (file, location))
309 {
310 gtk_source_file_set_location (self->source_file, file);
311 g_clear_object (&self->readlink_file);
312 self->readlink_file = _ide_g_file_readlink (file);
313 ide_buffer_reload_file_settings (self);
314
315 if (self->addins != NULL && self->enable_addins)
316 {
317 IdeBufferFileLoad closure = { self, file };
318 ide_extension_set_adapter_foreach (self->addins,
319 _ide_buffer_addin_file_loaded_cb,
320 &closure);
321 }
322
323 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_FILE]);
324 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TITLE]);
325 }
326 }
327
328 static void
ide_buffer_set_state(IdeBuffer * self,IdeBufferState state)329 ide_buffer_set_state (IdeBuffer *self,
330 IdeBufferState state)
331 {
332 g_return_if_fail (IDE_IS_MAIN_THREAD ());
333 g_return_if_fail (IDE_IS_BUFFER (self));
334 g_return_if_fail (state == IDE_BUFFER_STATE_READY ||
335 state == IDE_BUFFER_STATE_LOADING ||
336 state == IDE_BUFFER_STATE_SAVING ||
337 state == IDE_BUFFER_STATE_FAILED);
338
339 if (self->state != state)
340 {
341 self->state = state;
342 if (self->state != IDE_BUFFER_STATE_FAILED)
343 g_clear_pointer (&self->failure, g_error_free);
344 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_STATE]);
345 }
346 }
347
348 static void
ide_buffer_real_loaded(IdeBuffer * self)349 ide_buffer_real_loaded (IdeBuffer *self)
350 {
351 g_assert (IDE_IS_BUFFER (self));
352
353 if (self->buffer_manager != NULL)
354 _ide_buffer_manager_buffer_loaded (self->buffer_manager, self);
355 }
356
357 static void
ide_buffer_notify_language(IdeBuffer * self,GParamSpec * pspec,gpointer user_data)358 ide_buffer_notify_language (IdeBuffer *self,
359 GParamSpec *pspec,
360 gpointer user_data)
361 {
362 const gchar *lang_id;
363
364 g_assert (IDE_IS_MAIN_THREAD ());
365 g_assert (IDE_IS_BUFFER (self));
366
367 ide_buffer_reload_file_settings (self);
368
369 lang_id = ide_buffer_get_language_id (self);
370
371 if (self->addins != NULL && self->enable_addins)
372 {
373 IdeBufferLanguageSet state = { self, lang_id };
374
375 ide_extension_set_adapter_set_value (self->addins, state.language_id);
376 ide_extension_set_adapter_foreach (self->addins,
377 _ide_buffer_addin_language_set_cb,
378 &state);
379 }
380
381 if (self->symbol_resolvers)
382 ide_extension_set_adapter_set_value (self->symbol_resolvers, lang_id);
383
384 if (self->rename_provider)
385 ide_extension_adapter_set_value (self->rename_provider, lang_id);
386
387 if (self->formatter)
388 ide_extension_adapter_set_value (self->formatter, lang_id);
389 }
390
391 static void
ide_buffer_constructed(GObject * object)392 ide_buffer_constructed (GObject *object)
393 {
394 IdeBuffer *self = (IdeBuffer *)object;
395
396 g_assert (IDE_IS_MAIN_THREAD ());
397 g_assert (IDE_IS_BUFFER (self));
398
399 G_OBJECT_CLASS (ide_buffer_parent_class)->constructed (object);
400
401 ide_buffer_init_tags (self);
402 }
403
404 static void
ide_buffer_dispose(GObject * object)405 ide_buffer_dispose (GObject *object)
406 {
407 IdeBuffer *self = (IdeBuffer *)object;
408 IdeObjectBox *box;
409
410 g_assert (IDE_IS_MAIN_THREAD ());
411
412 g_clear_handle_id (&self->settling_source, g_source_remove);
413
414 /* Remove ourselves from the object-tree if necessary */
415 if ((box = ide_object_box_from_object (object)) &&
416 !ide_object_in_destruction (IDE_OBJECT (box)))
417 ide_object_destroy (IDE_OBJECT (box));
418
419 ide_clear_and_destroy_object (&self->addins);
420 ide_clear_and_destroy_object (&self->rename_provider);
421 ide_clear_and_destroy_object (&self->symbol_resolvers);
422 ide_clear_and_destroy_object (&self->formatter);
423 ide_clear_and_destroy_object (&self->highlight_engine);
424 g_clear_object (&self->buffer_manager);
425 ide_clear_and_destroy_object (&self->change_monitor);
426 g_clear_pointer (&self->content, g_bytes_unref);
427 g_clear_object (&self->diagnostics);
428 ide_clear_and_destroy_object (&self->file_settings);
429
430 G_OBJECT_CLASS (ide_buffer_parent_class)->dispose (object);
431 }
432
433 static void
ide_buffer_finalize(GObject * object)434 ide_buffer_finalize (GObject *object)
435 {
436 IdeBuffer *self = (IdeBuffer *)object;
437
438 g_clear_object (&self->source_file);
439 g_clear_object (&self->readlink_file);
440 g_clear_pointer (&self->failure, g_error_free);
441
442 G_OBJECT_CLASS (ide_buffer_parent_class)->finalize (object);
443 }
444
445 static void
ide_buffer_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)446 ide_buffer_get_property (GObject *object,
447 guint prop_id,
448 GValue *value,
449 GParamSpec *pspec)
450 {
451 IdeBuffer *self = IDE_BUFFER (object);
452
453 switch (prop_id)
454 {
455 case PROP_CHANGE_MONITOR:
456 g_value_set_object (value, ide_buffer_get_change_monitor (self));
457 break;
458
459 case PROP_CHANGED_ON_VOLUME:
460 g_value_set_boolean (value, ide_buffer_get_changed_on_volume (self));
461 break;
462
463 case PROP_ENABLE_ADDINS:
464 g_value_set_boolean (value, self->enable_addins);
465 break;
466
467 case PROP_DIAGNOSTICS:
468 g_value_set_object (value, ide_buffer_get_diagnostics (self));
469 break;
470
471 case PROP_FAILED:
472 g_value_set_boolean (value, ide_buffer_get_failed (self));
473 break;
474
475 case PROP_FILE:
476 g_value_set_object (value, ide_buffer_get_file (self));
477 break;
478
479 case PROP_FILE_SETTINGS:
480 g_value_set_object (value, ide_buffer_get_file_settings (self));
481 break;
482
483 case PROP_HAS_DIAGNOSTICS:
484 g_value_set_boolean (value, ide_buffer_has_diagnostics (self));
485 break;
486
487 case PROP_HAS_SYMBOL_RESOLVERS:
488 g_value_set_boolean (value, ide_buffer_has_symbol_resolvers (self));
489 break;
490
491 case PROP_HIGHLIGHT_DIAGNOSTICS:
492 g_value_set_boolean (value, ide_buffer_get_highlight_diagnostics (self));
493 break;
494
495 case PROP_LANGUAGE_ID:
496 g_value_set_string (value, ide_buffer_get_language_id (self));
497 break;
498
499 case PROP_IS_TEMPORARY:
500 g_value_set_boolean (value, ide_buffer_get_is_temporary (self));
501 break;
502
503 case PROP_READ_ONLY:
504 g_value_set_boolean (value, ide_buffer_get_read_only (self));
505 break;
506
507 case PROP_STATE:
508 g_value_set_enum (value, ide_buffer_get_state (self));
509 break;
510
511 case PROP_STYLE_SCHEME_NAME:
512 g_value_set_string (value, ide_buffer_get_style_scheme_name (self));
513 break;
514
515 case PROP_TITLE:
516 g_value_take_string (value, ide_buffer_dup_title (self));
517 break;
518
519 default:
520 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
521 }
522 }
523
524 static void
ide_buffer_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)525 ide_buffer_set_property (GObject *object,
526 guint prop_id,
527 const GValue *value,
528 GParamSpec *pspec)
529 {
530 IdeBuffer *self = IDE_BUFFER (object);
531
532 switch (prop_id)
533 {
534 case PROP_BUFFER_MANAGER:
535 self->buffer_manager = g_value_dup_object (value);
536 break;
537
538 case PROP_CHANGE_MONITOR:
539 ide_buffer_set_change_monitor (self, g_value_get_object (value));
540 break;
541
542 case PROP_ENABLE_ADDINS:
543 self->enable_addins = g_value_get_boolean (value);
544 break;
545
546 case PROP_DIAGNOSTICS:
547 ide_buffer_set_diagnostics (self, g_value_get_object (value));
548 break;
549
550 case PROP_FILE:
551 _ide_buffer_set_file (self, g_value_get_object (value));
552 break;
553
554 case PROP_HIGHLIGHT_DIAGNOSTICS:
555 ide_buffer_set_highlight_diagnostics (self, g_value_get_boolean (value));
556 break;
557
558 case PROP_LANGUAGE_ID:
559 ide_buffer_set_language_id (self, g_value_get_string (value));
560 break;
561
562 case PROP_IS_TEMPORARY:
563 self->is_temporary = g_value_get_boolean (value);
564 break;
565
566 case PROP_STYLE_SCHEME_NAME:
567 ide_buffer_set_style_scheme_name (self, g_value_get_string (value));
568 break;
569
570 default:
571 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
572 }
573 }
574
575 static void
ide_buffer_class_init(IdeBufferClass * klass)576 ide_buffer_class_init (IdeBufferClass *klass)
577 {
578 GObjectClass *object_class = G_OBJECT_CLASS (klass);
579 GtkTextBufferClass *buffer_class = GTK_TEXT_BUFFER_CLASS (klass);
580
581 object_class->constructed = ide_buffer_constructed;
582 object_class->dispose = ide_buffer_dispose;
583 object_class->finalize = ide_buffer_finalize;
584 object_class->get_property = ide_buffer_get_property;
585 object_class->set_property = ide_buffer_set_property;
586
587 buffer_class->changed = ide_buffer_changed;
588 buffer_class->delete_range = ide_buffer_delete_range;
589 buffer_class->insert_text = ide_buffer_insert_text;
590 buffer_class->mark_set = ide_buffer_mark_set;
591
592 /**
593 * IdeBuffer:buffer-manager:
594 *
595 * Sets the "buffer-manager" property, which is used by the buffer to
596 * clean-up state when the buffer is no longer in use.
597 *
598 * Since: 3.32
599 */
600 properties [PROP_BUFFER_MANAGER] =
601 g_param_spec_object ("buffer-manager",
602 "Buffer Manager",
603 "The buffer manager for the context.",
604 IDE_TYPE_BUFFER_MANAGER,
605 (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
606
607 /**
608 * IdeBuffer:change-monitor:
609 *
610 * The "change-monitor" property is an #IdeBufferChangeMonitor that will be
611 * used to track changes in the #IdeBuffer. This can be used to show line
612 * changes in the editor gutter.
613 *
614 * Since: 3.32
615 */
616 properties [PROP_CHANGE_MONITOR] =
617 g_param_spec_object ("change-monitor",
618 "Change Monitor",
619 "Change Monitor",
620 IDE_TYPE_BUFFER_CHANGE_MONITOR,
621 (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
622
623 /**
624 * IdeBuffer:changed-on-volume:
625 *
626 * The "changed-on-volume" property is set to %TRUE when it has been
627 * discovered that the file represented by the #IdeBuffer has changed
628 * externally to Builder.
629 *
630 * Since: 3.32
631 */
632 properties [PROP_CHANGED_ON_VOLUME] =
633 g_param_spec_boolean ("changed-on-volume",
634 "Changed On Volume",
635 "If the buffer has been modified externally",
636 FALSE,
637 (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
638
639 /**
640 * IdeBuffer:enable-addins:
641 *
642 * The "enable-addins" property determines whether addins will be aware of
643 * this buffer. When set to %FALSE no ide_buffer_addin_*() functions will be
644 * called on this buffer.
645 *
646 * Since: 41.0
647 */
648 properties [PROP_ENABLE_ADDINS] =
649 g_param_spec_boolean ("enable-addins",
650 "Enable Addins",
651 "Whether to enable addins for this buffer",
652 TRUE,
653 (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY));
654
655 /**
656 * IdeBuffer:diagnostics:
657 *
658 * The "diagnostics" property contains an #IdeDiagnostics that represent
659 * the diagnostics found in the buffer.
660 *
661 * Since: 3.32
662 */
663 properties [PROP_DIAGNOSTICS] =
664 g_param_spec_object ("diagnostics",
665 "Diagnostics",
666 "The diagnostics for the buffer",
667 IDE_TYPE_DIAGNOSTICS,
668 (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
669
670 /**
671 * IdeBuffer:failed:
672 *
673 * The "failed" property is %TRUE when the buffer has entered a failed
674 * state such as when loading or saving the buffer to disk.
675 *
676 * Since: 3.32
677 */
678 properties [PROP_FAILED] =
679 g_param_spec_boolean ("failed",
680 "Failed",
681 "If the buffer has entered a failed state",
682 FALSE,
683 (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
684
685 /**
686 * IdeBuffer:file:
687 *
688 * The "file" property is the underlying file represented by the buffer.
689 *
690 * Since: 3.32
691 */
692 properties [PROP_FILE] =
693 g_param_spec_object ("file",
694 "File",
695 "The file the buffer represents",
696 G_TYPE_FILE,
697 (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
698
699 /**
700 * IdeBuffer:file-settings:
701 *
702 * The "file-settings" property are the settings to be used by the buffer
703 * and source-view for the underlying file.
704 *
705 * These are automatically discovered and kept up to date based on the
706 * #IdeFileSettings extension points.
707 *
708 * Since: 3.32
709 */
710 properties [PROP_FILE_SETTINGS] =
711 g_param_spec_object ("file-settings",
712 "File Settings",
713 "The file settings for the buffer",
714 IDE_TYPE_FILE_SETTINGS,
715 (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
716
717 /**
718 * IdeBuffer:has-diagnostics:
719 *
720 * The "has-diagnostics" property denotes that there are a non-zero number
721 * of diangostics registered for the buffer.
722 *
723 * Since: 3.32
724 */
725 properties [PROP_HAS_DIAGNOSTICS] =
726 g_param_spec_boolean ("has-diagnostics",
727 "Has Diagnostics",
728 "The diagnostics for the buffer",
729 FALSE,
730 (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
731
732 /**
733 * IdeBuffer:has-symbol-resolvers:
734 *
735 * The "has-symbol-resolvers" property is %TRUE if there are any symbol
736 * resolvers loaded.
737 *
738 * Since: 3.32
739 */
740 properties [PROP_HAS_SYMBOL_RESOLVERS] =
741 g_param_spec_boolean ("has-symbol-resolvers",
742 "Has symbol resolvers",
743 "If there is at least one symbol resolver available",
744 FALSE,
745 (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
746
747 /**
748 * IdeBuffer:highlight-diagnostics:
749 *
750 * The "highlight-diagnostics" property indicates that diagnostics which
751 * are discovered should be styled.
752 *
753 * Since: 3.32
754 */
755 properties [PROP_HIGHLIGHT_DIAGNOSTICS] =
756 g_param_spec_boolean ("highlight-diagnostics",
757 "Highlight Diagnostics",
758 "If diagnostics should be highlighted",
759 TRUE,
760 (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
761
762 /**
763 * IdeBuffer:is-temporary:
764 *
765 * The "is-temporary" property denotes the #IdeBuffer:file property points
766 * to a temporary file. When saving the the buffer, various UI components
767 * know to check this property and provide a file chooser to allow the user
768 * to select the destination file.
769 *
770 * Upon saving the file, the property will change to %FALSE.
771 *
772 * Since: 3.32
773 */
774 properties [PROP_IS_TEMPORARY] =
775 g_param_spec_boolean ("is-temporary",
776 "Is Temporary",
777 "If the file property is a temporary file",
778 FALSE,
779 (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
780
781 /**
782 * IdeBuffer:language-id:
783 *
784 * The "language-id" property is a convenience property to set the
785 * #GtkSourceBuffer:langauge property using a string name.
786 *
787 * Since: 3.32
788 */
789 properties [PROP_LANGUAGE_ID] =
790 g_param_spec_string ("language-id",
791 "Language Id",
792 "The language identifier as a string",
793 NULL,
794 (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
795
796 /**
797 * IdeBuffer:read-only:
798 *
799 * The "read-only" property is set to %TRUE when it has been
800 * discovered that the file represented by the #IdeBuffer is read-only
801 * on the underlying storage.
802 *
803 * Since: 3.32
804 */
805 properties [PROP_READ_ONLY] =
806 g_param_spec_boolean ("read-only",
807 "Read Only",
808 "If the buffer's file is read-only",
809 FALSE,
810 (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
811
812 /**
813 * IdeBuffer:state:
814 *
815 * The "state" property can be used to determine if the buffer is
816 * currently performing any specific background work, such as loading
817 * from or saving a buffer to storage.
818 *
819 * Since: 3.32
820 */
821 properties [PROP_STATE] =
822 g_param_spec_enum ("state",
823 "State",
824 "The state for the buffer",
825 IDE_TYPE_BUFFER_STATE,
826 IDE_BUFFER_STATE_READY,
827 (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
828
829 /**
830 * IdeBuffer:style-scheme-name:
831 *
832 * The "style-scheme-name" is the name of the style scheme that is used.
833 * It is a convenience property so that you do not need to use the
834 * #GtkSourceStyleSchemeManager to lookup style schemes.
835 *
836 * Since: 3.32
837 */
838 properties [PROP_STYLE_SCHEME_NAME] =
839 g_param_spec_string ("style-scheme-name",
840 "Style Scheme Name",
841 "The name of the GtkSourceStyleScheme to use",
842 NULL,
843 (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
844
845 /**
846 * IdeBuffer:title:
847 *
848 * The "title" for the buffer which includes some variant of the path
849 * to the underlying file.
850 *
851 * Since: 3.32
852 */
853 properties [PROP_TITLE] =
854 g_param_spec_string ("title",
855 "Title",
856 "The title for the buffer",
857 NULL,
858 (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
859
860 g_object_class_install_properties (object_class, N_PROPS, properties);
861
862 /**
863 * IdeBuffer::change-settled:
864 * @self: an #IdeBuffer
865 *
866 * The "change-settled" signal is emitted when the buffer has stopped
867 * being edited for a short period of time. This is useful to connect
868 * to when you want to perform work as the user is editing, but you
869 * don't want to get in the way of their editing.
870 *
871 * Since: 3.32
872 */
873 signals [CHANGE_SETTLED] =
874 g_signal_new ("change-settled",
875 G_TYPE_FROM_CLASS (klass),
876 G_SIGNAL_RUN_LAST,
877 0,
878 NULL, NULL,
879 g_cclosure_marshal_VOID__VOID,
880 G_TYPE_NONE, 0);
881 g_signal_set_va_marshaller (signals [CHANGE_SETTLED],
882 G_TYPE_FROM_CLASS (klass),
883 g_cclosure_marshal_VOID__VOIDv);
884
885 /**
886 * IdeBuffer::cursor-moved:
887 * @self: an #IdeBuffer
888 * @location: a #GtkTextIter
889 *
890 * This signal is emitted when the insertion location has moved. You might
891 * want to attach to this signal to update the location of the insert mark in
892 * the display.
893 *
894 * Since: 3.32
895 */
896 signals [CURSOR_MOVED] =
897 g_signal_new ("cursor-moved",
898 G_TYPE_FROM_CLASS (klass),
899 G_SIGNAL_RUN_LAST,
900 0,
901 NULL,
902 NULL,
903 g_cclosure_marshal_VOID__BOXED,
904 G_TYPE_NONE,
905 1,
906 GTK_TYPE_TEXT_ITER | G_SIGNAL_TYPE_STATIC_SCOPE);
907 g_signal_set_va_marshaller (signals [CURSOR_MOVED],
908 G_TYPE_FROM_CLASS (klass),
909 g_cclosure_marshal_VOID__BOXEDv);
910
911 /**
912 * IdeBuffer::line-flags-changed:
913 * @self: an #IdeBuffer
914 *
915 * The "line-flags-changed" signal is emitted when the buffer has detected
916 * ancillary information has changed for lines in the buffer. Such information
917 * might include diagnostics or version control information.
918 *
919 * Since: 3.32
920 */
921 signals [LINE_FLAGS_CHANGED] =
922 g_signal_new_class_handler ("line-flags-changed",
923 G_TYPE_FROM_CLASS (klass),
924 G_SIGNAL_RUN_LAST,
925 NULL,
926 NULL, NULL,
927 g_cclosure_marshal_VOID__VOID,
928 G_TYPE_NONE, 0);
929 g_signal_set_va_marshaller (signals [LINE_FLAGS_CHANGED],
930 G_TYPE_FROM_CLASS (klass),
931 g_cclosure_marshal_VOID__VOIDv);
932
933 /**
934 * IdeBuffer::loaded:
935 * @self: an #IdeBuffer
936 *
937 * The "loaded" signal is emitted after the buffer is loaded.
938 *
939 * This is useful to watch if you want to perform a given action but do
940 * not want to interfere with buffer loading.
941 *
942 * Since: 3.32
943 */
944 signals [LOADED] =
945 g_signal_new_class_handler ("loaded",
946 G_TYPE_FROM_CLASS (klass),
947 G_SIGNAL_RUN_LAST,
948 G_CALLBACK (ide_buffer_real_loaded),
949 NULL, NULL,
950 g_cclosure_marshal_VOID__VOID,
951 G_TYPE_NONE, 0);
952 g_signal_set_va_marshaller (signals [LOADED],
953 G_TYPE_FROM_CLASS (klass),
954 g_cclosure_marshal_VOID__VOIDv);
955
956 /**
957 * IdeBuffer::request-scroll-to-insert:
958 *
959 * Requests that attached views scroll to insert location.
960 *
961 * This is generally only used when loading a buffer.
962 *
963 * Since: 3.32
964 */
965 signals [REQUEST_SCROLL_TO_INSERT] =
966 g_signal_new_class_handler ("request-scroll-to-insert",
967 G_TYPE_FROM_CLASS (klass),
968 G_SIGNAL_RUN_LAST,
969 NULL,
970 NULL, NULL,
971 g_cclosure_marshal_VOID__VOID,
972 G_TYPE_NONE, 0);
973 g_signal_set_va_marshaller (signals [REQUEST_SCROLL_TO_INSERT],
974 G_TYPE_FROM_CLASS (klass),
975 g_cclosure_marshal_VOID__VOIDv);
976 }
977
978 static void
ide_buffer_init(IdeBuffer * self)979 ide_buffer_init (IdeBuffer *self)
980 {
981 self->in_flight_symbol_at_location_pos = -1;
982 self->source_file = gtk_source_file_new ();
983 self->can_restore_cursor = TRUE;
984 self->highlight_diagnostics = TRUE;
985 self->enable_addins = TRUE;
986
987 g_assert (IDE_IS_MAIN_THREAD ());
988
989 g_signal_connect (self,
990 "notify::language",
991 G_CALLBACK (ide_buffer_notify_language),
992 NULL);
993
994 g_signal_connect (self,
995 "notify::style-scheme",
996 G_CALLBACK (ide_buffer_notify_style_scheme),
997 NULL);
998 }
999
1000 static void
ide_buffer_rename_provider_notify_extension(IdeBuffer * self,GParamSpec * pspec,IdeExtensionAdapter * adapter)1001 ide_buffer_rename_provider_notify_extension (IdeBuffer *self,
1002 GParamSpec *pspec,
1003 IdeExtensionAdapter *adapter)
1004 {
1005 IdeRenameProvider *provider;
1006
1007 g_assert (IDE_IS_MAIN_THREAD ());
1008 g_assert (IDE_IS_BUFFER (self));
1009 g_assert (IDE_IS_EXTENSION_ADAPTER (adapter));
1010
1011 if ((provider = ide_extension_adapter_get_extension (adapter)))
1012 {
1013 g_object_set (provider, "buffer", self, NULL);
1014 ide_rename_provider_load (provider);
1015 }
1016 }
1017
1018 static void
ide_buffer_formatter_notify_extension(IdeBuffer * self,GParamSpec * pspec,IdeExtensionAdapter * adapter)1019 ide_buffer_formatter_notify_extension (IdeBuffer *self,
1020 GParamSpec *pspec,
1021 IdeExtensionAdapter *adapter)
1022 {
1023 IdeFormatter *formatter;
1024
1025 g_assert (IDE_IS_MAIN_THREAD ());
1026 g_assert (IDE_IS_BUFFER (self));
1027 g_assert (IDE_IS_EXTENSION_ADAPTER (adapter));
1028
1029 if ((formatter = ide_extension_adapter_get_extension (adapter)))
1030 ide_formatter_load (formatter);
1031 }
1032
1033 static void
ide_buffer_symbol_resolver_added(IdeExtensionSetAdapter * adapter,PeasPluginInfo * plugin_info,PeasExtension * extension,gpointer user_data)1034 ide_buffer_symbol_resolver_added (IdeExtensionSetAdapter *adapter,
1035 PeasPluginInfo *plugin_info,
1036 PeasExtension *extension,
1037 gpointer user_data)
1038 {
1039 IdeSymbolResolver *resolver = (IdeSymbolResolver *)extension;
1040 IdeBuffer *self = user_data;
1041
1042 IDE_ENTRY;
1043
1044 g_assert (IDE_IS_MAIN_THREAD ());
1045 g_assert (IDE_IS_EXTENSION_SET_ADAPTER (adapter));
1046 g_assert (plugin_info != NULL);
1047 g_assert (IDE_IS_SYMBOL_RESOLVER (resolver));
1048 g_assert (IDE_IS_BUFFER (self));
1049
1050 IDE_TRACE_MSG ("Loading symbol resolver %s", G_OBJECT_TYPE_NAME (resolver));
1051
1052 ide_symbol_resolver_load (resolver);
1053
1054 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_HAS_SYMBOL_RESOLVERS]);
1055
1056 IDE_EXIT;
1057 }
1058
1059 static void
ide_buffer_symbol_resolver_removed(IdeExtensionSetAdapter * adapter,PeasPluginInfo * plugin_info,PeasExtension * extension,gpointer user_data)1060 ide_buffer_symbol_resolver_removed (IdeExtensionSetAdapter *adapter,
1061 PeasPluginInfo *plugin_info,
1062 PeasExtension *extension,
1063 gpointer user_data)
1064 {
1065 IdeSymbolResolver *resolver = (IdeSymbolResolver *)extension;
1066 IdeBuffer *self = user_data;
1067
1068 IDE_ENTRY;
1069
1070 g_assert (IDE_IS_MAIN_THREAD ());
1071 g_assert (IDE_IS_EXTENSION_SET_ADAPTER (adapter));
1072 g_assert (plugin_info != NULL);
1073 g_assert (IDE_IS_SYMBOL_RESOLVER (resolver));
1074 g_assert (IDE_IS_BUFFER (self));
1075
1076 IDE_TRACE_MSG ("Unloading symbol resolver %s", G_OBJECT_TYPE_NAME (resolver));
1077
1078 ide_symbol_resolver_unload (resolver);
1079
1080 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_HAS_SYMBOL_RESOLVERS]);
1081
1082 IDE_EXIT;
1083 }
1084
1085 void
_ide_buffer_attach(IdeBuffer * self,IdeObject * parent)1086 _ide_buffer_attach (IdeBuffer *self,
1087 IdeObject *parent)
1088 {
1089 g_return_if_fail (IDE_IS_MAIN_THREAD ());
1090 g_return_if_fail (IDE_IS_OBJECT_BOX (parent));
1091 g_return_if_fail (ide_object_box_contains (IDE_OBJECT_BOX (parent), self));
1092 g_return_if_fail (IDE_IS_BUFFER (self));
1093 g_return_if_fail (self->addins == NULL);
1094 g_return_if_fail (self->highlight_engine == NULL);
1095 g_return_if_fail (self->formatter == NULL);
1096 g_return_if_fail (self->rename_provider == NULL);
1097
1098 /* Setup the semantic highlight engine */
1099 self->highlight_engine = ide_highlight_engine_new (self);
1100
1101 /* Load buffer addins */
1102 self->addins = ide_extension_set_adapter_new (parent,
1103 peas_engine_get_default (),
1104 IDE_TYPE_BUFFER_ADDIN,
1105 "Buffer-Addin-Languages",
1106 ide_buffer_get_language_id (self));
1107 g_signal_connect (self->addins,
1108 "extension-added",
1109 G_CALLBACK (_ide_buffer_addin_load_cb),
1110 self);
1111 g_signal_connect (self->addins,
1112 "extension-removed",
1113 G_CALLBACK (_ide_buffer_addin_unload_cb),
1114 self);
1115 ide_extension_set_adapter_foreach (self->addins,
1116 _ide_buffer_addin_load_cb,
1117 self);
1118
1119 /* Setup our rename provider, if any */
1120 self->rename_provider = ide_extension_adapter_new (parent,
1121 peas_engine_get_default (),
1122 IDE_TYPE_RENAME_PROVIDER,
1123 "Rename-Provider-Languages",
1124 ide_buffer_get_language_id (self));
1125 g_signal_connect_object (self->rename_provider,
1126 "notify::extension",
1127 G_CALLBACK (ide_buffer_rename_provider_notify_extension),
1128 self,
1129 G_CONNECT_SWAPPED);
1130 ide_buffer_rename_provider_notify_extension (self, NULL, self->rename_provider);
1131
1132 /* Setup our formatter, if any */
1133 self->formatter = ide_extension_adapter_new (parent,
1134 peas_engine_get_default (),
1135 IDE_TYPE_FORMATTER,
1136 "Formatter-Languages",
1137 ide_buffer_get_language_id (self));
1138 g_signal_connect_object (self->formatter,
1139 "notify::extension",
1140 G_CALLBACK (ide_buffer_formatter_notify_extension),
1141 self,
1142 G_CONNECT_SWAPPED);
1143 ide_buffer_formatter_notify_extension (self, NULL, self->formatter);
1144
1145 /* Setup symbol resolvers */
1146 self->symbol_resolvers = ide_extension_set_adapter_new (parent,
1147 peas_engine_get_default (),
1148 IDE_TYPE_SYMBOL_RESOLVER,
1149 "Symbol-Resolver-Languages",
1150 ide_buffer_get_language_id (self));
1151 g_signal_connect_object (self->symbol_resolvers,
1152 "extension-added",
1153 G_CALLBACK (ide_buffer_symbol_resolver_added),
1154 self,
1155 0);
1156 g_signal_connect_object (self->symbol_resolvers,
1157 "extension-removed",
1158 G_CALLBACK (ide_buffer_symbol_resolver_removed),
1159 self,
1160 0);
1161 ide_extension_set_adapter_foreach (self->symbol_resolvers,
1162 ide_buffer_symbol_resolver_added,
1163 self);
1164 }
1165
1166 /**
1167 * ide_buffer_get_file:
1168 * @self: an #IdeBuffer
1169 *
1170 * Gets the #IdeBuffer:file property.
1171 *
1172 * Returns: (transfer none): a #GFile
1173 *
1174 * Since: 3.32
1175 */
1176 GFile *
ide_buffer_get_file(IdeBuffer * self)1177 ide_buffer_get_file (IdeBuffer *self)
1178 {
1179 GFile *ret;
1180
1181 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
1182 g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
1183
1184 ret = gtk_source_file_get_location (self->source_file);
1185
1186 g_return_val_if_fail (G_IS_FILE (ret), NULL);
1187
1188 return ret;
1189 }
1190
1191 /**
1192 * ide_buffer_dup_uri:
1193 * @self: a #IdeBuffer
1194 *
1195 * Gets the URI for the underlying file and returns a copy of it.
1196 *
1197 * Returns: (transfer full): a new string
1198 *
1199 * Since: 3.32
1200 */
1201 gchar *
ide_buffer_dup_uri(IdeBuffer * self)1202 ide_buffer_dup_uri (IdeBuffer *self)
1203 {
1204 g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
1205
1206 return g_file_get_uri (ide_buffer_get_file (self));
1207 }
1208
1209 /**
1210 * ide_buffer_get_is_temporary:
1211 *
1212 * Checks if the buffer represents a temporary file.
1213 *
1214 * This is useful to check by views that want to provide a save-as dialog
1215 * when the user requests to save the buffer.
1216 *
1217 * Returns: %TRUE if the buffer is for a temporary file
1218 *
1219 * Since: 3.32
1220 */
1221 gboolean
ide_buffer_get_is_temporary(IdeBuffer * self)1222 ide_buffer_get_is_temporary (IdeBuffer *self)
1223 {
1224 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), FALSE);
1225 g_return_val_if_fail (IDE_IS_BUFFER (self), FALSE);
1226
1227 return self->is_temporary;
1228 }
1229
1230 /**
1231 * ide_buffer_get_state:
1232 * @self: an #IdeBuffer
1233 *
1234 * Gets the #IdeBuffer:state property.
1235 *
1236 * This will changed while files are loaded or saved to disk.
1237 *
1238 * Returns: an #IdeBufferState
1239 *
1240 * Since: 3.32
1241 */
1242 IdeBufferState
ide_buffer_get_state(IdeBuffer * self)1243 ide_buffer_get_state (IdeBuffer *self)
1244 {
1245 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), 0);
1246 g_return_val_if_fail (IDE_IS_BUFFER (self), 0);
1247
1248 return self->state;
1249 }
1250
1251 static void
ide_buffer_progress_cb(goffset current_num_bytes,goffset total_num_bytes,gpointer user_data)1252 ide_buffer_progress_cb (goffset current_num_bytes,
1253 goffset total_num_bytes,
1254 gpointer user_data)
1255 {
1256 IdeNotification *notif = user_data;
1257 gdouble progress = 0.0;
1258
1259 g_assert (IDE_IS_MAIN_THREAD ());
1260 g_assert (IDE_IS_NOTIFICATION (notif));
1261
1262 if (total_num_bytes)
1263 progress = (gdouble)current_num_bytes / (gdouble)total_num_bytes;
1264
1265 ide_notification_set_progress (notif, progress);
1266 }
1267
1268 static void
ide_buffer_load_file_cb(GObject * object,GAsyncResult * result,gpointer user_data)1269 ide_buffer_load_file_cb (GObject *object,
1270 GAsyncResult *result,
1271 gpointer user_data)
1272 {
1273 GtkSourceFileLoader *loader = (GtkSourceFileLoader *)object;
1274 g_autoptr(IdeTask) task = user_data;
1275 g_autoptr(GError) error = NULL;
1276 GtkTextIter iter;
1277 LoadState *state;
1278 IdeBuffer *self;
1279
1280 IDE_ENTRY;
1281
1282 g_assert (IDE_IS_MAIN_THREAD ());
1283 g_assert (GTK_SOURCE_IS_FILE_LOADER (loader));
1284 g_assert (G_IS_ASYNC_RESULT (result));
1285 g_assert (IDE_IS_TASK (task));
1286
1287 self = ide_task_get_source_object (task);
1288 state = ide_task_get_task_data (task);
1289
1290 g_assert (IDE_IS_BUFFER (self));
1291 g_assert (state != NULL);
1292 g_assert (G_IS_FILE (state->file));
1293 g_assert (IDE_IS_NOTIFICATION (state->notif));
1294
1295 if (!gtk_source_file_loader_load_finish (loader, result, &error))
1296 {
1297 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
1298 {
1299 ide_buffer_set_state (self, IDE_BUFFER_STATE_FAILED);
1300 ide_notification_set_progress (state->notif, 0.0);
1301 ide_task_return_error (task, g_steal_pointer (&error));
1302 IDE_EXIT;
1303 }
1304
1305 g_clear_error (&error);
1306 }
1307
1308 /* First move the insert cursor back to 0:0, plugins might move it
1309 * but we certainly don't want to leave it at the end.
1310 */
1311 gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (self), &iter);
1312 gtk_text_buffer_select_range (GTK_TEXT_BUFFER (self), &iter, &iter);
1313
1314 /* Assume we are at newest state at end of file-load operation */
1315 _ide_buffer_set_changed_on_volume (self, FALSE);
1316
1317 ide_highlight_engine_unpause (self->highlight_engine);
1318 ide_buffer_set_state (self, IDE_BUFFER_STATE_READY);
1319 ide_notification_set_progress (state->notif, 1.0);
1320 ide_task_return_boolean (task, TRUE);
1321
1322 IDE_EXIT;
1323 }
1324
1325 void
_ide_buffer_load_file_async(IdeBuffer * self,IdeNotification * notif,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)1326 _ide_buffer_load_file_async (IdeBuffer *self,
1327 IdeNotification *notif,
1328 GCancellable *cancellable,
1329 GAsyncReadyCallback callback,
1330 gpointer user_data)
1331 {
1332 g_autoptr(GtkSourceFileLoader) loader = NULL;
1333 g_autoptr(IdeTask) task = NULL;
1334 LoadState *state;
1335
1336 IDE_ENTRY;
1337
1338 g_return_if_fail (IDE_IS_MAIN_THREAD ());
1339 g_return_if_fail (IDE_IS_BUFFER (self));
1340 g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
1341 g_return_if_fail (ide_buffer_get_file (self) != NULL);
1342
1343 task = ide_task_new (self, cancellable, callback, user_data);
1344 ide_task_set_source_tag (task, _ide_buffer_load_file_async);
1345
1346 if (self->state != IDE_BUFFER_STATE_READY &&
1347 self->state != IDE_BUFFER_STATE_FAILED)
1348 {
1349 ide_task_return_new_error (task,
1350 G_IO_ERROR,
1351 G_IO_ERROR_BUSY,
1352 "Cannot load file while buffer is busy");
1353 IDE_EXIT;
1354 }
1355
1356 state = g_slice_new0 (LoadState);
1357 state->file = g_object_ref (ide_buffer_get_file (self));
1358 state->notif = notif ? g_object_ref (notif) : ide_notification_new ();
1359 state->highlight_syntax = gtk_source_buffer_get_highlight_syntax (GTK_SOURCE_BUFFER (self));
1360 ide_task_set_task_data (task, state, load_state_free);
1361
1362 ide_buffer_set_state (self, IDE_BUFFER_STATE_LOADING);
1363
1364 /* Disable some features while we reload */
1365 gtk_source_buffer_set_highlight_syntax (GTK_SOURCE_BUFFER (self), FALSE);
1366 ide_highlight_engine_pause (self->highlight_engine);
1367
1368 loader = gtk_source_file_loader_new (GTK_SOURCE_BUFFER (self), self->source_file);
1369 gtk_source_file_loader_load_async (loader,
1370 G_PRIORITY_DEFAULT,
1371 cancellable,
1372 ide_buffer_progress_cb,
1373 g_object_ref (state->notif),
1374 g_object_unref,
1375 ide_buffer_load_file_cb,
1376 g_steal_pointer (&task));
1377
1378 /* Load file settings immediately so that we can increase the chance
1379 * they are settled by the the load operation is finished. The modelines
1380 * file settings will auto-monitor for IdeBufferManager::buffer-loaded
1381 * and settle the file settings when we complete.
1382 */
1383 ide_buffer_reload_file_settings (self);
1384
1385 IDE_EXIT;
1386 }
1387
1388 /**
1389 * _ide_buffer_load_file_finish:
1390 * @self: an #IdeBuffer
1391 * @result: a #GAsyncResult
1392 * @error: a location for a #GError, or %NULL
1393 *
1394 * This should be called by the buffer manager to complete loading the initial
1395 * state of a buffer. It can also be used to reload a buffer after it was
1396 * modified on disk.
1397 *
1398 * You MUST call this function after using _ide_buffer_load_file_async() so
1399 * that the completion of signals and addins may be notified.
1400 *
1401 * Returns: %TRUE if the file was successfully loaded
1402 *
1403 * Since: 3.32
1404 */
1405 gboolean
_ide_buffer_load_file_finish(IdeBuffer * self,GAsyncResult * result,GError ** error)1406 _ide_buffer_load_file_finish (IdeBuffer *self,
1407 GAsyncResult *result,
1408 GError **error)
1409 {
1410 LoadState *state;
1411
1412 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), FALSE);
1413 g_return_val_if_fail (IDE_IS_BUFFER (self), FALSE);
1414 g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
1415
1416 if (!ide_task_propagate_boolean (IDE_TASK (result), error))
1417 return FALSE;
1418
1419 /* Restore various buffer features we disabled while loading */
1420 state = ide_task_get_task_data (IDE_TASK (result));
1421 if (state->highlight_syntax)
1422 gtk_source_buffer_set_highlight_syntax (GTK_SOURCE_BUFFER (self), TRUE);
1423
1424 /* Guess the syntax language now if necessary */
1425 if (!gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (self)))
1426 ide_buffer_guess_language (self);
1427
1428 /* Let consumers know they can access the buffer now */
1429 g_signal_emit (self, signals [LOADED], 0);
1430
1431 /* Notify buffer addins that a file has been loaded */
1432 if (self->addins != NULL && self->enable_addins)
1433 {
1434 IdeBufferFileLoad closure = { self, state->file };
1435 ide_extension_set_adapter_foreach (self->addins,
1436 _ide_buffer_addin_file_loaded_cb,
1437 &closure);
1438 }
1439
1440 return TRUE;
1441 }
1442
1443 static void
ide_buffer_save_file_cb(GObject * object,GAsyncResult * result,gpointer user_data)1444 ide_buffer_save_file_cb (GObject *object,
1445 GAsyncResult *result,
1446 gpointer user_data)
1447 {
1448 GtkSourceFileSaver *saver = (GtkSourceFileSaver *)object;
1449 g_autoptr(IdeTask) task = user_data;
1450 g_autoptr(GError) error = NULL;
1451 IdeBuffer *self;
1452 SaveState *state;
1453
1454 IDE_ENTRY;
1455
1456 g_assert (IDE_IS_MAIN_THREAD ());
1457 g_assert (GTK_SOURCE_IS_FILE_SAVER (saver));
1458 g_assert (G_IS_ASYNC_RESULT (result));
1459 g_assert (IDE_IS_TASK (task));
1460
1461 self = ide_task_get_source_object (task);
1462 state = ide_task_get_task_data (task);
1463
1464 g_assert (IDE_IS_BUFFER (self));
1465 g_assert (state != NULL);
1466 g_assert (G_IS_FILE (state->file));
1467 g_assert (IDE_IS_NOTIFICATION (state->notif));
1468
1469 if (!gtk_source_file_saver_save_finish (saver, result, &error))
1470 {
1471 ide_notification_set_progress (state->notif, 0.0);
1472 ide_buffer_set_state (self, IDE_BUFFER_STATE_FAILED);
1473 ide_task_return_error (task, g_steal_pointer (&error));
1474 IDE_EXIT;
1475 }
1476
1477 ide_notification_set_progress (state->notif, 1.0);
1478 ide_buffer_set_state (self, IDE_BUFFER_STATE_READY);
1479
1480 /* Treat our save as freshest. It's possible we race, as we'd need an etag to
1481 * detect that, probably fine in all but the most slowest of races.
1482 */
1483 _ide_buffer_set_changed_on_volume (self, FALSE);
1484
1485 /* Notify addins that a save has completed */
1486 if (self->addins != NULL && self->enable_addins)
1487 {
1488 IdeBufferFileSave closure = { self, state->file };
1489 ide_extension_set_adapter_foreach (self->addins,
1490 _ide_buffer_addin_file_saved_cb,
1491 &closure);
1492 }
1493
1494 if (self->buffer_manager != NULL)
1495 _ide_buffer_manager_buffer_saved (self->buffer_manager, self);
1496 else
1497 g_critical ("Attempt to save buffer without access to buffer-manager");
1498
1499 ide_task_return_boolean (task, TRUE);
1500
1501 IDE_EXIT;
1502 }
1503
1504 static void
ide_buffer_save_file_settle_cb(GObject * object,GAsyncResult * result,gpointer user_data)1505 ide_buffer_save_file_settle_cb (GObject *object,
1506 GAsyncResult *result,
1507 gpointer user_data)
1508 {
1509 IdeBuffer *self = (IdeBuffer *)object;
1510 g_autoptr(IdeTask) task = user_data;
1511 g_autoptr(GtkSourceFileSaver) saver = NULL;
1512 SaveState *state;
1513
1514 g_assert (IDE_IS_MAIN_THREAD ());
1515 g_assert (IDE_IS_BUFFER (self));
1516 g_assert (G_IS_ASYNC_RESULT (result));
1517 g_assert (IDE_IS_TASK (task));
1518
1519 settle_finish (self, result, NULL);
1520
1521 state = ide_task_get_task_data (task);
1522
1523 g_assert (state != NULL);
1524 g_assert (G_IS_FILE (state->file));
1525 g_assert (IDE_IS_NOTIFICATION (state->notif));
1526 g_assert (GTK_SOURCE_IS_FILE (state->source_file));
1527
1528 if (self->addins != NULL && self->enable_addins)
1529 {
1530 IdeBufferFileSave closure = { self, state->file };
1531 ide_extension_set_adapter_foreach (self->addins,
1532 _ide_buffer_addin_save_file_cb,
1533 &closure);
1534 }
1535
1536 saver = gtk_source_file_saver_new (GTK_SOURCE_BUFFER (self), state->source_file);
1537 /* At this point, we've notified the user of changes to the underlying file using
1538 * the infobar, so just save the file knowing that we are overwriting things.
1539 */
1540 gtk_source_file_saver_set_flags (saver,
1541 (GTK_SOURCE_FILE_SAVER_FLAGS_IGNORE_INVALID_CHARS |
1542 GTK_SOURCE_FILE_SAVER_FLAGS_IGNORE_MODIFICATION_TIME));
1543 gtk_source_file_saver_save_async (saver,
1544 G_PRIORITY_DEFAULT,
1545 ide_task_get_cancellable (task),
1546 ide_buffer_progress_cb,
1547 g_object_ref (state->notif),
1548 g_object_unref,
1549 ide_buffer_save_file_cb,
1550 g_object_ref (task));
1551
1552 }
1553
1554 /**
1555 * ide_buffer_save_file_async:
1556 * @self: an #IdeBuffer
1557 * @file: (nullable): a #GFile or %NULL
1558 * @cancellable: (nullable): a #GCancellable
1559 * @callback: a #GAsyncReadyCallback to execute upon completion
1560 * @user_data: closure data for @callback
1561 *
1562 * Asynchronously saves the buffer contents to @file.
1563 *
1564 * If @file is %NULL, then the #IdeBuffer:file property is used.
1565 *
1566 * The buffer is marked as busy during the operation, and must not have
1567 * further editing until the operation is complete.
1568 *
1569 * @callback is executed upon completion and should call
1570 * ide_buffer_save_file_finish() to get the result of the operation.
1571 *
1572 * Since: 3.32
1573 */
1574 void
ide_buffer_save_file_async(IdeBuffer * self,GFile * file,GCancellable * cancellable,IdeNotification ** notif,GAsyncReadyCallback callback,gpointer user_data)1575 ide_buffer_save_file_async (IdeBuffer *self,
1576 GFile *file,
1577 GCancellable *cancellable,
1578 IdeNotification **notif,
1579 GAsyncReadyCallback callback,
1580 gpointer user_data)
1581 {
1582 g_autoptr(IdeTask) task = NULL;
1583 g_autoptr(GtkSourceFile) alternate = NULL;
1584 g_autoptr(IdeNotification) local_notif = NULL;
1585 GtkSourceFile *source_file;
1586 SaveState *state;
1587
1588 IDE_ENTRY;
1589
1590 g_return_if_fail (IDE_IS_MAIN_THREAD ());
1591 g_return_if_fail (IDE_IS_BUFFER (self));
1592 g_return_if_fail (!file || G_IS_FILE (file));
1593 g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
1594 ide_clear_param (notif, NULL);
1595
1596 /* If the user is requesting to save a file and our current file
1597 * is a temporary file, then we want to transition to become that
1598 * file instead of our temporary one.
1599 */
1600 if (file != NULL && self->is_temporary)
1601 {
1602 _ide_buffer_set_file (self, file);
1603 self->is_temporary = FALSE;
1604
1605 /* The buffer might be empty, so mark it as modified so we really save */
1606 gtk_text_buffer_set_modified (GTK_TEXT_BUFFER (self), TRUE);
1607
1608 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_IS_TEMPORARY]);
1609 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TITLE]);
1610 }
1611
1612 if (file == NULL)
1613 file = ide_buffer_get_file (self);
1614
1615 local_notif = ide_notification_new ();
1616 ide_notification_set_has_progress (local_notif, TRUE);
1617
1618 state = g_slice_new0 (SaveState);
1619 state->file = g_object_ref (file);
1620 state->notif = g_object_ref (local_notif);
1621
1622 task = ide_task_new (self, cancellable, callback, user_data);
1623 ide_task_set_source_tag (task, ide_buffer_save_file_async);
1624 ide_task_set_task_data (task, state, save_state_free);
1625
1626 /* Keep buffer alive during save operation */
1627 ide_buffer_hold (self);
1628 g_signal_connect_object (task,
1629 "notify::completed",
1630 G_CALLBACK (ide_buffer_release),
1631 self,
1632 G_CONNECT_SWAPPED);
1633
1634 if (self->state == IDE_BUFFER_STATE_SAVING)
1635 {
1636 /* TODO: We could save in-flight tasks and chain to them */
1637 ide_task_return_boolean (task, TRUE);
1638 IDE_EXIT;
1639 }
1640
1641 if (self->state != IDE_BUFFER_STATE_READY)
1642 {
1643 ide_task_return_new_error (task,
1644 G_IO_ERROR,
1645 G_IO_ERROR_BUSY,
1646 "Failed to save buffer as it is busy");
1647 IDE_EXIT;
1648 }
1649
1650 source_file = self->source_file;
1651
1652 if (file && !g_file_equal (file, ide_buffer_get_file (self)))
1653 {
1654 alternate = gtk_source_file_new ();
1655 gtk_source_file_set_location (alternate, file);
1656 source_file = alternate;
1657 }
1658
1659 state->source_file = g_object_ref (source_file);
1660
1661 /* Possibly avoid any writing if we can detect a no-change state */
1662 if (file == NULL || g_file_equal (file, ide_buffer_get_file (self)))
1663 {
1664 if (!self->changed_on_volume &&
1665 !gtk_text_buffer_get_modified (GTK_TEXT_BUFFER (self)))
1666 {
1667 ide_notification_set_progress (local_notif, 1.0);
1668 ide_task_return_boolean (task, TRUE);
1669 IDE_GOTO (set_out_param);
1670 }
1671 }
1672
1673 ide_buffer_set_state (self, IDE_BUFFER_STATE_SAVING);
1674
1675 settle_async (self,
1676 cancellable,
1677 ide_buffer_save_file_settle_cb,
1678 g_steal_pointer (&task));
1679
1680 set_out_param:
1681 if (notif != NULL)
1682 *notif = g_steal_pointer (&local_notif);
1683
1684 IDE_EXIT;
1685 }
1686
1687 /**
1688 * ide_buffer_save_file_finish:
1689 * @self: an #IdeBuffer
1690 * @result: a #GAsyncResult provided to callback
1691 * @error: a location for a #GError, or %NULL
1692 *
1693 * Completes an asynchronous request to save the buffer via
1694 * ide_buffer_save_file_async().
1695 *
1696 * Returns: %TRUE if successful; otherwise %FALSE and @error is set.
1697 *
1698 * Since: 3.32
1699 */
1700 gboolean
ide_buffer_save_file_finish(IdeBuffer * self,GAsyncResult * result,GError ** error)1701 ide_buffer_save_file_finish (IdeBuffer *self,
1702 GAsyncResult *result,
1703 GError **error)
1704 {
1705 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), FALSE);
1706 g_return_val_if_fail (IDE_IS_BUFFER (self), FALSE);
1707 g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
1708
1709 return ide_task_propagate_boolean (IDE_TASK (result), error);
1710 }
1711
1712 /**
1713 * ide_buffer_get_language_id:
1714 * @self: an #IdeBuffer
1715 *
1716 * A helper to get the language identifier of the buffers current language.
1717 *
1718 * Returns: (nullable): a string containing the language id, or %NULL
1719 *
1720 * Since: 3.32
1721 */
1722 const gchar *
ide_buffer_get_language_id(IdeBuffer * self)1723 ide_buffer_get_language_id (IdeBuffer *self)
1724 {
1725 GtkSourceLanguage *lang;
1726
1727 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
1728 g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
1729
1730 if ((lang = gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (self))))
1731 return gtk_source_language_get_id (lang);
1732
1733 return NULL;
1734 }
1735
1736 void
ide_buffer_set_language_id(IdeBuffer * self,const gchar * language_id)1737 ide_buffer_set_language_id (IdeBuffer *self,
1738 const gchar *language_id)
1739 {
1740 GtkSourceLanguage *language = NULL;
1741
1742 g_return_if_fail (IDE_IS_BUFFER (self));
1743
1744 if (language_id != NULL)
1745 {
1746 GtkSourceLanguageManager *manager;
1747
1748 manager = gtk_source_language_manager_get_default ();
1749 language = gtk_source_language_manager_get_language (manager, language_id);
1750 }
1751
1752 gtk_source_buffer_set_language (GTK_SOURCE_BUFFER (self), language);
1753 }
1754
1755 IdeHighlightEngine *
_ide_buffer_get_highlight_engine(IdeBuffer * self)1756 _ide_buffer_get_highlight_engine (IdeBuffer *self)
1757 {
1758 g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
1759
1760 return self->highlight_engine;
1761 }
1762
1763 void
_ide_buffer_set_failure(IdeBuffer * self,const GError * error)1764 _ide_buffer_set_failure (IdeBuffer *self,
1765 const GError *error)
1766 {
1767 g_return_if_fail (IDE_IS_MAIN_THREAD ());
1768 g_return_if_fail (IDE_IS_BUFFER (self));
1769
1770 if (error == self->failure)
1771 return;
1772
1773 if (error != NULL)
1774 self->state = IDE_BUFFER_STATE_FAILED;
1775
1776 g_clear_pointer (&self->failure, g_error_free);
1777 self->failure = g_error_copy (error);
1778
1779 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_FAILED]);
1780 }
1781
1782 /**
1783 * ide_buffer_get_failure:
1784 *
1785 * Gets a #GError representing a failure that has occurred for the
1786 * buffer.
1787 *
1788 * Returns: (transfer none): a #GError, or %NULL
1789 *
1790 * Since: 3.32
1791 */
1792 const GError *
ide_buffer_get_failure(IdeBuffer * self)1793 ide_buffer_get_failure (IdeBuffer *self)
1794 {
1795 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
1796 g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
1797
1798 return self->failure;
1799 }
1800
1801 /**
1802 * ide_buffer_get_failed:
1803 * @self: an #IdeBuffer
1804 *
1805 * Gets the #IdeBuffer:failed property, denoting if the buffer has failed
1806 * in some aspect such as loading or saving.
1807 *
1808 * Returns: %TRUE if the buffer is in a failed state
1809 *
1810 * Since: 3.32
1811 */
1812 gboolean
ide_buffer_get_failed(IdeBuffer * self)1813 ide_buffer_get_failed (IdeBuffer *self)
1814 {
1815 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), FALSE);
1816 g_return_val_if_fail (IDE_IS_BUFFER (self), FALSE);
1817
1818 return self->state == IDE_BUFFER_STATE_FAILED;
1819 }
1820
1821 static void
ide_buffer_set_file_settings(IdeBuffer * self,IdeFileSettings * file_settings)1822 ide_buffer_set_file_settings (IdeBuffer *self,
1823 IdeFileSettings *file_settings)
1824 {
1825 g_assert (IDE_IS_MAIN_THREAD ());
1826 g_assert (IDE_IS_BUFFER (self));
1827
1828 if (self->file_settings == file_settings)
1829 return;
1830
1831 ide_clear_and_destroy_object (&self->file_settings);
1832 self->file_settings = g_object_ref (file_settings);
1833 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_FILE_SETTINGS]);
1834 }
1835
1836 static void
ide_buffer_reload_file_settings(IdeBuffer * self)1837 ide_buffer_reload_file_settings (IdeBuffer *self)
1838 {
1839 IdeObjectBox *box;
1840 const gchar *lang_id;
1841 GFile *file;
1842
1843 g_assert (IDE_IS_MAIN_THREAD ());
1844 g_assert (IDE_IS_BUFFER (self));
1845
1846 file = ide_buffer_get_file (self);
1847 lang_id = ide_buffer_get_language_id (self);
1848
1849 /* Bail if we'll just create the same settings as before */
1850 if (self->file_settings != NULL &&
1851 (g_file_equal (file, ide_file_settings_get_file (self->file_settings)) &&
1852 ide_str_equal0 (lang_id, ide_file_settings_get_language (self->file_settings))))
1853 return;
1854
1855 /* Now apply the settings (and they'll settle in the background) */
1856 if ((box = ide_object_box_from_object (G_OBJECT (self))))
1857 {
1858 g_autoptr(IdeFileSettings) file_settings = NULL;
1859
1860 file_settings = ide_file_settings_new (IDE_OBJECT (box), file, lang_id);
1861 ide_buffer_set_file_settings (self, file_settings);
1862 }
1863 }
1864
1865 static void
ide_buffer_emit_cursor_moved(IdeBuffer * self)1866 ide_buffer_emit_cursor_moved (IdeBuffer *self)
1867 {
1868 g_assert (IDE_IS_MAIN_THREAD ());
1869 g_assert (IDE_IS_BUFFER (self));
1870
1871 if (!ide_buffer_get_loading (self))
1872 {
1873 GtkTextMark *mark;
1874 GtkTextIter iter;
1875
1876 mark = gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (self));
1877 gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (self), &iter, mark);
1878 g_signal_emit (self, signals [CURSOR_MOVED], 0, &iter);
1879 }
1880 }
1881
1882 /**
1883 * ide_buffer_get_loading:
1884 * @self: an #IdeBuffer
1885 *
1886 * This checks to see if the buffer is currently loading. This is equivalent
1887 * to calling ide_buffer_get_state() and checking for %IDE_BUFFER_STATE_LOADING.
1888 *
1889 * Returns: %TRUE if the buffer is loading; otherwise %FALSE.
1890 *
1891 * Since: 3.32
1892 */
1893 gboolean
ide_buffer_get_loading(IdeBuffer * self)1894 ide_buffer_get_loading (IdeBuffer *self)
1895 {
1896 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), FALSE);
1897 g_return_val_if_fail (IDE_IS_BUFFER (self), FALSE);
1898
1899 return ide_buffer_get_state (self) == IDE_BUFFER_STATE_LOADING;
1900 }
1901
1902 static void
ide_buffer_changed(GtkTextBuffer * buffer)1903 ide_buffer_changed (GtkTextBuffer *buffer)
1904 {
1905 IdeBuffer *self = (IdeBuffer *)buffer;
1906
1907 g_assert (IDE_IS_BUFFER (self));
1908
1909 GTK_TEXT_BUFFER_CLASS (ide_buffer_parent_class)->changed (buffer);
1910
1911 g_clear_object (&self->in_flight_symbol_at_location);
1912 self->in_flight_symbol_at_location_pos = -1;
1913
1914 self->change_count++;
1915 g_clear_pointer (&self->content, g_bytes_unref);
1916 ide_buffer_delay_settling (self);
1917 }
1918
1919 static void
ide_buffer_delete_range(GtkTextBuffer * buffer,GtkTextIter * begin,GtkTextIter * end)1920 ide_buffer_delete_range (GtkTextBuffer *buffer,
1921 GtkTextIter *begin,
1922 GtkTextIter *end)
1923 {
1924 IDE_ENTRY;
1925
1926 g_assert (IDE_IS_MAIN_THREAD ());
1927 g_assert (IDE_IS_BUFFER (buffer));
1928 g_assert (begin != NULL);
1929 g_assert (end != NULL);
1930
1931 #ifdef IDE_ENABLE_TRACE
1932 {
1933 gint begin_line, begin_offset;
1934 gint end_line, end_offset;
1935
1936 begin_line = gtk_text_iter_get_line (begin);
1937 begin_offset = gtk_text_iter_get_line_offset (begin);
1938 end_line = gtk_text_iter_get_line (end);
1939 end_offset = gtk_text_iter_get_line_offset (end);
1940
1941 IDE_TRACE_MSG ("delete-range (%d:%d, %d:%d)",
1942 begin_line, begin_offset,
1943 end_line, end_offset);
1944 }
1945 #endif
1946
1947 GTK_TEXT_BUFFER_CLASS (ide_buffer_parent_class)->delete_range (buffer, begin, end);
1948
1949 ide_buffer_emit_cursor_moved (IDE_BUFFER (buffer));
1950
1951 IDE_EXIT;
1952 }
1953
1954 static void
ide_buffer_insert_text(GtkTextBuffer * buffer,GtkTextIter * location,const gchar * text,gint len)1955 ide_buffer_insert_text (GtkTextBuffer *buffer,
1956 GtkTextIter *location,
1957 const gchar *text,
1958 gint len)
1959 {
1960 gboolean recheck_language = FALSE;
1961
1962 IDE_ENTRY;
1963
1964 g_assert (IDE_IS_MAIN_THREAD ());
1965 g_assert (IDE_IS_BUFFER (buffer));
1966 g_assert (location != NULL);
1967 g_assert (text != NULL);
1968
1969 /*
1970 * If we are inserting a \n at the end of the first line, then we might want
1971 * to adjust the GtkSourceBuffer:language property to reflect the format.
1972 * This is similar to emacs "modelines", which is apparently a bit of an
1973 * overloaded term as is not to be confused with editor setting modelines.
1974 */
1975 if ((gtk_text_iter_get_line (location) == 0) && gtk_text_iter_ends_line (location) &&
1976 ((text [0] == '\n') || ((len > 1) && (strchr (text, '\n') != NULL))))
1977 recheck_language = TRUE;
1978
1979 GTK_TEXT_BUFFER_CLASS (ide_buffer_parent_class)->insert_text (buffer, location, text, len);
1980
1981 ide_buffer_emit_cursor_moved (IDE_BUFFER (buffer));
1982
1983 if G_UNLIKELY (recheck_language)
1984 ide_buffer_guess_language (IDE_BUFFER (buffer));
1985
1986 IDE_EXIT;
1987 }
1988
1989 static void
ide_buffer_mark_set(GtkTextBuffer * buffer,const GtkTextIter * iter,GtkTextMark * mark)1990 ide_buffer_mark_set (GtkTextBuffer *buffer,
1991 const GtkTextIter *iter,
1992 GtkTextMark *mark)
1993 {
1994 IdeBuffer *self = (IdeBuffer *)buffer;
1995
1996 g_assert (IDE_IS_MAIN_THREAD ());
1997 g_assert (IDE_IS_BUFFER (self));
1998
1999 GTK_TEXT_BUFFER_CLASS (ide_buffer_parent_class)->mark_set (buffer, iter, mark);
2000
2001 if (!ide_buffer_get_loading (self))
2002 {
2003 if (mark == gtk_text_buffer_get_insert (buffer))
2004 ide_buffer_emit_cursor_moved (IDE_BUFFER (buffer));
2005 }
2006 }
2007
2008 /**
2009 * ide_buffer_get_changed_on_volume:
2010 * @self: an #IdeBuffer
2011 *
2012 * Returns %TRUE if the #IdeBuffer is known to have been modified on storage
2013 * externally from this #IdeBuffer.
2014 *
2015 * Returns: %TRUE if @self is known to be modified on storage
2016 *
2017 * Since: 3.32
2018 */
2019 gboolean
ide_buffer_get_changed_on_volume(IdeBuffer * self)2020 ide_buffer_get_changed_on_volume (IdeBuffer *self)
2021 {
2022 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), FALSE);
2023 g_return_val_if_fail (IDE_IS_BUFFER (self), FALSE);
2024
2025 return self->changed_on_volume;
2026 }
2027
2028 /**
2029 * _ide_buffer_set_changed_on_volume:
2030 * @self: an #IdeBuffer
2031 * @changed_on_volume: if the buffer was changed externally
2032 *
2033 * Sets the #IdeBuffer:changed-on-volume property.
2034 *
2035 * Set this to %TRUE if the buffer has been discovered to have changed
2036 * outside of this buffer.
2037 *
2038 * Since: 3.32
2039 */
2040 void
_ide_buffer_set_changed_on_volume(IdeBuffer * self,gboolean changed_on_volume)2041 _ide_buffer_set_changed_on_volume (IdeBuffer *self,
2042 gboolean changed_on_volume)
2043 {
2044 g_return_if_fail (IDE_IS_MAIN_THREAD ());
2045 g_return_if_fail (IDE_IS_BUFFER (self));
2046
2047 changed_on_volume = !!changed_on_volume;
2048
2049 if (changed_on_volume != self->changed_on_volume)
2050 {
2051 self->changed_on_volume = changed_on_volume;
2052 if (changed_on_volume)
2053 gtk_text_buffer_set_modified (GTK_TEXT_BUFFER (self), TRUE);
2054 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CHANGED_ON_VOLUME]);
2055 }
2056 }
2057
2058 /**
2059 * ide_buffer_get_read_only:
2060 *
2061 * This function returns %TRUE if the underlying file has been discovered to
2062 * be read-only. This may be used by the interface to display information to
2063 * the user about saving the file.
2064 *
2065 * Returns: %TRUE if the underlying file is read-only
2066 *
2067 * Since: 3.32
2068 */
2069 gboolean
ide_buffer_get_read_only(IdeBuffer * self)2070 ide_buffer_get_read_only (IdeBuffer *self)
2071 {
2072 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), FALSE);
2073 g_return_val_if_fail (IDE_IS_BUFFER (self), FALSE);
2074
2075 return self->read_only;
2076 }
2077
2078 /**
2079 * _ide_buffer_set_read_only:
2080 * @self: an #IdeBuffer
2081 * @read_only: if the buffer is read-only
2082 *
2083 * Sets the #IdeBuffer:read-only property, which should be set when the buffer
2084 * has been discovered to be read-only on disk.
2085 *
2086 * Since: 3.32
2087 */
2088 void
_ide_buffer_set_read_only(IdeBuffer * self,gboolean read_only)2089 _ide_buffer_set_read_only (IdeBuffer *self,
2090 gboolean read_only)
2091 {
2092 g_return_if_fail (IDE_IS_MAIN_THREAD ());
2093 g_return_if_fail (IDE_IS_BUFFER (self));
2094
2095 read_only = !!read_only;
2096
2097 if (read_only != self->read_only)
2098 {
2099 self->read_only = read_only;
2100 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_READ_ONLY]);
2101 }
2102 }
2103
2104 /**
2105 * ide_buffer_get_style_scheme_name:
2106 * @self: an #IdeBuffer
2107 *
2108 * Gets the name of the #GtkSourceStyleScheme from the #IdeBuffer:style-scheme
2109 * property.
2110 *
2111 * Returns: (nullable): a string containing the style scheme or %NULL
2112 *
2113 * Since: 3.32
2114 */
2115 const gchar *
ide_buffer_get_style_scheme_name(IdeBuffer * self)2116 ide_buffer_get_style_scheme_name (IdeBuffer *self)
2117 {
2118 GtkSourceStyleScheme *scheme;
2119
2120 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
2121 g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
2122
2123 if ((scheme = gtk_source_buffer_get_style_scheme (GTK_SOURCE_BUFFER (self))))
2124 return gtk_source_style_scheme_get_id (scheme);
2125
2126 return NULL;
2127 }
2128
2129 /**
2130 * ide_buffer_set_style_scheme_name:
2131 * @self: an #IdeBuffer
2132 * @style_scheme_name: (nullable): string containing the style scheme's name
2133 *
2134 * Sets the #IdeBuffer:style-scheme property by locating the style scheme
2135 * matching @style_scheme_name.
2136 *
2137 * Since: 3.32
2138 */
2139 void
ide_buffer_set_style_scheme_name(IdeBuffer * self,const gchar * style_scheme_name)2140 ide_buffer_set_style_scheme_name (IdeBuffer *self,
2141 const gchar *style_scheme_name)
2142 {
2143 GtkSourceStyleSchemeManager *manager;
2144 GtkSourceStyleScheme *scheme;
2145
2146 g_return_if_fail (IDE_IS_MAIN_THREAD ());
2147 g_return_if_fail (IDE_IS_BUFFER (self));
2148
2149 if ((manager = gtk_source_style_scheme_manager_get_default ()) &&
2150 (scheme = gtk_source_style_scheme_manager_get_scheme (manager, style_scheme_name)))
2151 gtk_source_buffer_set_style_scheme (GTK_SOURCE_BUFFER (self), scheme);
2152 else
2153 gtk_source_buffer_set_style_scheme (GTK_SOURCE_BUFFER (self), NULL);
2154 }
2155
2156 /**
2157 * ide_buffer_dup_title:
2158 * @self: an #IdeBuffer
2159 *
2160 * Gets a string to represent the title of the buffer. An attempt is made to
2161 * make this relative to the project workdir if possible.
2162 *
2163 * Returns: (transfer full): a string containing a title
2164 *
2165 * Since: 3.32
2166 */
2167 gchar *
ide_buffer_dup_title(IdeBuffer * self)2168 ide_buffer_dup_title (IdeBuffer *self)
2169 {
2170 g_autoptr(IdeContext) context = NULL;
2171 g_autoptr(GFile) workdir = NULL;
2172 g_autoptr(GFile) home = NULL;
2173 GFile *file;
2174
2175 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
2176 g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
2177
2178 file = ide_buffer_get_file (self);
2179
2180 if (self->is_temporary)
2181 return g_file_get_basename (file);
2182
2183 /* Unlikely, but better to be safe */
2184 if (!(context = ide_buffer_ref_context (self)))
2185 return g_file_get_basename (file);
2186
2187 workdir = ide_context_ref_workdir (context);
2188
2189 if (g_file_has_prefix (file, workdir))
2190 return g_file_get_relative_path (workdir, file);
2191
2192 home = g_file_new_for_path (g_get_home_dir ());
2193
2194 if (g_file_has_prefix (file, home))
2195 {
2196 g_autofree gchar *relative = g_file_get_relative_path (home, file);
2197 return g_strdup_printf ("~/%s", relative);
2198 }
2199
2200 if (!g_file_is_native (file))
2201 return g_file_get_uri (file);
2202 else
2203 return g_file_get_path (file);
2204 }
2205
2206 /**
2207 * ide_buffer_get_highlight_diagnostics:
2208 * @self: an #IdeBuffer
2209 *
2210 * Checks if diagnostics should be highlighted.
2211 *
2212 * Returns: %TRUE if diagnostics should be highlighted
2213 *
2214 * Since: 3.32
2215 */
2216 gboolean
ide_buffer_get_highlight_diagnostics(IdeBuffer * self)2217 ide_buffer_get_highlight_diagnostics (IdeBuffer *self)
2218 {
2219 g_return_val_if_fail (IDE_IS_BUFFER (self), FALSE);
2220
2221 return self->highlight_diagnostics;
2222 }
2223
2224 /**
2225 * ide_buffer_set_highlight_diagnostics:
2226 * @self: an #IdeBuffer
2227 * @highlight_diagnostics: if diagnostics should be highlighted
2228 *
2229 * Sets the #IdeBuffer:highlight-diagnostics property.
2230 *
2231 * If set to %TRUE, diagnostics will be styled in the buffer.
2232 *
2233 * Since: 3.32
2234 */
2235 void
ide_buffer_set_highlight_diagnostics(IdeBuffer * self,gboolean highlight_diagnostics)2236 ide_buffer_set_highlight_diagnostics (IdeBuffer *self,
2237 gboolean highlight_diagnostics)
2238 {
2239 g_return_if_fail (IDE_IS_BUFFER (self));
2240
2241 highlight_diagnostics = !!highlight_diagnostics;
2242
2243 if (self->highlight_diagnostics != highlight_diagnostics)
2244 {
2245 ide_buffer_clear_diagnostics (self);
2246 self->highlight_diagnostics = highlight_diagnostics;
2247 ide_buffer_apply_diagnostics (self);
2248
2249 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_HIGHLIGHT_DIAGNOSTICS]);
2250 }
2251 }
2252
2253 /**
2254 * ide_buffer_get_iter_location:
2255 * @self: an #IdeBuffer
2256 * @iter: a #GtkTextIter
2257 *
2258 * Gets an #IdeLocation for the position represented by @iter.
2259 *
2260 * Returns: (transfer full): an #IdeLocation
2261 *
2262 * Since: 3.32
2263 */
2264 IdeLocation *
ide_buffer_get_iter_location(IdeBuffer * self,const GtkTextIter * iter)2265 ide_buffer_get_iter_location (IdeBuffer *self,
2266 const GtkTextIter *iter)
2267 {
2268 g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
2269 g_return_val_if_fail (iter != NULL, NULL);
2270
2271 return ide_location_new_with_offset (ide_buffer_get_file (self),
2272 gtk_text_iter_get_line (iter),
2273 gtk_text_iter_get_line_offset (iter),
2274 gtk_text_iter_get_offset (iter));
2275 }
2276
2277 /**
2278 * ide_buffer_get_selection_range:
2279 * @self: an #IdeBuffer
2280 *
2281 * Gets an #IdeRange to represent the current buffer selection.
2282 *
2283 * Returns: (transfer full): an #IdeRange
2284 *
2285 * Since: 3.32
2286 */
2287 IdeRange *
ide_buffer_get_selection_range(IdeBuffer * self)2288 ide_buffer_get_selection_range (IdeBuffer *self)
2289 {
2290 g_autoptr(IdeLocation) begin = NULL;
2291 g_autoptr(IdeLocation) end = NULL;
2292 GtkTextIter begin_iter;
2293 GtkTextIter end_iter;
2294
2295 g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
2296
2297 gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (self), &begin_iter, &end_iter);
2298 gtk_text_iter_order (&begin_iter, &end_iter);
2299
2300 begin = ide_buffer_get_iter_location (self, &begin_iter);
2301 end = ide_buffer_get_iter_location (self, &end_iter);
2302
2303 return ide_range_new (begin, end);
2304 }
2305
2306 /**
2307 * ide_buffer_get_change_count:
2308 * @self: an #IdeBuffer
2309 *
2310 * Gets the monotonic change count for the buffer.
2311 *
2312 * Returns: the change count for the buffer
2313 *
2314 * Since: 3.32
2315 */
2316 guint
ide_buffer_get_change_count(IdeBuffer * self)2317 ide_buffer_get_change_count (IdeBuffer *self)
2318 {
2319 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), 0);
2320 g_return_val_if_fail (IDE_IS_BUFFER (self), 0);
2321
2322 return self->change_count;
2323 }
2324
2325 static gboolean
ide_buffer_settled_cb(gpointer user_data)2326 ide_buffer_settled_cb (gpointer user_data)
2327 {
2328 IdeBuffer *self = user_data;
2329
2330 g_assert (IDE_IS_MAIN_THREAD ());
2331 g_assert (IDE_IS_BUFFER (self));
2332
2333 self->settling_source = 0;
2334 g_signal_emit (self, signals [CHANGE_SETTLED], 0);
2335
2336 if (self->addins != NULL && self->enable_addins)
2337 ide_extension_set_adapter_foreach (self->addins,
2338 _ide_buffer_addin_change_settled_cb,
2339 self);
2340
2341 return G_SOURCE_REMOVE;
2342 }
2343
2344 static void
ide_buffer_delay_settling(IdeBuffer * self)2345 ide_buffer_delay_settling (IdeBuffer *self)
2346 {
2347 g_assert (IDE_IS_MAIN_THREAD ());
2348 g_assert (IDE_IS_BUFFER (self));
2349
2350 g_clear_handle_id (&self->settling_source, g_source_remove);
2351 self->settling_source = gdk_threads_add_timeout (SETTLING_DELAY_MSEC,
2352 ide_buffer_settled_cb,
2353 self);
2354 }
2355
2356 /**
2357 * ide_buffer_set_diagnostics:
2358 * @self: an #IdeBuffer
2359 * @diagnostics: (nullable): an #IdeDiagnostics
2360 *
2361 * Sets the #IdeDiagnostics for the buffer. These will be used to highlight
2362 * the buffer for errors and warnings if #IdeBuffer:highlight-diagnostics
2363 * is %TRUE.
2364 *
2365 * Since: 3.32
2366 */
2367 void
ide_buffer_set_diagnostics(IdeBuffer * self,IdeDiagnostics * diagnostics)2368 ide_buffer_set_diagnostics (IdeBuffer *self,
2369 IdeDiagnostics *diagnostics)
2370 {
2371 g_return_if_fail (IDE_IS_MAIN_THREAD ());
2372 g_return_if_fail (IDE_IS_BUFFER (self));
2373 g_return_if_fail (!diagnostics || IDE_IS_DIAGNOSTICS (diagnostics));
2374
2375 if (diagnostics == self->diagnostics)
2376 return;
2377
2378 if (self->diagnostics)
2379 {
2380 ide_buffer_clear_diagnostics (self);
2381 g_clear_object (&self->diagnostics);
2382 }
2383
2384 if (diagnostics)
2385 {
2386 self->diagnostics = g_object_ref (diagnostics);
2387 ide_buffer_apply_diagnostics (self);
2388 }
2389
2390 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DIAGNOSTICS]);
2391 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_HAS_DIAGNOSTICS]);
2392
2393 _ide_buffer_line_flags_changed (self);
2394 }
2395
2396 /**
2397 * ide_buffer_get_diagnostics:
2398 * @self: an #IdeBuffer
2399 *
2400 * Gets the #IdeDiagnostics for the buffer if any have been registered.
2401 *
2402 * Returns: (transfer none) (nullable): an #IdeDiagnostics or %NULL
2403 *
2404 * Since: 3.32
2405 */
2406 IdeDiagnostics *
ide_buffer_get_diagnostics(IdeBuffer * self)2407 ide_buffer_get_diagnostics (IdeBuffer *self)
2408 {
2409 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
2410 g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
2411
2412 return self->diagnostics;
2413 }
2414
2415 /**
2416 * ide_buffer_has_diagnostics:
2417 * @self: a #IdeBuffer
2418 *
2419 * Returns %TRUE if any diagnostics have been registered for the buffer.
2420 *
2421 * Returns: %TRUE if there are a non-zero number of diagnostics.
2422 *
2423 * Since: 3.32
2424 */
2425 gboolean
ide_buffer_has_diagnostics(IdeBuffer * self)2426 ide_buffer_has_diagnostics (IdeBuffer *self)
2427 {
2428 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), FALSE);
2429 g_return_val_if_fail (IDE_IS_BUFFER (self), FALSE);
2430
2431 if (self->diagnostics)
2432 return g_list_model_get_n_items (G_LIST_MODEL (self->diagnostics)) > 0;
2433
2434 return FALSE;
2435 }
2436
2437 static void
ide_buffer_clear_diagnostics(IdeBuffer * self)2438 ide_buffer_clear_diagnostics (IdeBuffer *self)
2439 {
2440 GtkTextTagTable *table;
2441 GtkTextTag *tag;
2442 GtkTextIter begin;
2443 GtkTextIter end;
2444
2445 g_assert (IDE_IS_MAIN_THREAD ());
2446 g_assert (IDE_IS_BUFFER (self));
2447
2448 if (!self->highlight_diagnostics)
2449 return;
2450
2451 gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (self), &begin, &end);
2452
2453 table = gtk_text_buffer_get_tag_table (GTK_TEXT_BUFFER (self));
2454
2455 if (NULL != (tag = gtk_text_tag_table_lookup (table, TAG_NOTE)))
2456 dzl_gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (self), tag, &begin, &end, TRUE);
2457
2458 if (NULL != (tag = gtk_text_tag_table_lookup (table, TAG_WARNING)))
2459 dzl_gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (self), tag, &begin, &end, TRUE);
2460
2461 if (NULL != (tag = gtk_text_tag_table_lookup (table, TAG_DEPRECATED)))
2462 dzl_gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (self), tag, &begin, &end, TRUE);
2463
2464 if (NULL != (tag = gtk_text_tag_table_lookup (table, TAG_UNUSED)))
2465 dzl_gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (self), tag, &begin, &end, TRUE);
2466
2467 if (NULL != (tag = gtk_text_tag_table_lookup (table, TAG_ERROR)))
2468 dzl_gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (self), tag, &begin, &end, TRUE);
2469 }
2470
2471 static void
ide_buffer_apply_diagnostic(IdeBuffer * self,IdeDiagnostic * diagnostic)2472 ide_buffer_apply_diagnostic (IdeBuffer *self,
2473 IdeDiagnostic *diagnostic)
2474 {
2475 IdeDiagnosticSeverity severity;
2476 const gchar *tag_name = NULL;
2477 IdeLocation *location;
2478 guint n_ranges;
2479
2480 g_assert (IDE_IS_MAIN_THREAD ());
2481 g_assert (IDE_IS_BUFFER (self));
2482 g_assert (IDE_IS_DIAGNOSTIC (diagnostic));
2483
2484 severity = ide_diagnostic_get_severity (diagnostic);
2485
2486 switch (severity)
2487 {
2488 case IDE_DIAGNOSTIC_NOTE:
2489 tag_name = TAG_NOTE;
2490 break;
2491
2492 case IDE_DIAGNOSTIC_UNUSED:
2493 tag_name = TAG_UNUSED;
2494 break;
2495
2496 case IDE_DIAGNOSTIC_DEPRECATED:
2497 tag_name = TAG_DEPRECATED;
2498 break;
2499
2500 case IDE_DIAGNOSTIC_WARNING:
2501 tag_name = TAG_WARNING;
2502 break;
2503
2504 case IDE_DIAGNOSTIC_ERROR:
2505 case IDE_DIAGNOSTIC_FATAL:
2506 tag_name = TAG_ERROR;
2507 break;
2508
2509 case IDE_DIAGNOSTIC_IGNORED:
2510 default:
2511 return;
2512 }
2513
2514 n_ranges = ide_diagnostic_get_n_ranges (diagnostic);
2515 if (n_ranges == 0)
2516 {
2517 if ((location = ide_diagnostic_get_location (diagnostic)))
2518 {
2519 GtkTextIter begin_iter;
2520 GtkTextIter end_iter;
2521
2522 ide_buffer_get_iter_at_location (self, &begin_iter, location);
2523 end_iter = begin_iter;
2524
2525 if (gtk_text_iter_ends_line (&end_iter))
2526 {
2527 gtk_text_iter_backward_char (&begin_iter);
2528 }
2529 else
2530 {
2531 /* Only highlight to next word */
2532 if (_ide_source_iter_inside_word (&end_iter) ||
2533 _ide_source_iter_starts_word (&end_iter))
2534 _ide_source_iter_forward_visible_word_end (&end_iter);
2535 else
2536 gtk_text_iter_forward_to_line_end (&end_iter);
2537 }
2538
2539 gtk_text_buffer_apply_tag_by_name (GTK_TEXT_BUFFER (self), tag_name, &begin_iter, &end_iter);
2540 }
2541 }
2542
2543 for (guint i = 0; i < n_ranges; i++)
2544 {
2545 GtkTextIter begin_iter;
2546 GtkTextIter end_iter;
2547 IdeLocation *begin;
2548 IdeLocation *end;
2549 IdeRange *range;
2550 GFile *file;
2551
2552 range = ide_diagnostic_get_range (diagnostic, i);
2553 begin = ide_range_get_begin (range);
2554 end = ide_range_get_end (range);
2555 file = ide_location_get_file (begin);
2556
2557 if (file != NULL)
2558 {
2559 if (!g_file_equal (file, ide_buffer_get_file (self)))
2560 continue;
2561 }
2562
2563 ide_buffer_get_iter_at_location (self, &begin_iter, begin);
2564 ide_buffer_get_iter_at_location (self, &end_iter, end);
2565
2566 if (gtk_text_iter_equal (&begin_iter, &end_iter))
2567 {
2568 if (!gtk_text_iter_ends_line (&end_iter))
2569 gtk_text_iter_forward_char (&end_iter);
2570 else
2571 gtk_text_iter_backward_char (&begin_iter);
2572 }
2573
2574 gtk_text_buffer_apply_tag_by_name (GTK_TEXT_BUFFER (self), tag_name, &begin_iter, &end_iter);
2575 }
2576 }
2577
2578 static void
ide_buffer_apply_diagnostics(IdeBuffer * self)2579 ide_buffer_apply_diagnostics (IdeBuffer *self)
2580 {
2581 guint n_items;
2582
2583 g_assert (IDE_IS_MAIN_THREAD ());
2584 g_assert (IDE_IS_BUFFER (self));
2585
2586 if (!self->highlight_diagnostics)
2587 return;
2588
2589 if (self->diagnostics == NULL)
2590 return;
2591
2592 n_items = g_list_model_get_n_items (G_LIST_MODEL (self->diagnostics));
2593
2594 for (guint i = 0; i < n_items; i++)
2595 {
2596 g_autoptr(IdeDiagnostic) diagnostic = NULL;
2597
2598 diagnostic = g_list_model_get_item (G_LIST_MODEL (self->diagnostics), i);
2599 ide_buffer_apply_diagnostic (self, diagnostic);
2600 }
2601 }
2602
2603 /**
2604 * ide_buffer_get_iter_at_location:
2605 * @self: an #IdeBuffer
2606 * @iter: (out): a #GtkTextIter
2607 * @location: a #IdeLocation
2608 *
2609 * Set @iter to the position designated by @location.
2610 *
2611 * Since: 3.32
2612 */
2613 void
ide_buffer_get_iter_at_location(IdeBuffer * self,GtkTextIter * iter,IdeLocation * location)2614 ide_buffer_get_iter_at_location (IdeBuffer *self,
2615 GtkTextIter *iter,
2616 IdeLocation *location)
2617 {
2618 gint line;
2619 gint line_offset;
2620
2621 g_return_if_fail (IDE_IS_MAIN_THREAD ());
2622 g_return_if_fail (IDE_IS_BUFFER (self));
2623 g_return_if_fail (iter != NULL);
2624 g_return_if_fail (location != NULL);
2625
2626 line = ide_location_get_line (location);
2627 line_offset = ide_location_get_line_offset (location);
2628
2629 gtk_text_buffer_get_iter_at_line_offset (GTK_TEXT_BUFFER (self),
2630 iter,
2631 MAX (0, line),
2632 MAX (0, line_offset));
2633
2634 /* Advance to first non-space if offset < 0 */
2635 if (line_offset < 0)
2636 {
2637 while (!gtk_text_iter_ends_line (iter))
2638 {
2639 if (!g_unichar_isspace (gtk_text_iter_get_char (iter)))
2640 break;
2641 gtk_text_iter_forward_char (iter);
2642 }
2643 }
2644 }
2645
2646 /**
2647 * ide_buffer_get_change_monitor:
2648 * @self: an #IdeBuffer
2649 *
2650 * Gets the #IdeBuffer:change-monitor for the buffer.
2651 *
2652 * Returns: (transfer none) (nullable): an #IdeBufferChangeMonitor or %NULL
2653 *
2654 * Since: 3.32
2655 */
2656 IdeBufferChangeMonitor *
ide_buffer_get_change_monitor(IdeBuffer * self)2657 ide_buffer_get_change_monitor (IdeBuffer *self)
2658 {
2659 g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
2660
2661 return self->change_monitor;
2662 }
2663
2664 /**
2665 * ide_buffer_set_change_monitor:
2666 * @self: an #IdeBuffer
2667 * @change_monitor: (nullable): an #IdeBufferChangeMonitor or %NULL
2668 *
2669 * Sets an #IdeBufferChangeMonitor to use for the buffer.
2670 *
2671 * Since: 3.32
2672 */
2673 void
ide_buffer_set_change_monitor(IdeBuffer * self,IdeBufferChangeMonitor * change_monitor)2674 ide_buffer_set_change_monitor (IdeBuffer *self,
2675 IdeBufferChangeMonitor *change_monitor)
2676 {
2677 g_return_if_fail (IDE_IS_BUFFER (self));
2678 g_return_if_fail (!change_monitor || IDE_IS_BUFFER_CHANGE_MONITOR (change_monitor));
2679
2680 if (g_set_object (&self->change_monitor, change_monitor))
2681 {
2682 /* Destroy change monitor with us if we can */
2683 if (change_monitor && ide_object_is_root (IDE_OBJECT (change_monitor)))
2684 {
2685 IdeObjectBox *box = ide_object_box_from_object (G_OBJECT (self));
2686 ide_object_append (IDE_OBJECT (box), IDE_OBJECT (change_monitor));
2687 }
2688
2689 if (change_monitor != NULL)
2690 ide_buffer_change_monitor_reload (change_monitor);
2691
2692 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CHANGE_MONITOR]);
2693 }
2694 }
2695
2696 static gboolean
ide_buffer_can_do_newline_hack(IdeBuffer * self,guint len)2697 ide_buffer_can_do_newline_hack (IdeBuffer *self,
2698 guint len)
2699 {
2700 guint next_pow2;
2701
2702 g_assert (IDE_IS_MAIN_THREAD ());
2703 g_assert (IDE_IS_BUFFER (self));
2704
2705 /*
2706 * If adding two bytes to our length (one for \n and one for \0) is still
2707 * under the next power of two, then we can avoid making a copy of the buffer
2708 * when saving the buffer to our drafts.
2709 *
2710 * HACK: This relies on the fact that GtkTextBuffer returns a GString
2711 * allocated string which grows the string in powers of two.
2712 */
2713
2714 if ((len == 0) || (len & (len - 1)) == 0)
2715 return FALSE;
2716
2717 next_pow2 = len;
2718 next_pow2 |= next_pow2 >> 1;
2719 next_pow2 |= next_pow2 >> 2;
2720 next_pow2 |= next_pow2 >> 4;
2721 next_pow2 |= next_pow2 >> 8;
2722 next_pow2 |= next_pow2 >> 16;
2723 next_pow2++;
2724
2725 return ((len + 2) < next_pow2);
2726 }
2727
2728 /**
2729 * ide_buffer_dup_content:
2730 * @self: an #IdeBuffer.
2731 *
2732 * Gets the contents of the buffer as GBytes.
2733 *
2734 * By using this function to get the bytes, you allow #IdeBuffer to avoid
2735 * calculating the buffer text unnecessarily, potentially saving on
2736 * allocations.
2737 *
2738 * Additionally, this allows the buffer to update the state in #IdeUnsavedFiles
2739 * if the content is out of sync.
2740 *
2741 * Returns: (transfer full): a #GBytes containing the buffer content.
2742 *
2743 * Since: 3.32
2744 */
2745 GBytes *
ide_buffer_dup_content(IdeBuffer * self)2746 ide_buffer_dup_content (IdeBuffer *self)
2747 {
2748 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
2749 g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
2750
2751 if (self->content == NULL)
2752 {
2753 g_autoptr(IdeContext) context = NULL;
2754 IdeUnsavedFiles *unsaved_files;
2755 GtkTextIter begin;
2756 GtkTextIter end;
2757 GFile *file;
2758 gchar *text;
2759 gsize len;
2760
2761 gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (self), &begin, &end);
2762 text = gtk_text_buffer_get_text (GTK_TEXT_BUFFER (self), &begin, &end, TRUE);
2763
2764 /*
2765 * If implicit newline is set, add a \n in place of the \0 and avoid
2766 * duplicating the buffer. Make sure to track length beforehand, since we
2767 * would overwrite afterwards. Since conversion to \r\n is dealth with
2768 * during save operations, this should be fine for both. The unsaved
2769 * files will restore to a buffer, for which \n is acceptable.
2770 */
2771 len = strlen (text);
2772 if (gtk_source_buffer_get_implicit_trailing_newline (GTK_SOURCE_BUFFER (self)) &&
2773 (len == 0 || text[len - 1] != '\n'))
2774 {
2775 if (!ide_buffer_can_do_newline_hack (self, len))
2776 {
2777 gchar *copy;
2778
2779 copy = g_malloc (len + 2);
2780 memcpy (copy, text, len);
2781 g_free (text);
2782 text = copy;
2783 }
2784
2785 text [len] = '\n';
2786 text [++len] = '\0';
2787 }
2788
2789 /*
2790 * We pass a buffer that is longer than the length we tell GBytes about.
2791 * This way, compilers that don't want to see the trailing \0 can ignore
2792 * that data, but compilers that rely on valid C strings can also rely
2793 * on the buffer to be valid.
2794 */
2795 self->content = g_bytes_new_take (g_steal_pointer (&text), len);
2796
2797 /* Only persist if we have access to the object tree */
2798 if (self->buffer_manager != NULL &&
2799 !ide_object_in_destruction (IDE_OBJECT (self->buffer_manager)))
2800 {
2801 file = ide_buffer_get_file (self);
2802 context = ide_buffer_ref_context (IDE_BUFFER (self));
2803 unsaved_files = ide_unsaved_files_from_context (context);
2804 ide_unsaved_files_update (unsaved_files, file, self->content);
2805 }
2806 }
2807
2808 return g_bytes_ref (self->content);
2809 }
2810
2811 static void
ide_buffer_format_selection_cb(GObject * object,GAsyncResult * result,gpointer user_data)2812 ide_buffer_format_selection_cb (GObject *object,
2813 GAsyncResult *result,
2814 gpointer user_data)
2815 {
2816 IdeFormatter *formatter = (IdeFormatter *)object;
2817 g_autoptr(GError) error = NULL;
2818 g_autoptr(IdeTask) task = user_data;
2819
2820 g_assert (IDE_IS_FORMATTER (object));
2821 g_assert (G_IS_ASYNC_RESULT (result));
2822 g_assert (IDE_IS_TASK (task));
2823
2824 if (!ide_formatter_format_finish (formatter, result, &error))
2825 ide_task_return_error (task, g_steal_pointer (&error));
2826 else
2827 ide_task_return_boolean (task, TRUE);
2828 }
2829
2830 static void
ide_buffer_format_selection_range_cb(GObject * object,GAsyncResult * result,gpointer user_data)2831 ide_buffer_format_selection_range_cb (GObject *object,
2832 GAsyncResult *result,
2833 gpointer user_data)
2834 {
2835 IdeFormatter *formatter = (IdeFormatter *)object;
2836 g_autoptr(GError) error = NULL;
2837 g_autoptr(IdeTask) task = user_data;
2838
2839 g_assert (IDE_IS_FORMATTER (object));
2840 g_assert (G_IS_ASYNC_RESULT (result));
2841 g_assert (IDE_IS_TASK (task));
2842
2843 if (!ide_formatter_format_range_finish (formatter, result, &error))
2844 ide_task_return_error (task, g_steal_pointer (&error));
2845 else
2846 ide_task_return_boolean (task, TRUE);
2847 }
2848
2849 /**
2850 * ide_buffer_format_selection_async:
2851 * @self: an #IdeBuffer
2852 * @options: options for the formatting
2853 * @cancellable: (nullable): a #GCancellable, or %NULL
2854 * @callback: the callback upon completion
2855 * @user_data: user data for @callback
2856 *
2857 * Formats the selection using an available #IdeFormatter for the buffer.
2858 *
2859 * Since: 3.32
2860 */
2861 void
ide_buffer_format_selection_async(IdeBuffer * self,IdeFormatterOptions * options,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)2862 ide_buffer_format_selection_async (IdeBuffer *self,
2863 IdeFormatterOptions *options,
2864 GCancellable *cancellable,
2865 GAsyncReadyCallback callback,
2866 gpointer user_data)
2867 {
2868 g_autoptr(IdeTask) task = NULL;
2869 IdeFormatter *formatter;
2870 GtkTextIter begin;
2871 GtkTextIter end;
2872
2873 IDE_ENTRY;
2874
2875 g_return_if_fail (IDE_IS_MAIN_THREAD ());
2876 g_return_if_fail (IDE_IS_BUFFER (self));
2877 g_return_if_fail (IDE_IS_FORMATTER_OPTIONS (options));
2878 g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
2879
2880 task = ide_task_new (self, cancellable, callback, user_data);
2881 ide_task_set_source_tag (task, ide_buffer_format_selection_async);
2882
2883 if (!(formatter = ide_extension_adapter_get_extension (self->formatter)))
2884 {
2885 const gchar *language_id = ide_buffer_get_language_id (self);
2886
2887 if (language_id == NULL)
2888 language_id = "none";
2889
2890 ide_task_return_new_error (task,
2891 G_IO_ERROR,
2892 G_IO_ERROR_NOT_SUPPORTED,
2893 "No formatter registered for language %s",
2894 language_id);
2895
2896 IDE_EXIT;
2897 }
2898
2899 if (!gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (self), &begin, &end))
2900 {
2901 ide_formatter_format_async (formatter,
2902 self,
2903 options,
2904 cancellable,
2905 ide_buffer_format_selection_cb,
2906 g_steal_pointer (&task));
2907 IDE_EXIT;
2908 }
2909
2910 gtk_text_iter_order (&begin, &end);
2911
2912 ide_formatter_format_range_async (formatter,
2913 self,
2914 options,
2915 &begin,
2916 &end,
2917 cancellable,
2918 ide_buffer_format_selection_range_cb,
2919 g_steal_pointer (&task));
2920
2921 IDE_EXIT;
2922 }
2923
2924 /**
2925 * ide_buffer_format_selection_finish:
2926 * @self: an #IdeBuffer
2927 * @result: a #GAsyncResult
2928 * @error: a location for a #GError, or %NULL
2929 *
2930 * Completes an asynchronous request to ide_buffer_format_selection_async().
2931 *
2932 * Returns: %TRUE if successful; otherwise %FALSE and @error is set.
2933 *
2934 * Since: 3.32
2935 */
2936 gboolean
ide_buffer_format_selection_finish(IdeBuffer * self,GAsyncResult * result,GError ** error)2937 ide_buffer_format_selection_finish (IdeBuffer *self,
2938 GAsyncResult *result,
2939 GError **error)
2940 {
2941 gboolean ret;
2942
2943 IDE_ENTRY;
2944
2945 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), FALSE);
2946 g_return_val_if_fail (IDE_IS_BUFFER (self), FALSE);
2947 g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
2948
2949 ret = ide_task_propagate_boolean (IDE_TASK (result), error);
2950
2951 IDE_RETURN (ret);
2952 }
2953
2954 /**
2955 * ide_buffer_get_insert_location:
2956 *
2957 * Gets the location of the insert mark as an #IdeLocation.
2958 *
2959 * Returns: (transfer full): An #IdeLocation
2960 *
2961 * Since: 3.32
2962 */
2963 IdeLocation *
ide_buffer_get_insert_location(IdeBuffer * self)2964 ide_buffer_get_insert_location (IdeBuffer *self)
2965 {
2966 GtkTextMark *mark;
2967 GtkTextIter iter;
2968
2969 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
2970 g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
2971
2972 mark = gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (self));
2973 gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (self), &iter, mark);
2974
2975 return ide_buffer_get_iter_location (self, &iter);
2976 }
2977
2978 /**
2979 * ide_buffer_get_word_at_iter:
2980 * @self: an #IdeBuffer.
2981 * @iter: a #GtkTextIter.
2982 *
2983 * Gets the word found under the position denoted by @iter.
2984 *
2985 * Returns: (transfer full): A newly allocated string.
2986 *
2987 * Since: 3.32
2988 */
2989 gchar *
ide_buffer_get_word_at_iter(IdeBuffer * self,const GtkTextIter * iter)2990 ide_buffer_get_word_at_iter (IdeBuffer *self,
2991 const GtkTextIter *iter)
2992 {
2993 GtkTextIter begin;
2994 GtkTextIter end;
2995
2996 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
2997 g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
2998 g_return_val_if_fail (iter != NULL, NULL);
2999
3000 end = begin = *iter;
3001
3002 if (!_ide_source_iter_starts_word (&begin))
3003 _ide_source_iter_backward_extra_natural_word_start (&begin);
3004
3005 if (!_ide_source_iter_ends_word (&end))
3006 _ide_source_iter_forward_extra_natural_word_end (&end);
3007
3008 return gtk_text_iter_get_slice (&begin, &end);
3009 }
3010
3011 /**
3012 * ide_buffer_get_rename_provider:
3013 * @self: an #IdeBuffer
3014 *
3015 * Gets the #IdeRenameProvider for this buffer, or %NULL.
3016 *
3017 * Returns: (nullable) (transfer none): An #IdeRenameProvider or %NULL if
3018 * there is no #IdeRenameProvider that can statisfy the buffer.
3019 *
3020 * Since: 3.32
3021 */
3022 IdeRenameProvider *
ide_buffer_get_rename_provider(IdeBuffer * self)3023 ide_buffer_get_rename_provider (IdeBuffer *self)
3024 {
3025 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
3026 g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
3027
3028 if (self->rename_provider != NULL)
3029 return ide_extension_adapter_get_extension (self->rename_provider);
3030
3031 return NULL;
3032 }
3033
3034 /**
3035 * ide_buffer_get_file_settings:
3036 * @self: an #IdeBuffer
3037 *
3038 * Gets the #IdeBuffer:file-settings property.
3039 *
3040 * The #IdeFileSettings are updated when changes to the file or language
3041 * syntax are chnaged.
3042 *
3043 * Returns: (transfer none) (nullable): an #IdeFileSettings or %NULL
3044 *
3045 * Since: 3.32
3046 */
3047 IdeFileSettings *
ide_buffer_get_file_settings(IdeBuffer * self)3048 ide_buffer_get_file_settings (IdeBuffer *self)
3049 {
3050 g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
3051
3052 return self->file_settings;
3053 }
3054
3055 /**
3056 * ide_buffer_ref_context:
3057 * @self: an #IdeBuffer
3058 *
3059 * Locates the #IdeContext for the buffer and returns it.
3060 *
3061 * Returns: (transfer full): an #IdeContext
3062 *
3063 * Since: 3.32
3064 */
3065 IdeContext *
ide_buffer_ref_context(IdeBuffer * self)3066 ide_buffer_ref_context (IdeBuffer *self)
3067 {
3068 g_autoptr(IdeObject) root = NULL;
3069
3070 g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
3071
3072 if (self->buffer_manager != NULL)
3073 root = ide_object_ref_root (IDE_OBJECT (self->buffer_manager));
3074
3075 g_return_val_if_fail (root != NULL, NULL);
3076 g_return_val_if_fail (IDE_IS_CONTEXT (root), NULL);
3077
3078 return IDE_CONTEXT (g_steal_pointer (&root));
3079 }
3080
3081 static void
apply_style(GtkTextTag * tag,const gchar * first_property,...)3082 apply_style (GtkTextTag *tag,
3083 const gchar *first_property,
3084 ...)
3085 {
3086 va_list args;
3087
3088 g_assert (IDE_IS_MAIN_THREAD ());
3089 g_assert (!tag || GTK_IS_TEXT_TAG (tag));
3090 g_assert (first_property != NULL);
3091
3092 if (tag == NULL)
3093 return;
3094
3095 va_start (args, first_property);
3096 g_object_set_valist (G_OBJECT (tag), first_property, args);
3097 va_end (args);
3098 }
3099
3100 static void
ide_buffer_notify_style_scheme(IdeBuffer * self,GParamSpec * pspec,gpointer unused)3101 ide_buffer_notify_style_scheme (IdeBuffer *self,
3102 GParamSpec *pspec,
3103 gpointer unused)
3104 {
3105 GtkSourceStyleScheme *style_scheme;
3106 GtkTextTagTable *table;
3107 GdkRGBA deprecated_rgba;
3108 GdkRGBA unused_rgba;
3109 GdkRGBA error_rgba;
3110 GdkRGBA note_rgba;
3111 GdkRGBA warning_rgba;
3112
3113 g_assert (IDE_IS_MAIN_THREAD ());
3114 g_assert (IDE_IS_BUFFER (self));
3115 g_assert (pspec != NULL);
3116
3117 style_scheme = gtk_source_buffer_get_style_scheme (GTK_SOURCE_BUFFER (self));
3118 table = gtk_text_buffer_get_tag_table (GTK_TEXT_BUFFER (self));
3119
3120 #define GET_TAG(name) (gtk_text_tag_table_lookup(table, name))
3121
3122 if (style_scheme != NULL)
3123 {
3124 /* These are a fall-back if our style scheme isn't installed. */
3125 gdk_rgba_parse (&deprecated_rgba, DEPRECATED_COLOR);
3126 gdk_rgba_parse (&unused_rgba, UNUSED_COLOR);
3127 gdk_rgba_parse (&error_rgba, ERROR_COLOR);
3128 gdk_rgba_parse (¬e_rgba, NOTE_COLOR);
3129 gdk_rgba_parse (&warning_rgba, WARNING_COLOR);
3130
3131 if (!ide_source_style_scheme_apply_style (style_scheme,
3132 TAG_DEPRECATED,
3133 GET_TAG (TAG_DEPRECATED)))
3134 apply_style (GET_TAG (TAG_DEPRECATED),
3135 "underline", PANGO_UNDERLINE_ERROR,
3136 "underline-rgba", &deprecated_rgba,
3137 NULL);
3138
3139 if (!ide_source_style_scheme_apply_style (style_scheme,
3140 TAG_UNUSED,
3141 GET_TAG (TAG_UNUSED)))
3142 apply_style (GET_TAG (TAG_UNUSED),
3143 "underline", PANGO_UNDERLINE_ERROR,
3144 "underline-rgba", &unused_rgba,
3145 NULL);
3146
3147 if (!ide_source_style_scheme_apply_style (style_scheme,
3148 TAG_ERROR,
3149 GET_TAG (TAG_ERROR)))
3150 apply_style (GET_TAG (TAG_ERROR),
3151 "underline", PANGO_UNDERLINE_ERROR,
3152 "underline-rgba", &error_rgba,
3153 NULL);
3154
3155 if (!ide_source_style_scheme_apply_style (style_scheme,
3156 TAG_NOTE,
3157 GET_TAG (TAG_NOTE)))
3158 apply_style (GET_TAG (TAG_NOTE),
3159 "underline", PANGO_UNDERLINE_ERROR,
3160 "underline-rgba", ¬e_rgba,
3161 NULL);
3162
3163 if (!ide_source_style_scheme_apply_style (style_scheme,
3164 TAG_WARNING,
3165 GET_TAG (TAG_WARNING)))
3166 apply_style (GET_TAG (TAG_WARNING),
3167 "underline", PANGO_UNDERLINE_ERROR,
3168 "underline-rgba", &warning_rgba,
3169 NULL);
3170
3171 if (!ide_source_style_scheme_apply_style (style_scheme,
3172 TAG_SNIPPET_TAB_STOP,
3173 GET_TAG (TAG_SNIPPET_TAB_STOP)))
3174 apply_style (GET_TAG (TAG_SNIPPET_TAB_STOP),
3175 "underline", PANGO_UNDERLINE_SINGLE,
3176 NULL);
3177
3178 if (!ide_source_style_scheme_apply_style (style_scheme,
3179 TAG_DEFINITION,
3180 GET_TAG (TAG_DEFINITION)))
3181 apply_style (GET_TAG (TAG_DEFINITION),
3182 "underline", PANGO_UNDERLINE_SINGLE,
3183 NULL);
3184
3185 if (!ide_source_style_scheme_apply_style (style_scheme,
3186 TAG_CURRENT_BKPT,
3187 GET_TAG (TAG_CURRENT_BKPT)))
3188 apply_style (GET_TAG (TAG_CURRENT_BKPT),
3189 "paragraph-background", CURRENT_BKPT_BG,
3190 "foreground", CURRENT_BKPT_FG,
3191 NULL);
3192 }
3193
3194 #undef GET_TAG
3195
3196 if (self->addins != NULL && self->enable_addins)
3197 ide_extension_set_adapter_foreach (self->addins,
3198 _ide_buffer_addin_style_scheme_changed_cb,
3199 self);
3200
3201 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_STYLE_SCHEME_NAME]);
3202
3203 }
3204
3205 static void
ide_buffer_on_tag_added(IdeBuffer * self,GtkTextTag * tag,GtkTextTagTable * table)3206 ide_buffer_on_tag_added (IdeBuffer *self,
3207 GtkTextTag *tag,
3208 GtkTextTagTable *table)
3209 {
3210 GtkTextTag *chunk_tag;
3211
3212 g_assert (IDE_IS_MAIN_THREAD ());
3213 g_assert (IDE_IS_BUFFER (self));
3214 g_assert (GTK_IS_TEXT_TAG (tag));
3215 g_assert (GTK_IS_TEXT_TAG_TABLE (table));
3216
3217 /* Adjust priority of our tab-stop tag. */
3218 chunk_tag = gtk_text_tag_table_lookup (table, TAG_SNIPPET_TAB_STOP);
3219 if (chunk_tag != NULL)
3220 gtk_text_tag_set_priority (chunk_tag,
3221 gtk_text_tag_table_get_size (table) - 1);
3222 }
3223
3224 static void
ide_buffer_init_tags(IdeBuffer * self)3225 ide_buffer_init_tags (IdeBuffer *self)
3226 {
3227 GtkTextTagTable *tag_table;
3228 GtkSourceStyleScheme *style_scheme;
3229 g_autoptr(GtkTextTag) deprecated_tag = NULL;
3230 g_autoptr(GtkTextTag) unused_tag = NULL;
3231 g_autoptr(GtkTextTag) error_tag = NULL;
3232 g_autoptr(GtkTextTag) note_tag = NULL;
3233 g_autoptr(GtkTextTag) warning_tag = NULL;
3234 GdkRGBA deprecated_rgba;
3235 GdkRGBA unused_rgba;
3236 GdkRGBA error_rgba;
3237 GdkRGBA note_rgba;
3238 GdkRGBA warning_rgba;
3239
3240 g_assert (IDE_IS_MAIN_THREAD ());
3241 g_assert (IDE_IS_BUFFER (self));
3242
3243 tag_table = gtk_text_buffer_get_tag_table (GTK_TEXT_BUFFER (self));
3244 style_scheme = gtk_source_buffer_get_style_scheme (GTK_SOURCE_BUFFER (self));
3245
3246 /* These are fall-back if our style scheme isn't installed. */
3247 gdk_rgba_parse (&deprecated_rgba, DEPRECATED_COLOR);
3248 gdk_rgba_parse (&unused_rgba, UNUSED_COLOR);
3249 gdk_rgba_parse (&error_rgba, ERROR_COLOR);
3250 gdk_rgba_parse (¬e_rgba, NOTE_COLOR);
3251 gdk_rgba_parse (&warning_rgba, WARNING_COLOR);
3252
3253 /*
3254 * NOTE:
3255 *
3256 * The tag table assigns priority upon insert. Each successive insert
3257 * is higher priority than the last.
3258 */
3259
3260 deprecated_tag = gtk_text_tag_new (TAG_DEPRECATED);
3261 unused_tag = gtk_text_tag_new (TAG_UNUSED);
3262 error_tag = gtk_text_tag_new (TAG_ERROR);
3263 note_tag = gtk_text_tag_new (TAG_NOTE);
3264 warning_tag = gtk_text_tag_new (TAG_WARNING);
3265
3266 if (!ide_source_style_scheme_apply_style (style_scheme, TAG_DEPRECATED, deprecated_tag))
3267 apply_style (deprecated_tag,
3268 "underline", PANGO_UNDERLINE_ERROR,
3269 "underline-rgba", &deprecated_rgba,
3270 NULL);
3271
3272 if (!ide_source_style_scheme_apply_style (style_scheme, TAG_UNUSED, unused_tag))
3273 apply_style (unused_tag,
3274 "underline", PANGO_UNDERLINE_ERROR,
3275 "underline-rgba", &unused_rgba,
3276 NULL);
3277
3278 if (!ide_source_style_scheme_apply_style (style_scheme, TAG_ERROR, error_tag))
3279 apply_style (error_tag,
3280 "underline", PANGO_UNDERLINE_ERROR,
3281 "underline-rgba", &error_rgba,
3282 NULL);
3283
3284 if (!ide_source_style_scheme_apply_style (style_scheme, TAG_NOTE, note_tag))
3285 apply_style (note_tag,
3286 "underline", PANGO_UNDERLINE_ERROR,
3287 "underline-rgba", ¬e_rgba,
3288 NULL);
3289
3290 if (!ide_source_style_scheme_apply_style (style_scheme, TAG_NOTE, warning_tag))
3291 apply_style (warning_tag,
3292 "underline", PANGO_UNDERLINE_ERROR,
3293 "underline-rgba", &warning_rgba,
3294 NULL);
3295
3296 gtk_text_tag_table_add (tag_table, deprecated_tag);
3297 gtk_text_tag_table_add (tag_table, unused_tag);
3298 gtk_text_tag_table_add (tag_table, error_tag);
3299 gtk_text_tag_table_add (tag_table, note_tag);
3300 gtk_text_tag_table_add (tag_table, warning_tag);
3301
3302 gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (self), TAG_SNIPPET_TAB_STOP,
3303 NULL);
3304 gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (self), TAG_DEFINITION,
3305 "underline", PANGO_UNDERLINE_SINGLE,
3306 NULL);
3307 gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (self), TAG_CURRENT_BKPT,
3308 "paragraph-background", CURRENT_BKPT_BG,
3309 "foreground", CURRENT_BKPT_FG,
3310 NULL);
3311
3312 g_signal_connect_object (tag_table,
3313 "tag-added",
3314 G_CALLBACK (ide_buffer_on_tag_added),
3315 self,
3316 G_CONNECT_SWAPPED);
3317 }
3318
3319 /**
3320 * ide_buffer_get_formatter:
3321 * @self: an #IdeBuffer
3322 *
3323 * Gets an #IdeFormatter for the buffer, if any.
3324 *
3325 * Returns: (transfer none) (nullable): an #IdeFormatter or %NULL
3326 *
3327 * Since: 3.32
3328 */
3329 IdeFormatter *
ide_buffer_get_formatter(IdeBuffer * self)3330 ide_buffer_get_formatter (IdeBuffer *self)
3331 {
3332 g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
3333
3334 if (self->formatter == NULL)
3335 return NULL;
3336
3337 return ide_extension_adapter_get_extension (self->formatter);
3338 }
3339
3340 void
_ide_buffer_sync_to_unsaved_files(IdeBuffer * self)3341 _ide_buffer_sync_to_unsaved_files (IdeBuffer *self)
3342 {
3343 GBytes *content;
3344
3345 g_assert (IDE_IS_MAIN_THREAD ());
3346 g_assert (IDE_IS_BUFFER (self));
3347
3348 if ((content = ide_buffer_dup_content (self)))
3349 g_bytes_unref (content);
3350 }
3351
3352 /**
3353 * ide_buffer_rehighlight:
3354 * @self: an #IdeBuffer
3355 *
3356 * Force @self to rebuild the highlighted words.
3357 *
3358 * Since: 3.32
3359 */
3360 void
ide_buffer_rehighlight(IdeBuffer * self)3361 ide_buffer_rehighlight (IdeBuffer *self)
3362 {
3363 IDE_ENTRY;
3364
3365 g_return_if_fail (IDE_IS_MAIN_THREAD ());
3366 g_return_if_fail (IDE_IS_BUFFER (self));
3367
3368 /* In case we are disposing */
3369 if (self->highlight_engine == NULL || ide_buffer_get_loading (self))
3370 IDE_EXIT;
3371
3372 if (gtk_source_buffer_get_highlight_syntax (GTK_SOURCE_BUFFER (self)))
3373 ide_highlight_engine_rebuild (self->highlight_engine);
3374 else
3375 ide_highlight_engine_clear (self->highlight_engine);
3376
3377 IDE_EXIT;
3378 }
3379
3380 static void
ide_buffer_get_symbol_at_location_cb(GObject * object,GAsyncResult * result,gpointer user_data)3381 ide_buffer_get_symbol_at_location_cb (GObject *object,
3382 GAsyncResult *result,
3383 gpointer user_data)
3384 {
3385 IdeSymbolResolver *symbol_resolver = (IdeSymbolResolver *)object;
3386 g_autoptr(IdeSymbol) symbol = NULL;
3387 g_autoptr(IdeTask) task = user_data;
3388 g_autoptr(GError) error = NULL;
3389 LookUpSymbolData *data;
3390 IdeBuffer *self;
3391
3392 g_assert (IDE_IS_MAIN_THREAD ());
3393 g_assert (IDE_IS_SYMBOL_RESOLVER (symbol_resolver));
3394 g_assert (G_IS_ASYNC_RESULT (result));
3395 g_assert (IDE_IS_TASK (task));
3396
3397 data = ide_task_get_task_data (task);
3398 self = ide_task_get_source_object (task);
3399 g_assert (data->resolvers != NULL);
3400 g_assert (data->resolvers->len > 0);
3401
3402 g_clear_object (&self->in_flight_symbol_at_location);
3403 self->in_flight_symbol_at_location_pos = -1;
3404
3405 if ((symbol = ide_symbol_resolver_lookup_symbol_finish (symbol_resolver, result, &error)))
3406 {
3407 /*
3408 * Store symbol which has definition location. If no symbol has
3409 * definition location then store symbol which has declaration location.
3410 */
3411 if ((data->symbol == NULL) ||
3412 (ide_symbol_get_location (symbol) != NULL) ||
3413 (ide_symbol_get_location (data->symbol) == NULL &&
3414 ide_symbol_get_header_location (symbol)))
3415 {
3416 g_clear_object (&data->symbol);
3417 data->symbol = g_steal_pointer (&symbol);
3418 }
3419 }
3420
3421 g_ptr_array_remove_index (data->resolvers, data->resolvers->len - 1);
3422
3423 if (data->resolvers->len > 0)
3424 {
3425 IdeSymbolResolver *resolver;
3426 GCancellable *cancellable;
3427
3428 resolver = g_ptr_array_index (data->resolvers, data->resolvers->len - 1);
3429 cancellable = ide_task_get_cancellable (task);
3430
3431 ide_symbol_resolver_lookup_symbol_async (resolver,
3432 data->location,
3433 cancellable,
3434 ide_buffer_get_symbol_at_location_cb,
3435 g_steal_pointer (&task));
3436 }
3437 else if (data->symbol == NULL)
3438 {
3439 ide_task_return_new_error (task,
3440 G_IO_ERROR,
3441 G_IO_ERROR_NOT_FOUND,
3442 "Symbol not found");
3443 }
3444 else
3445 {
3446 ide_task_return_object (task, g_steal_pointer (&data->symbol));
3447 }
3448 }
3449
3450 /**
3451 * ide_buffer_get_symbol_at_location_async:
3452 * @self: an #IdeBuffer
3453 * @location: a #GtkTextIter indicating a position to search for a symbol
3454 * @cancellable: a #GCancellable
3455 * @callback: a #GAsyncReadyCallback
3456 * @user_data: a #gpointer to hold user data
3457 *
3458 * Asynchronously get a possible symbol at @location.
3459 *
3460 * Since: 3.32
3461 */
3462 void
ide_buffer_get_symbol_at_location_async(IdeBuffer * self,const GtkTextIter * location,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)3463 ide_buffer_get_symbol_at_location_async (IdeBuffer *self,
3464 const GtkTextIter *location,
3465 GCancellable *cancellable,
3466 GAsyncReadyCallback callback,
3467 gpointer user_data)
3468 {
3469 g_autoptr(IdeLocation) srcloc = NULL;
3470 g_autoptr(IdeTask) task = NULL;
3471 g_autoptr(GPtrArray) resolvers = NULL;
3472 IdeSymbolResolver *resolver;
3473 LookUpSymbolData *data;
3474 guint line;
3475 guint line_offset;
3476
3477 g_return_if_fail (IDE_IS_MAIN_THREAD ());
3478 g_return_if_fail (IDE_IS_BUFFER (self));
3479 g_return_if_fail (location != NULL);
3480 g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
3481
3482 resolvers = ide_buffer_get_symbol_resolvers (self);
3483 IDE_PTR_ARRAY_SET_FREE_FUNC (resolvers, g_object_unref);
3484
3485 task = ide_task_new (self, cancellable, callback, user_data);
3486 ide_task_set_source_tag (task, ide_buffer_get_symbol_at_location_async);
3487
3488 if (resolvers->len == 0)
3489 {
3490 ide_task_return_new_error (task,
3491 G_IO_ERROR,
3492 G_IO_ERROR_NOT_SUPPORTED,
3493 _("The current language lacks a symbol resolver."));
3494 return;
3495 }
3496
3497 /* If this query is the same as one in-flight, then try to chain
3498 * to that query instead of competing and duplicating work.
3499 */
3500 if (self->in_flight_symbol_at_location_pos == (int)gtk_text_iter_get_offset (location) &&
3501 self->in_flight_symbol_at_location != NULL)
3502 {
3503 ide_task_chain (self->in_flight_symbol_at_location, task);
3504 return;
3505 }
3506 else
3507 {
3508 g_set_object (&self->in_flight_symbol_at_location, task);
3509 self->in_flight_symbol_at_location_pos = gtk_text_iter_get_offset (location);
3510 }
3511
3512 _ide_buffer_sync_to_unsaved_files (self);
3513
3514 line = gtk_text_iter_get_line (location);
3515 line_offset = gtk_text_iter_get_line_offset (location);
3516 srcloc = ide_location_new (ide_buffer_get_file (self), line, line_offset);
3517
3518 data = g_slice_new0 (LookUpSymbolData);
3519 data->resolvers = g_steal_pointer (&resolvers);
3520 data->location = g_steal_pointer (&srcloc);
3521 ide_task_set_task_data (task, data, lookup_symbol_data_free);
3522
3523 /* Try lookup_symbol on each symbol resolver one by by one. */
3524 resolver = g_ptr_array_index (data->resolvers, data->resolvers->len - 1);
3525 ide_symbol_resolver_lookup_symbol_async (resolver,
3526 data->location,
3527 cancellable,
3528 ide_buffer_get_symbol_at_location_cb,
3529 g_steal_pointer (&task));
3530 }
3531
3532 /**
3533 * ide_buffer_get_symbol_at_location_finish:
3534 * @self: an #IdeBuffer
3535 * @result: a #GAsyncResult
3536 * @error: a location for a #GError
3537 *
3538 * Completes an asynchronous request to locate a symbol at a location.
3539 *
3540 * Returns: (transfer full): An #IdeSymbol or %NULL.
3541 *
3542 * Since: 3.32
3543 */
3544 IdeSymbol *
ide_buffer_get_symbol_at_location_finish(IdeBuffer * self,GAsyncResult * result,GError ** error)3545 ide_buffer_get_symbol_at_location_finish (IdeBuffer *self,
3546 GAsyncResult *result,
3547 GError **error)
3548 {
3549 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
3550 g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
3551 g_return_val_if_fail (IDE_IS_TASK (result), NULL);
3552
3553 return ide_task_propagate_object (IDE_TASK (result), error);
3554 }
3555
3556 /**
3557 * ide_buffer_get_selection_bounds:
3558 * @self: an #IdeBuffer
3559 * @insert: (out): a #GtkTextIter to get the insert position
3560 * @selection: (out): a #GtkTextIter to get the selection position
3561 *
3562 * This function acts like gtk_text_buffer_get_selection_bounds() except that
3563 * it always places the location of the insert mark at @insert and the location
3564 * of the selection mark at @selection.
3565 *
3566 * Calling gtk_text_iter_order() with the results of this function would be
3567 * equivalent to calling gtk_text_buffer_get_selection_bounds().
3568 *
3569 * Since: 3.32
3570 */
3571 void
ide_buffer_get_selection_bounds(IdeBuffer * self,GtkTextIter * insert,GtkTextIter * selection)3572 ide_buffer_get_selection_bounds (IdeBuffer *self,
3573 GtkTextIter *insert,
3574 GtkTextIter *selection)
3575 {
3576 GtkTextMark *mark;
3577
3578 g_return_if_fail (IDE_IS_MAIN_THREAD ());
3579 g_return_if_fail (IDE_IS_BUFFER (self));
3580
3581 if (insert != NULL)
3582 {
3583 mark = gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (self));
3584 gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (self), insert, mark);
3585 }
3586
3587 if (selection != NULL)
3588 {
3589 mark = gtk_text_buffer_get_selection_bound (GTK_TEXT_BUFFER (self));
3590 gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (self), selection, mark);
3591 }
3592 }
3593
3594 static void
ide_buffer_get_symbol_resolvers_cb(IdeExtensionSetAdapter * set,PeasPluginInfo * plugin_info,PeasExtension * exten,gpointer user_data)3595 ide_buffer_get_symbol_resolvers_cb (IdeExtensionSetAdapter *set,
3596 PeasPluginInfo *plugin_info,
3597 PeasExtension *exten,
3598 gpointer user_data)
3599 {
3600 IdeSymbolResolver *resolver = (IdeSymbolResolver *)exten;
3601 GPtrArray *ar = user_data;
3602
3603 g_assert (IDE_IS_MAIN_THREAD ());
3604 g_assert (IDE_IS_EXTENSION_SET_ADAPTER (set));
3605 g_assert (plugin_info != NULL);
3606 g_assert (IDE_IS_SYMBOL_RESOLVER (resolver));
3607 g_assert (ar != NULL);
3608
3609 g_ptr_array_add (ar, g_object_ref (resolver));
3610 }
3611
3612 /**
3613 * ide_buffer_get_symbol_resolvers:
3614 * @self: an #IdeBuffer
3615 *
3616 * Gets the symbol resolvers for the buffer based on the current language. The
3617 * resolvers in the resulting array are sorted by priority.
3618 *
3619 * Returns: (transfer full) (element-type IdeSymbolResolver): a #GPtrArray
3620 * of #IdeSymbolResolver.
3621 *
3622 * Since: 3.32
3623 */
3624 GPtrArray *
ide_buffer_get_symbol_resolvers(IdeBuffer * self)3625 ide_buffer_get_symbol_resolvers (IdeBuffer *self)
3626 {
3627 GPtrArray *ar;
3628
3629 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
3630 g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
3631
3632 ar = g_ptr_array_new_with_free_func (g_object_unref);
3633
3634 if (self->symbol_resolvers != NULL)
3635 ide_extension_set_adapter_foreach_by_priority (self->symbol_resolvers,
3636 ide_buffer_get_symbol_resolvers_cb,
3637 ar);
3638
3639 return IDE_PTR_ARRAY_STEAL_FULL (&ar);
3640 }
3641
3642 /**
3643 * ide_buffer_get_line_text:
3644 * @self: a #IdeBuffer
3645 * @line: a line number starting from 0
3646 *
3647 * Gets the contents of a single line within the buffer.
3648 *
3649 * Returns: (transfer full) (nullable): a string containing the line's text
3650 * or %NULL if the line does not exist.
3651 *
3652 * Since: 3.32
3653 */
3654 gchar *
ide_buffer_get_line_text(IdeBuffer * self,guint line)3655 ide_buffer_get_line_text (IdeBuffer *self,
3656 guint line)
3657 {
3658 GtkTextIter begin;
3659
3660 g_assert (IDE_IS_BUFFER (self));
3661
3662 gtk_text_buffer_get_iter_at_line (GTK_TEXT_BUFFER (self), &begin, line);
3663
3664 if (gtk_text_iter_get_line (&begin) == line)
3665 {
3666 GtkTextIter end = begin;
3667
3668 if (gtk_text_iter_ends_line (&end) ||
3669 gtk_text_iter_forward_to_line_end (&end))
3670 return gtk_text_iter_get_slice (&begin, &end);
3671 }
3672
3673 return g_strdup ("");
3674 }
3675
3676 static void
ide_buffer_guess_language(IdeBuffer * self)3677 ide_buffer_guess_language (IdeBuffer *self)
3678 {
3679 GtkSourceLanguageManager *manager;
3680 GtkSourceLanguage *lang;
3681 g_autofree gchar *basename = NULL;
3682 g_autofree gchar *content_type = NULL;
3683 g_autofree gchar *line = NULL;
3684 const gchar *lang_id;
3685 const gchar *path;
3686 GFile *file;
3687 gboolean uncertain = FALSE;
3688
3689 g_assert (IDE_IS_MAIN_THREAD ());
3690 g_assert (IDE_IS_BUFFER (self));
3691
3692 line = ide_buffer_get_line_text (self, 0);
3693 file = ide_buffer_get_file (self);
3694
3695 basename = g_file_get_basename (file);
3696
3697 if (!g_file_is_native (file))
3698 path = basename;
3699 else
3700 path = g_file_peek_path (file);
3701
3702 manager = gtk_source_language_manager_get_default ();
3703 lang = gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (self));
3704
3705 content_type = g_content_type_guess (path, (const guchar *)line, strlen (line), &uncertain);
3706 if (uncertain && lang != NULL)
3707 return;
3708
3709 /* First try with full path, then with shortname */
3710 if (!(lang = gtk_source_language_manager_guess_language (manager, path, content_type)) &&
3711 !(lang = gtk_source_language_manager_guess_language (manager, basename, content_type)))
3712 return;
3713
3714 lang_id = gtk_source_language_get_id (lang);
3715
3716 /* Override to python3 by default for now, until shared-mime-info
3717 * gets a better way to detect the difference between the two.
3718 */
3719 if (ide_str_equal0 (lang_id, "python"))
3720 {
3721 lang_id = "python3";
3722 lang = gtk_source_language_manager_get_language (manager, lang_id);
3723 }
3724
3725 if (!ide_str_equal0 (lang_id, ide_buffer_get_language_id (self)))
3726 gtk_source_buffer_set_language (GTK_SOURCE_BUFFER (self), lang);
3727 }
3728
3729 gboolean
_ide_buffer_can_restore_cursor(IdeBuffer * self)3730 _ide_buffer_can_restore_cursor (IdeBuffer *self)
3731 {
3732 g_return_val_if_fail (IDE_IS_BUFFER (self), FALSE);
3733
3734 return self->can_restore_cursor;
3735 }
3736
3737 void
_ide_buffer_cancel_cursor_restore(IdeBuffer * self)3738 _ide_buffer_cancel_cursor_restore (IdeBuffer *self)
3739 {
3740 g_return_if_fail (IDE_IS_BUFFER (self));
3741
3742 self->can_restore_cursor = FALSE;
3743 }
3744
3745 /**
3746 * ide_buffer_hold:
3747 * @self: a #IdeBuffer
3748 *
3749 * Increases the "hold count" of the #IdeBuffer by one.
3750 *
3751 * The hold count is similar to a reference count, as it allows the buffer
3752 * manager to know when a buffer may be destroyed cleanly.
3753 *
3754 * Doing so ensures that the buffer wont be unloaded or have reference
3755 * cycles broken.
3756 *
3757 * Release the hold with ide_buffer_release().
3758 *
3759 * When the hold count reaches zero, the buffer will be destroyed.
3760 *
3761 * Returns: (transfer full): @self
3762 *
3763 * Since: 3.32
3764 */
3765 IdeBuffer *
ide_buffer_hold(IdeBuffer * self)3766 ide_buffer_hold (IdeBuffer *self)
3767 {
3768 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
3769 g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
3770
3771 self->hold++;
3772
3773 return g_object_ref (self);
3774 }
3775
3776 /**
3777 * ide_buffer_release:
3778 * @self: a #IdeBuffer
3779 *
3780 * Releases the "hold count" on a buffer.
3781 *
3782 * The buffer will be destroyed and unloaded when the hold count
3783 * reaches zero.
3784 *
3785 * Since: 3.32
3786 */
3787 void
ide_buffer_release(IdeBuffer * self)3788 ide_buffer_release (IdeBuffer *self)
3789 {
3790 g_return_if_fail (IDE_IS_MAIN_THREAD ());
3791 g_return_if_fail (IDE_IS_BUFFER (self));
3792 g_return_if_fail (self->hold > 0);
3793
3794 self->hold--;
3795
3796 if (self->hold == 0)
3797 {
3798 IdeObjectBox *box = ide_object_box_from_object (G_OBJECT (self));
3799
3800 if (box != NULL)
3801 ide_object_destroy (IDE_OBJECT (box));
3802 }
3803
3804 g_object_unref (self);
3805 }
3806
3807 IdeExtensionSetAdapter *
_ide_buffer_get_addins(IdeBuffer * self)3808 _ide_buffer_get_addins (IdeBuffer *self)
3809 {
3810 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
3811 g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
3812
3813 return self->addins;
3814 }
3815
3816 void
_ide_buffer_line_flags_changed(IdeBuffer * self)3817 _ide_buffer_line_flags_changed (IdeBuffer *self)
3818 {
3819 g_return_if_fail (IDE_IS_MAIN_THREAD ());
3820 g_return_if_fail (IDE_IS_BUFFER (self));
3821
3822 g_signal_emit (self, signals [LINE_FLAGS_CHANGED], 0);
3823 }
3824
3825 /**
3826 * ide_buffer_has_symbol_resolvers:
3827 * @self: a #IdeBuffer
3828 *
3829 * Checks if any symbol resolvers are available.
3830 *
3831 * Returns: %TRUE if at least one symbol resolvers is available
3832 *
3833 * Since: 3.32
3834 */
3835 gboolean
ide_buffer_has_symbol_resolvers(IdeBuffer * self)3836 ide_buffer_has_symbol_resolvers (IdeBuffer *self)
3837 {
3838 g_return_val_if_fail (IDE_IS_BUFFER (self), FALSE);
3839
3840 return self->symbol_resolvers != NULL &&
3841 ide_extension_set_adapter_get_n_extensions (self->symbol_resolvers) > 0;
3842 }
3843
3844 static void
settle_cb(GObject * object,GAsyncResult * result,gpointer user_data)3845 settle_cb (GObject *object,
3846 GAsyncResult *result,
3847 gpointer user_data)
3848 {
3849 IdeBufferAddin *addin = (IdeBufferAddin *)object;
3850 g_autoptr(IdeTask) task = user_data;
3851 g_autoptr(GError) error = NULL;
3852 gint *n_active;
3853
3854 g_assert (IDE_IS_MAIN_THREAD ());
3855 g_assert (IDE_IS_BUFFER_ADDIN (addin));
3856 g_assert (G_IS_ASYNC_RESULT (result));
3857 g_assert (IDE_IS_TASK (task));
3858
3859 n_active = ide_task_get_task_data (task);
3860
3861 if (!ide_buffer_addin_settle_finish (addin, result, &error))
3862 g_warning ("Buffer addin \"%s\" failed to settle: %s",
3863 G_OBJECT_TYPE_NAME (addin),
3864 error->message);
3865
3866 (*n_active)--;
3867
3868 if (*n_active == 0)
3869 ide_task_return_boolean (task, TRUE);
3870 }
3871
3872 static void
settle_foreach_cb(IdeExtensionSetAdapter * set,PeasPluginInfo * plugin_info,PeasExtension * exten,gpointer user_data)3873 settle_foreach_cb (IdeExtensionSetAdapter *set,
3874 PeasPluginInfo *plugin_info,
3875 PeasExtension *exten,
3876 gpointer user_data)
3877 {
3878 IdeBufferAddin *addin = (IdeBufferAddin *)exten;
3879 IdeTask *task = user_data;
3880 gint *n_active;
3881
3882 g_assert (IDE_IS_EXTENSION_SET_ADAPTER (set));
3883 g_assert (plugin_info != NULL);
3884 g_assert (IDE_IS_BUFFER_ADDIN (addin));
3885 g_assert (IDE_IS_TASK (task));
3886
3887 n_active = ide_task_get_task_data (task);
3888
3889 (*n_active)++;
3890
3891 ide_buffer_addin_settle_async (addin,
3892 ide_task_get_cancellable (task),
3893 settle_cb,
3894 g_object_ref (task));
3895 }
3896
3897 static void
settle_async(IdeBuffer * self,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)3898 settle_async (IdeBuffer *self,
3899 GCancellable *cancellable,
3900 GAsyncReadyCallback callback,
3901 gpointer user_data)
3902 {
3903 g_autoptr(IdeTask) task = NULL;
3904 gint *n_active;
3905
3906 IDE_ENTRY;
3907
3908 g_assert (IDE_IS_MAIN_THREAD ());
3909 g_assert (IDE_IS_BUFFER (self));
3910 g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
3911
3912 n_active = g_new0 (gint, 1);
3913
3914 task = ide_task_new (self, cancellable, callback, user_data);
3915 ide_task_set_source_tag (task, settle_async);
3916 ide_task_set_task_data (task, n_active, g_free);
3917
3918 if (self->addins != NULL && self->enable_addins)
3919 ide_extension_set_adapter_foreach (self->addins,
3920 settle_foreach_cb,
3921 task);
3922
3923 if (*n_active == 0)
3924 ide_task_return_boolean (task, TRUE);
3925
3926 IDE_EXIT;
3927 }
3928
3929 static gboolean
settle_finish(IdeBuffer * self,GAsyncResult * result,GError ** error)3930 settle_finish (IdeBuffer *self,
3931 GAsyncResult *result,
3932 GError **error)
3933 {
3934 gboolean ret;
3935
3936 IDE_ENTRY;
3937
3938 g_assert (IDE_IS_MAIN_THREAD ());
3939 g_assert (IDE_IS_BUFFER (self));
3940 g_assert (IDE_IS_TASK (result));
3941
3942 ret = ide_task_propagate_boolean (IDE_TASK (result), error);
3943
3944 IDE_RETURN (ret);
3945 }
3946
3947 void
_ide_buffer_request_scroll_to_cursor(IdeBuffer * self)3948 _ide_buffer_request_scroll_to_cursor (IdeBuffer *self)
3949 {
3950 g_return_if_fail (IDE_IS_MAIN_THREAD ());
3951 g_return_if_fail (IDE_IS_BUFFER (self));
3952
3953 g_signal_emit (self, signals [REQUEST_SCROLL_TO_INSERT], 0);
3954 }
3955
3956 gboolean
_ide_buffer_is_file(IdeBuffer * self,GFile * nolink_file)3957 _ide_buffer_is_file (IdeBuffer *self,
3958 GFile *nolink_file)
3959 {
3960 g_return_val_if_fail (IDE_IS_BUFFER (self), FALSE);
3961 g_return_val_if_fail (G_IS_FILE (nolink_file), FALSE);
3962
3963 return g_file_equal (nolink_file, ide_buffer_get_file (self)) ||
3964 g_file_equal (nolink_file, self->readlink_file);
3965 }
3966