ide-buffer-manager.c 49.1 KB
Newer Older
1
2
3
4
/* ide-buffer-manager.c
 *
 * Copyright (C) 2015 Christian Hergert <christian@hergert.me>
 *
5
6
7
8
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
9
 *
10
11
12
13
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
14
15
16
17
18
19
20
21
22
23
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#define G_LOG_DOMAIN "ide-buffer-manager"

#include <gtksourceview/gtksource.h>
#include <glib/gi18n.h>

24
25
#include "ide-back-forward-item.h"
#include "ide-back-forward-list.h"
26
27
#include "ide-buffer.h"
#include "ide-buffer-manager.h"
28
#include "ide-context.h"
29
#include "ide-debug.h"
30
#include "ide-doc-seq.h"
31
32
#include "ide-file.h"
#include "ide-file-settings.h"
33
#include "ide-global.h"
34
35
#include "ide-internal.h"
#include "ide-progress.h"
36
#include "ide-source-location.h"
37
#include "ide-unsaved-files.h"
38
#include "ide-vcs.h"
39

40
41
#define AUTO_SAVE_TIMEOUT_DEFAULT    60
#define MAX_FILE_SIZE_BYTES_DEFAULT  (1024UL * 1024UL * 10UL)
42
43
44

struct _IdeBufferManager
{
Christian Hergert's avatar
Christian Hergert committed
45
  IdeObject                 parent_instance;
46

Christian Hergert's avatar
Christian Hergert committed
47
48
49
  GPtrArray                *buffers;
  GHashTable               *timeouts;
  IdeBuffer                *focus_buffer;
50
51
  GtkSourceCompletionWords *word_completion;

52
53
  gsize                     max_file_size;

Christian Hergert's avatar
Christian Hergert committed
54
55
  guint                     auto_save_timeout;
  guint                     auto_save : 1;
56
57
58
59
60
61
62
63
64
65
66
};

typedef struct
{
  IdeBufferManager *self;
  IdeBuffer        *buffer;
  guint             source_id;
} AutoSave;

typedef struct
{
67
68
69
70
71
  IdeBuffer           *buffer;
  IdeFile             *file;
  IdeProgress         *progress;
  GtkSourceFileLoader *loader;
  guint                is_new : 1;
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
} LoadState;

typedef struct
{
  IdeBuffer   *buffer;
  IdeFile     *file;
  IdeProgress *progress;
} SaveState;

G_DEFINE_TYPE (IdeBufferManager, ide_buffer_manager, IDE_TYPE_OBJECT)

enum {
  PROP_0,
  PROP_AUTO_SAVE,
  PROP_AUTO_SAVE_TIMEOUT,
  PROP_FOCUS_BUFFER,
88
  PROP_MAX_FILE_SIZE,
89
90
91
92
  LAST_PROP
};

enum {
Christian Hergert's avatar
wip    
Christian Hergert committed
93
94
  CREATE_BUFFER,

95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
  SAVE_BUFFER,
  BUFFER_SAVED,

  LOAD_BUFFER,
  BUFFER_LOADED,

  BUFFER_FOCUS_ENTER,
  BUFFER_FOCUS_LEAVE,

  LAST_SIGNAL
};

static void register_auto_save   (IdeBufferManager *self,
                                  IdeBuffer        *buffer);
static void unregister_auto_save (IdeBufferManager *self,
                                  IdeBuffer        *buffer);

static GParamSpec *gParamSpecs [LAST_PROP];
static guint gSignals [LAST_SIGNAL];

static void
save_state_free (gpointer data)
{
  SaveState *state = data;

  if (state)
    {
      g_clear_object (&state->buffer);
      g_clear_object (&state->file);
      g_clear_object (&state->progress);
      g_slice_free (SaveState, state);
    }
}

static void
load_state_free (gpointer data)
{
  LoadState *state = data;

  if (state)
    {
      g_clear_object (&state->buffer);
      g_clear_object (&state->file);
      g_clear_object (&state->progress);
139
      g_clear_object (&state->loader);
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
      g_slice_free (LoadState, state);
    }
}

/**
 * ide_buffer_manager_get_auto_save_timeout:
 *
 * Gets the value of the #IdeBufferManager:auto-save-timeout property.
 *
 * Returns: The timeout in seconds if enabled, otherwise 0.
 */
guint
ide_buffer_manager_get_auto_save_timeout (IdeBufferManager *self)
{
  g_return_val_if_fail (IDE_IS_BUFFER_MANAGER (self), 0);

  if (self->auto_save)
    return self->auto_save_timeout;

  return 0;
}

/**
 * ide_buffer_manager_set_auto_save_timeout:
 * @auto_save_timeout: The auto save timeout in seconds.
 *
 * Sets the #IdeBufferManager:auto-save-timeout property.
 *
 * You can set this property to 0 to use the default timeout.
 *
 * This is the number of seconds to wait after a buffer has been changed before
 * automatically saving the buffer.
 */
void
ide_buffer_manager_set_auto_save_timeout (IdeBufferManager *self,
                                          guint             auto_save_timeout)
{
  g_return_if_fail (IDE_IS_BUFFER_MANAGER (self));

  if (!auto_save_timeout)
    auto_save_timeout = AUTO_SAVE_TIMEOUT_DEFAULT;

  if (self->auto_save_timeout != auto_save_timeout)
    {
      self->auto_save_timeout = auto_save_timeout;
      g_object_notify_by_pspec (G_OBJECT (self),
                                gParamSpecs [PROP_AUTO_SAVE_TIMEOUT]);
    }
}

/**
 * ide_buffer_manager_get_auto_save:
 *
 * Gets the #IdeBufferManager:auto-save property.
 *
 * If auto-save is enabled, then buffers managed by @self will be automatically
 * persisted #IdeBufferManager:auto-save-timeout seconds after their last
 * change.
 *
 * Returns: %TRUE if auto save is enabled. otherwise %FALSE.
 */
gboolean
ide_buffer_manager_get_auto_save (IdeBufferManager *self)
{
  g_return_val_if_fail (IDE_IS_BUFFER_MANAGER (self), FALSE);

  return self->auto_save;
}

/**
 * ide_buffer_manager_set_auto_save:
 * @auto_save: %TRUE if auto-save should be enabled.
 *
 * Sets the #IdeBufferManager:auto-save property. If this is %TRUE, then a
 * buffer will automatically be saved after #IdeBufferManager:auto-save-timeout
 * seconds have elapsed since the buffers last modification.
 */
void
ide_buffer_manager_set_auto_save (IdeBufferManager *self,
                                  gboolean          auto_save)
{
  g_return_if_fail (IDE_IS_BUFFER_MANAGER (self));

  auto_save = !!auto_save;

  if (self->auto_save != auto_save)
    {
      gsize i;

      self->auto_save = auto_save;

      for (i = 0; i < self->buffers->len; i++)
        {
          IdeBuffer *buffer;

          buffer = g_ptr_array_index (self->buffers, i);

          if (auto_save)
            register_auto_save (self, buffer);
          else
            unregister_auto_save (self, buffer);
        }

      g_object_notify_by_pspec (G_OBJECT (self),
                                gParamSpecs [PROP_AUTO_SAVE]);
    }
}

/**
 * ide_buffer_manager_get_focus_buffer:
 *
 * Gets the #IdeBufferManager:focus-buffer property. This the buffer behind
 * the current selected view.
 *
 * Returns: (transfer none): An #IdeBuffer or %NULL.
 */
IdeBuffer *
ide_buffer_manager_get_focus_buffer (IdeBufferManager *self)
{
  g_return_val_if_fail (IDE_IS_BUFFER_MANAGER (self), NULL);

  return self->focus_buffer;
}

void
ide_buffer_manager_set_focus_buffer (IdeBufferManager *self,
                                     IdeBuffer        *buffer)
{
  IdeBuffer *previous;

  g_return_if_fail (IDE_IS_BUFFER_MANAGER (self));
  g_return_if_fail (!buffer || IDE_IS_BUFFER (buffer));

  previous = self->focus_buffer;

  if (ide_set_weak_pointer (&self->focus_buffer, buffer))
    {
      /* notify that we left the previous buffer */
      if (previous)
        g_signal_emit (self, gSignals [BUFFER_FOCUS_LEAVE], 0, previous);

      /* notify of the new buffer, but check for reentrancy */
      if (buffer && (buffer == self->focus_buffer))
        g_signal_emit (self, gSignals [BUFFER_FOCUS_ENTER], 0, buffer);
284
285

      g_object_notify_by_pspec (G_OBJECT (self), gParamSpecs [PROP_FOCUS_BUFFER]);
286
287
288
289
290
291
292
    }
}

static gboolean
ide_buffer_manager_auto_save_cb (gpointer data)
{
  AutoSave *state = data;
293
  IdeFile *file;
294

Christian Hergert's avatar
Christian Hergert committed
295
296
297
298
  g_assert (state);
  g_assert (IDE_IS_BUFFER_MANAGER (state->self));
  g_assert (IDE_IS_BUFFER (state->buffer));
  g_assert (state->source_id > 0);
299

300
301
302
303
  file = ide_buffer_get_file (state->buffer);
  if (file)
    ide_buffer_manager_save_file_async (state->self, state->buffer, file, NULL, NULL, NULL, NULL);

304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
  unregister_auto_save (state->self, state->buffer);

  return G_SOURCE_REMOVE;
}


static void
ide_buffer_manager_buffer_changed (IdeBufferManager *self,
                                   IdeBuffer        *buffer)
{
  g_return_if_fail (IDE_IS_BUFFER_MANAGER (self));
  g_return_if_fail (IDE_IS_BUFFER (buffer));

  if (self->auto_save)
    {
      unregister_auto_save (self, buffer);
      register_auto_save (self, buffer);
    }
}

static void
ide_buffer_manager_add_buffer (IdeBufferManager *self,
                               IdeBuffer        *buffer)
{
  g_return_if_fail (IDE_IS_BUFFER_MANAGER (self));
  g_return_if_fail (IDE_IS_BUFFER (buffer));

  g_ptr_array_add (self->buffers, g_object_ref (buffer));

  if (self->auto_save)
    register_auto_save (self, buffer);

336
337
  gtk_source_completion_words_register (self->word_completion, GTK_TEXT_BUFFER (buffer));

338
339
340
341
342
343
344
345
346
347
348
  g_signal_connect_object (buffer,
                           "changed",
                           G_CALLBACK (ide_buffer_manager_buffer_changed),
                           self,
                           (G_CONNECT_SWAPPED | G_CONNECT_AFTER));
}

static void
ide_buffer_manager_remove_buffer (IdeBufferManager *self,
                                  IdeBuffer        *buffer)
{
349
350
  IDE_ENTRY;

351
352
353
354
355
  g_return_if_fail (IDE_IS_BUFFER_MANAGER (self));
  g_return_if_fail (IDE_IS_BUFFER (buffer));

  if (g_ptr_array_remove_fast (self->buffers, buffer))
    {
356
357
358
359
360
361
362
363
364
365
366
367
      IdeUnsavedFiles *unsaved_files;
      IdeContext *context;
      IdeFile *file;
      GFile *gfile;

      file = ide_buffer_get_file (buffer);
      gfile = ide_file_get_file (file);

      context = ide_object_get_context (IDE_OBJECT (self));
      unsaved_files = ide_context_get_unsaved_files (context);
      ide_unsaved_files_remove (unsaved_files, gfile);

368
      gtk_source_completion_words_unregister (self->word_completion, GTK_TEXT_BUFFER (buffer));
369

370
      unregister_auto_save (self, buffer);
371

372
373
374
      g_signal_handlers_disconnect_by_func (buffer,
                                            G_CALLBACK (ide_buffer_manager_buffer_changed),
                                            self);
375

376
377
      g_object_unref (buffer);
    }
378
379

  IDE_EXIT;
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
}

static IdeBuffer *
ide_buffer_manager_get_buffer (IdeBufferManager *self,
                               IdeFile          *file)
{
  gsize i;

  g_return_val_if_fail (IDE_IS_BUFFER_MANAGER (self), NULL);

  for (i = 0; i < self->buffers->len; i++)
    {
      IdeBuffer *buffer;
      IdeFile *cur_file;

      buffer = g_ptr_array_index (self->buffers, i);
      cur_file = ide_buffer_get_file (buffer);

      if (ide_file_equal (cur_file, file))
        return buffer;
    }

  return NULL;
}

static void
ide_buffer_manager_load_file__load_cb (GObject      *object,
                                       GAsyncResult *result,
                                       gpointer      user_data)
{
  g_autoptr(GTask) task = user_data;
411
412
  g_autofree gchar *guess_contents = NULL;
  g_autofree gchar *content_type = NULL;
413
  GtkSourceFileLoader *loader = (GtkSourceFileLoader *)object;
414
415
  IdeBackForwardList *back_forward_list;
  IdeBackForwardItem *item;
416
  IdeBufferManager *self;
417
  const gchar *path;
418
  IdeContext *context;
419
  LoadState *state;
420
  GtkTextIter iter;
421
  GtkTextIter end;
422
  GError *error = NULL;
423
  gboolean uncertain = TRUE;
424
  gsize i;
425
426
427
428
429
430
431
432
433
434
435
436

  g_assert (G_IS_TASK (task));
  g_assert (GTK_SOURCE_IS_FILE_LOADER (loader));

  self = g_task_get_source_object (task);
  state = g_task_get_task_data (task);

  g_assert (IDE_IS_BUFFER_MANAGER (self));
  g_assert (IDE_IS_FILE (state->file));
  g_assert (IDE_IS_BUFFER (state->buffer));
  g_assert (IDE_IS_PROGRESS (state->progress));

437
438
  context = ide_object_get_context (IDE_OBJECT (self));

439
440
  if (!gtk_source_file_loader_load_finish (loader, result, &error))
    {
441
442
443
444
445
      /*
       * It's okay if we fail because the file does not exist yet.
       */
      if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
        {
446
          _ide_buffer_set_loading (state->buffer, FALSE);
447
448
449
450
451
          g_task_return_error (task, error);
          return;
        }

      g_clear_error (&error);
452
453
    }

454
455
456
457
458
459
460
461
462
463
  for (i = 0; i < self->buffers->len; i++)
    {
      IdeBuffer *cur_buffer;

      cur_buffer = g_ptr_array_index (self->buffers, i);

      if (cur_buffer == state->buffer)
        goto emit_signal;
    }

464
465
  if (state->is_new)
    ide_buffer_manager_add_buffer (self, state->buffer);
466

467
468
469
470
471
472
  /*
   * If we have a navigation item for this buffer, restore the insert mark to
   * the most recent navigation point.
   */
  back_forward_list = ide_context_get_back_forward_list (context);
  item = _ide_back_forward_list_find (back_forward_list, state->file);
473

474
475
476
477
478
479
480
481
482
483
  if (item != NULL)
    {
      IdeSourceLocation *item_loc;
      guint line;
      guint line_offset;

      item_loc = ide_back_forward_item_get_location (item);
      line = ide_source_location_get_line (item_loc);
      line_offset = ide_source_location_get_line_offset (item_loc);

484
      IDE_TRACE_MSG ("Restoring insert mark to %u:%u", line, line_offset);
485

486
      gtk_text_buffer_get_iter_at_line (GTK_TEXT_BUFFER (state->buffer), &iter, line);
487
488
489
490
      for (; line_offset; line_offset--)
        if (gtk_text_iter_ends_line (&iter) || !gtk_text_iter_forward_char (&iter))
          break;
    }
491
492
493
494
495
496
497
  else
    {
      IDE_TRACE_MSG ("Restoring insert mark to 0:0");
      gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (state->buffer), &iter);
    }

  gtk_text_buffer_select_range (GTK_TEXT_BUFFER (state->buffer), &iter, &iter);
498

499
500
501
502
503
504
505
506
507
508
509
510
511
512
  /*
   * Try to discover the content type more accurately now that we have access to the
   * file contents inside of the IdeBuffer.
   */
  gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (state->buffer), &iter);
  end = iter;
  gtk_text_iter_forward_chars (&end, 1024);
  guess_contents = gtk_text_iter_get_slice (&iter, &end);
  path = ide_file_get_path (state->file);
  content_type = g_content_type_guess (path, (const guchar *)guess_contents,
                                       strlen (guess_contents), &uncertain);
  if (content_type && !uncertain)
    _ide_file_set_content_type (state->file, content_type);

513
emit_signal:
514
515
  _ide_buffer_set_loading (state->buffer, FALSE);

516
517
518
  if (!_ide_context_is_restoring (context))
    ide_buffer_manager_set_focus_buffer (self, state->buffer);

519
520
521
522
523
  g_signal_emit (self, gSignals [BUFFER_LOADED], 0, state->buffer);

  g_task_return_pointer (task, g_object_ref (state->buffer), g_object_unref);
}

524
static void
525
526
527
ide_buffer_manager__load_file_query_info_cb (GObject      *object,
                                             GAsyncResult *result,
                                             gpointer      user_data)
528
529
530
531
{
  IdeBufferManager *self;
  GFile *file = (GFile *)object;
  g_autoptr(GTask) task = user_data;
532
  g_autoptr(GFileInfo) file_info = NULL;
533
534
  LoadState *state;
  GError *error = NULL;
535
536
537
  gsize size = 0;

  IDE_ENTRY;
538
539
540
541
542
543
544
545
546
547
548

  g_assert (G_IS_FILE (file));
  g_assert (G_IS_TASK (task));

  state = g_task_get_task_data (task);
  self = g_task_get_source_object (task);

  g_assert (state);
  g_assert (IDE_IS_BUFFER (state->buffer));
  g_assert (IDE_IS_BUFFER_MANAGER (self));

549
  file_info = g_file_query_info_finish (file, result, &error);
550

551
  if (!file_info)
552
    {
553
554
555
556
557
558
559
560
561
562
      if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
        {
          _ide_buffer_set_loading (state->buffer, FALSE);
          g_task_return_error (task, error);
          IDE_EXIT;
        }
    }
  else
    {
      size = g_file_info_get_attribute_uint64 (file_info, G_FILE_ATTRIBUTE_STANDARD_SIZE);
563
    }
564
565
566
567
568
569
570

  if ((self->max_file_size > 0) && (size > self->max_file_size))
    {
      g_task_return_new_error (task,
                               G_IO_ERROR,
                               G_IO_ERROR_INVALID_DATA,
                               _("File too large to be opened."));
571
      IDE_EXIT;
572
    }
573

Christian Hergert's avatar
Christian Hergert committed
574
  if (file_info && g_file_info_has_attribute (file_info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE))
575
576
577
578
579
580
581
582
    {
      gboolean read_only;

      read_only = !g_file_info_get_attribute_boolean (file_info,
                                                      G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE);
      _ide_buffer_set_read_only (state->buffer, read_only);
    }

Christian Hergert's avatar
Christian Hergert committed
583
  if (file_info && g_file_info_has_attribute (file_info, G_FILE_ATTRIBUTE_TIME_MODIFIED))
584
585
586
587
588
589
590
    {
      GTimeVal tv;

      g_file_info_get_modification_time (file_info, &tv);
      _ide_buffer_set_mtime (state->buffer, &tv);
    }

591
592
  g_signal_emit (self, gSignals [LOAD_BUFFER], 0, state->buffer);

593
  gtk_source_file_loader_load_async (state->loader,
594
595
596
597
598
599
600
                                     G_PRIORITY_DEFAULT,
                                     g_task_get_cancellable (task),
                                     ide_progress_file_progress_callback,
                                     g_object_ref (state->progress),
                                     g_object_unref,
                                     ide_buffer_manager_load_file__load_cb,
                                     g_object_ref (task));
601
602

  IDE_EXIT;
603
604
605
606
607
608
609
610
611
612
613
614
}

static void
ide_buffer_manager__load_file_read_cb (GObject      *object,
                                       GAsyncResult *result,
                                       gpointer      user_data)
{
  GFile *file = (GFile *)object;
  g_autoptr(GFileInputStream) stream = NULL;
  g_autoptr(GTask) task = user_data;
  GtkSourceFile *source_file;
  LoadState *state;
615
616

  IDE_ENTRY;
617
618
619

  g_assert (G_IS_FILE (file));
  g_assert (G_IS_TASK (task));
620

621
622
623
624
625
  state = g_task_get_task_data (task);

  g_assert (state);
  g_assert (IDE_IS_BUFFER (state->buffer));

626
  source_file = _ide_file_get_source_file (state->file);
627

628
  stream = g_file_read_finish (file, result, NULL);
629

630
631
632
633
634
635
  if (stream)
    state->loader = gtk_source_file_loader_new_from_stream (GTK_SOURCE_BUFFER (state->buffer),
                                                            source_file,
                                                            G_INPUT_STREAM (stream));
  else
    state->loader = gtk_source_file_loader_new (GTK_SOURCE_BUFFER (state->buffer), source_file);
636
637

  g_file_query_info_async (file,
638
639
640
                           G_FILE_ATTRIBUTE_STANDARD_SIZE","
                           G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE","
                           G_FILE_ATTRIBUTE_TIME_MODIFIED,
641
642
643
644
645
                           G_FILE_QUERY_INFO_NONE,
                           G_PRIORITY_DEFAULT,
                           g_task_get_cancellable (task),
                           ide_buffer_manager__load_file_query_info_cb,
                           g_object_ref (task));
646
647

  IDE_EXIT;
648
649
}

650
651
652
653
654
655
656
/**
 * ide_buffer_manager_load_file_async:
 * @progress: (out) (nullable): A location for an #IdeProgress or %NULL.
 *
 * Asynchronously requests that the file represented by @file is loaded. If the file is already
 * loaded, the previously loaded version of the file will be returned, asynchronously.
 *
657
658
659
660
 * Before loading the file, #IdeBufferManager will check the file size to help protect itself
 * from the user accidentally loading very large files. You can change the maximum size of file
 * that will be loaded with the #IdeBufferManager:max-file-size property.
 *
661
662
663
 * See ide_buffer_manager_load_file_finish() for how to complete this asynchronous request.
 */
void
Christian Hergert's avatar
Christian Hergert committed
664
665
666
667
668
669
670
ide_buffer_manager_load_file_async (IdeBufferManager     *self,
                                    IdeFile              *file,
                                    gboolean              force_reload,
                                    IdeProgress         **progress,
                                    GCancellable         *cancellable,
                                    GAsyncReadyCallback   callback,
                                    gpointer              user_data)
671
672
673
674
675
{
  g_autoptr(GTask) task = NULL;
  IdeContext *context;
  IdeBuffer *buffer;
  LoadState *state;
676
  GFile *gfile;
677

678
679
  IDE_ENTRY;

680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
  if (progress)
    *progress = NULL;

  g_return_if_fail (IDE_IS_BUFFER_MANAGER (self));
  g_return_if_fail (IDE_IS_FILE (file));
  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));

  task = g_task_new (self, cancellable, callback, user_data);

  context = ide_object_get_context (IDE_OBJECT (self));
  buffer = ide_buffer_manager_get_buffer (self, file);

  /*
   * If the buffer is already loaded, then we can complete the request immediately.
   */
  if (buffer && !force_reload)
    {
      if (progress)
        *progress = g_object_new (IDE_TYPE_PROGRESS,
                                  "fraction", 1.0,
                                  NULL);
      g_task_return_pointer (task, g_object_ref (buffer), g_object_unref);
702
      g_signal_emit (self, gSignals [LOAD_BUFFER], 0, buffer);
703
      ide_buffer_manager_set_focus_buffer (self, buffer);
704
      IDE_EXIT;
705
706
707
    }

  state = g_slice_new0 (LoadState);
708
  state->is_new = (buffer == NULL);
709
  state->file = g_object_ref (file);
710
711
  state->progress = ide_progress_new ();

712
  if (buffer)
Christian Hergert's avatar
wip    
Christian Hergert committed
713
714
715
    {
      state->buffer = g_object_ref (buffer);
    }
716
  else
Christian Hergert's avatar
wip    
Christian Hergert committed
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
    {
      /*
       * Allow application to specify the buffer instance which may be a
       * decendent of IdeBuffer.
       */
      g_signal_emit (self, gSignals [CREATE_BUFFER], 0, file, &state->buffer);

      if ((state->buffer != NULL) && !IDE_IS_BUFFER (state->buffer))
        {
          g_warning ("Invalid buffer type retrieved from create-buffer signal.");
          state->buffer = NULL;
        }

      if (state->buffer == NULL)
        state->buffer = g_object_new (IDE_TYPE_BUFFER,
                                      "context", context,
                                      "file", file,
                                      NULL);
    }
736

737
738
  _ide_buffer_set_mtime (state->buffer, NULL);
  _ide_buffer_set_changed_on_volume (state->buffer, FALSE);
739
740
  _ide_buffer_set_loading (state->buffer, TRUE);

741
742
743
744
745
  g_task_set_task_data (task, state, load_state_free);

  if (progress)
    *progress = g_object_ref (state->progress);

746
  gfile = ide_file_get_file (file);
747

748
749
750
751
752
  g_file_read_async (gfile,
                     G_PRIORITY_DEFAULT,
                     cancellable,
                     ide_buffer_manager__load_file_read_cb,
                     g_object_ref (task));
753
754

  IDE_EXIT;
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
}

/**
 * ide_buffer_manager_load_file_finish:
 *
 * Completes an asynchronous request to load a file via ide_buffer_manager_load_file_async().
 * If the buffer was already loaded, this function will return a reference to the previous buffer
 * with it's reference count incremented by one.
 *
 * Returns: (transfer full): An #IdeBuffer if successf; otherwise %NULL and @error is set.
 */
IdeBuffer *
ide_buffer_manager_load_file_finish (IdeBufferManager  *self,
                                     GAsyncResult      *result,
                                     GError           **error)
{
  GTask *task = (GTask *)result;
772
773
774
  IdeBuffer *ret;

  IDE_ENTRY;
775
776
777
778

  g_return_val_if_fail (IDE_IS_BUFFER_MANAGER (self), NULL);
  g_return_val_if_fail (G_IS_TASK (task), NULL);

779
780
781
  ret = g_task_propagate_pointer (task, error);

  IDE_RETURN (ret);
782
783
784
785
786
787
788
789
790
791
}

static void
ide_buffer_manager_save_file__save_cb (GObject      *object,
                                       GAsyncResult *result,
                                       gpointer      user_data)
{
  g_autoptr(GTask) task = user_data;
  GtkSourceFileSaver *saver = (GtkSourceFileSaver *)object;
  IdeBufferManager *self;
792
793
  IdeUnsavedFiles *unsaved_files;
  IdeContext *context;
Christian Hergert's avatar
wip    
Christian Hergert committed
794
  IdeFile *file;
795
  GFile *gfile;
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
  SaveState *state;
  GError *error = NULL;

  g_assert (GTK_SOURCE_IS_FILE_SAVER (saver));
  g_assert (G_IS_TASK (task));

  self = g_task_get_source_object (task);
  state = g_task_get_task_data (task);

  g_assert (IDE_IS_BUFFER_MANAGER (self));
  g_assert (state);
  g_assert (IDE_IS_BUFFER (state->buffer));
  g_assert (IDE_IS_FILE (state->file));
  g_assert (IDE_IS_PROGRESS (state->progress));

  /* Complete the save request */
  if (!gtk_source_file_saver_save_finish (saver, result, &error))
    {
      g_task_return_error (task, error);
      return;
    }

Christian Hergert's avatar
wip    
Christian Hergert committed
818
819
820
821
822
  /* Make the buffer as not modified if we saved it to the backing file */
  file = ide_buffer_get_file (state->buffer);
  if (ide_file_equal (file, state->file))
    gtk_text_buffer_set_modified (GTK_TEXT_BUFFER (state->buffer), FALSE);

823
824
825
826
  /* remove the unsaved files state */
  context = ide_object_get_context (IDE_OBJECT (self));
  unsaved_files = ide_context_get_unsaved_files (context);
  gfile = ide_file_get_file (state->file);
827
  ide_unsaved_files_remove (unsaved_files, gfile);
828

829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
  /* Notify signal handlers that the file is saved */
  g_signal_emit (self, gSignals [BUFFER_SAVED], 0, state->buffer);

  g_task_return_boolean (task, TRUE);
}

static void
ide_buffer_manager_save_file__load_settings_cb (GObject      *object,
                                                GAsyncResult *result,
                                                gpointer      user_data)
{
  IdeFile *file = (IdeFile *)object;
  g_autoptr(IdeFileSettings) file_settings = NULL;
  g_autoptr(GTask) task = user_data;
  SaveState *state;
  GtkSourceFileSaver *saver;
  GtkSourceFile *source_file;
  GtkSourceNewlineType newline_type;
  const GtkSourceEncoding *encoding;
  const gchar *charset;
  GError *error = NULL;

851
852
  IDE_ENTRY;

853
854
855
856
857
858
859
860
  g_assert (IDE_IS_FILE (file));
  g_assert (G_IS_TASK (task));

  file_settings = ide_file_load_settings_finish (file, result, &error);

  if (!file_settings)
    {
      g_task_return_error (task, error);
861
      IDE_EXIT;
862
863
864
865
866
867
868
869
870
871
872
    }

  source_file = _ide_file_get_source_file (file);

  state = g_task_get_task_data (task);

  g_assert (GTK_SOURCE_IS_FILE (source_file));
  g_assert (IDE_IS_BUFFER (state->buffer));
  g_assert (IDE_IS_FILE (state->file));
  g_assert (IDE_IS_PROGRESS (state->progress));

873
874
875
  if (!gtk_source_file_get_location (source_file))
    gtk_source_file_set_location (source_file, ide_file_get_file (file));

876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
  saver = gtk_source_file_saver_new (GTK_SOURCE_BUFFER (state->buffer), source_file);

  /* set file encoding and newline style defaults */
  newline_type = ide_file_settings_get_newline_type (file_settings);
  encoding = gtk_source_encoding_get_utf8 ();

  if ((charset = ide_file_settings_get_encoding (file_settings)))
    {
      encoding = gtk_source_encoding_get_from_charset (charset);
      if (!encoding)
        encoding = gtk_source_encoding_get_utf8 ();
    }

  /*
   * If we are performing a save-as operation, overwrite the defaults to match what was used
   * in the original source file.
   */
  if (!ide_file_equal (file, ide_buffer_get_file (state->buffer)))
    {
      IdeFile *orig_file = ide_buffer_get_file (state->buffer);

      if (orig_file)
        {
          source_file = _ide_file_get_source_file (orig_file);

          if (source_file)
            {
              encoding = gtk_source_file_get_encoding (source_file);
              newline_type = gtk_source_file_get_newline_type (source_file);
            }
        }
    }

909
910
911
912
913
914
915
916
  /*
   * If file-settings dictate that we should trim trailing whitespace, trim it from the modified
   * lines in the IdeBuffer. This is performed automatically based on line state within
   * ide_buffer_trim_trailing_whitespace().
   */
  if (ide_file_settings_get_trim_trailing_whitespace (file_settings))
    ide_buffer_trim_trailing_whitespace (state->buffer);

917
918
919
  gtk_source_file_saver_set_encoding (saver, encoding);
  gtk_source_file_saver_set_newline_type (saver, newline_type);

920
921
  _ide_buffer_set_mtime (state->buffer, NULL);

922
923
924
925
926
927
928
929
930
931
  gtk_source_file_saver_save_async (saver,
                                    G_PRIORITY_DEFAULT,
                                    g_task_get_cancellable (task),
                                    ide_progress_file_progress_callback,
                                    g_object_ref (state->progress),
                                    g_object_unref,
                                    ide_buffer_manager_save_file__save_cb,
                                    g_object_ref (task));

  g_clear_object (&saver);
932
933

  IDE_EXIT;
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
}

/**
 * ide_buffer_manager_save_file_async:
 *
 * This function asynchronously requests that a buffer be saved to the storage represented by
 * @file. @buffer should be a previously loaded buffer owned by @self, such as one loaded with
 * ide_buffer_manager_load_file_async().
 *
 * Call ide_buffer_manager_save_file_finish() to complete this asynchronous request.
 */
void
ide_buffer_manager_save_file_async  (IdeBufferManager     *self,
                                     IdeBuffer            *buffer,
                                     IdeFile              *file,
                                     IdeProgress         **progress,
                                     GCancellable         *cancellable,
                                     GAsyncReadyCallback   callback,
                                     gpointer              user_data)
{
  g_autoptr(GTask) task = NULL;
  SaveState *state;

  if (progress)
    *progress = NULL;

  g_return_if_fail (IDE_IS_BUFFER_MANAGER (self));
  g_return_if_fail (IDE_IS_BUFFER (buffer));
  g_return_if_fail (IDE_IS_FILE (file));
  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));

  task = g_task_new (self, cancellable, callback, user_data);

  state = g_slice_new0 (SaveState);
  state->file = g_object_ref (file);
  state->buffer = g_object_ref (buffer);
970
  state->progress = ide_progress_new ();
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000

  g_task_set_task_data (task, state, save_state_free);

  g_signal_emit (self, gSignals [SAVE_BUFFER], 0, buffer);

  if (progress)
    *progress = g_object_ref (state->progress);

  /*
   * First, we need to asynchronously load the file settings. The IdeFileSettings contains the
   * target encoding (utf-8, etc) as well as the newline style (\r\n vs \r vs \n). If the
   * file settings do not dictate an encoding, the encoding used to load the buffer will be used.
   */
  ide_file_load_settings_async (file,
                                cancellable,
                                ide_buffer_manager_save_file__load_settings_cb,
                                g_object_ref (task));
}

/**
 * ide_buffer_manager_save_file_finish:
 *
 * This function completes an asynchronous request to save a buffer to storage using
 * ide_buffer_manager_save_file_async(). Upon failure, %FALSE is returned and @error is set.
 *
 * Returns: %TRUE if successful %FALSE upon failure and @error is set.
 */
gboolean
ide_buffer_manager_save_file_finish (IdeBufferManager  *self,
                                     GAsyncResult      *result,
For faster browsing, not all history is shown. View entire blame