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 (&note_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", &note_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 (&note_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", &note_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