[gnome-builder] libide: add IdeBuffer and IdeBufferManager



commit 2fd195b2a786e54fc4227c2684c63f0e72a145f4
Author: Christian Hergert <christian hergert me>
Date:   Mon Feb 23 16:35:48 2015 -0800

    libide: add IdeBuffer and IdeBufferManager
    
    First off, IdeBuffer is missing a lot that we need to bring down from
    GbEditorDocument. However, it is in place so we can test the
    IdeBufferManager abstraction.
    
    Using scripts, you can hook load/save operations before and after using
    the signals:
    
     "load-buffer"
     "buffer-loaded"
     "save-buffer"
     "buffer-saved"

 libide/Makefile.am          |    4 +
 libide/ide-buffer-manager.c |  980 +++++++++++++++++++++++++++++++++++++++++++
 libide/ide-buffer-manager.h |   58 +++
 libide/ide-buffer.c         |  197 +++++++++
 libide/ide-buffer.h         |   48 +++
 libide/ide-types.h          |    4 +
 libide/ide.h                |    2 +
 po/POTFILES.in              |    2 +
 8 files changed, 1295 insertions(+), 0 deletions(-)
---
diff --git a/libide/Makefile.am b/libide/Makefile.am
index 7552361..e4f6aef 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -42,6 +42,10 @@ libide_1_0_la_public_sources = \
        libide/ide-back-forward-item.h \
        libide/ide-back-forward-list.c \
        libide/ide-back-forward-list.h \
+       libide/ide-buffer-manager.c \
+       libide/ide-buffer-manager.h \
+       libide/ide-buffer.c \
+       libide/ide-buffer.h \
        libide/ide-build-result.c \
        libide/ide-build-result.h \
        libide/ide-build-system.c \
diff --git a/libide/ide-buffer-manager.c b/libide/ide-buffer-manager.c
new file mode 100644
index 0000000..711463b
--- /dev/null
+++ b/libide/ide-buffer-manager.c
@@ -0,0 +1,980 @@
+/* ide-buffer-manager.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This file 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
+ * Lesser General Public License for more details.
+ *
+ * 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>
+
+#include "ide-buffer.h"
+#include "ide-buffer-manager.h"
+#include "ide-file.h"
+#include "ide-file-settings.h"
+#include "ide-internal.h"
+#include "ide-progress.h"
+
+#define AUTO_SAVE_TIMEOUT_DEFAULT 60
+
+struct _IdeBufferManager
+{
+  IdeObject    parent_instance;
+
+  GPtrArray   *buffers;
+  GHashTable  *timeouts;
+
+  IdeBuffer   *focus_buffer;
+
+  guint        auto_save_timeout;
+  guint        auto_save : 1;
+};
+
+typedef struct
+{
+  IdeBufferManager *self;
+  IdeBuffer        *buffer;
+  guint             source_id;
+} AutoSave;
+
+typedef struct
+{
+  IdeBuffer   *buffer;
+  IdeFile     *file;
+  IdeProgress *progress;
+} 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,
+  LAST_PROP
+};
+
+enum {
+  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);
+      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);
+    }
+}
+
+static void
+ide_buffer_manager_save_async (IdeBufferManager    *self,
+                               IdeBuffer           *buffer,
+                               GCancellable        *cancellable,
+                               GAsyncReadyCallback  callback,
+                               gpointer             user_data)
+{
+}
+
+static gboolean
+ide_buffer_manager_auto_save_cb (gpointer data)
+{
+  AutoSave *state = data;
+
+  g_return_val_if_fail (state, G_SOURCE_REMOVE);
+  g_return_val_if_fail (IDE_IS_BUFFER_MANAGER (state->self), G_SOURCE_REMOVE);
+  g_return_val_if_fail (IDE_IS_BUFFER (state->buffer), G_SOURCE_REMOVE);
+  g_return_val_if_fail (state->source_id > 0, G_SOURCE_REMOVE);
+
+  ide_buffer_manager_save_async (state->self, state->buffer, NULL, NULL, NULL);
+  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);
+
+  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)
+{
+  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))
+    {
+      unregister_auto_save (self, buffer);
+      g_signal_handlers_disconnect_by_func (buffer,
+                                            G_CALLBACK (ide_buffer_manager_buffer_changed),
+                                            self);
+      g_object_unref (buffer);
+    }
+}
+
+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;
+  GtkSourceFileLoader *loader = (GtkSourceFileLoader *)object;
+  IdeBufferManager *self;
+  LoadState *state;
+  GError *error = NULL;
+
+  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));
+
+  if (!gtk_source_file_loader_load_finish (loader, result, &error))
+    {
+      g_task_return_error (task, error);
+      return;
+    }
+
+  g_signal_emit (self, gSignals [BUFFER_LOADED], 0, state->buffer);
+
+  g_task_return_pointer (task, g_object_ref (state->buffer), g_object_unref);
+}
+
+/**
+ * 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.
+ *
+ * See ide_buffer_manager_load_file_finish() for how to complete this asynchronous request.
+ */
+void
+ide_buffer_manager_load_file_async  (IdeBufferManager     *self,
+                                     IdeFile              *file,
+                                     gboolean              force_reload,
+                                     IdeProgress         **progress,
+                                     GCancellable         *cancellable,
+                                     GAsyncReadyCallback   callback,
+                                     gpointer              user_data)
+{
+  g_autoptr(GTask) task = NULL;
+  IdeContext *context;
+  IdeBuffer *buffer;
+  LoadState *state;
+  GtkSourceFileLoader *loader;
+  GtkSourceFile *source_file;
+
+  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,
+                                  "context", context,
+                                  "fraction", 1.0,
+                                  NULL);
+      g_task_return_pointer (task, g_object_ref (buffer), g_object_unref);
+      return;
+    }
+
+  state = g_slice_new0 (LoadState);
+  state->file = g_object_ref (file);
+  state->progress = g_object_new (IDE_TYPE_PROGRESS,
+                                  "context", context,
+                                  NULL);
+  if (buffer)
+    state->buffer = g_object_ref (buffer);
+  else
+    state->buffer = g_object_new (IDE_TYPE_BUFFER,
+                                  "context", context,
+                                  "file", file,
+                                  NULL);
+
+  g_task_set_task_data (task, state, load_state_free);
+
+  if (progress)
+    *progress = g_object_ref (state->progress);
+
+  source_file = _ide_file_get_source_file (file);
+  loader = gtk_source_file_loader_new (GTK_SOURCE_BUFFER (state->buffer), source_file);
+
+  g_signal_emit (self, gSignals [LOAD_BUFFER], 0, state->buffer);
+
+  gtk_source_file_loader_load_async (loader,
+                                     G_PRIORITY_DEFAULT,
+                                     cancellable,
+                                     ide_progress_file_progress_callback,
+                                     g_object_ref (state->progress),
+                                     g_object_unref,
+                                     ide_buffer_manager_load_file__load_cb,
+                                     g_object_ref (task));
+
+  g_clear_object (&loader);
+}
+
+/**
+ * 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;
+
+  g_return_val_if_fail (IDE_IS_BUFFER_MANAGER (self), NULL);
+  g_return_val_if_fail (G_IS_TASK (task), NULL);
+
+  return g_task_propagate_pointer (task, error);
+}
+
+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;
+  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;
+    }
+
+  /* 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;
+
+  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);
+      return;
+    }
+
+  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));
+
+  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);
+            }
+        }
+    }
+
+  gtk_source_file_saver_set_encoding (saver, encoding);
+  gtk_source_file_saver_set_newline_type (saver, newline_type);
+
+  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);
+}
+
+/**
+ * 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;
+  IdeContext *context;
+  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);
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+
+  state = g_slice_new0 (SaveState);
+  state->file = g_object_ref (file);
+  state->buffer = g_object_ref (buffer);
+  state->progress = g_object_new (IDE_TYPE_PROGRESS,
+                                  "context", context,
+                                  NULL);
+
+  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,
+                                     GError           **error)
+{
+  GTask *task = (GTask *)result;
+
+  g_return_val_if_fail (IDE_IS_BUFFER_MANAGER (self), FALSE);
+  g_return_val_if_fail (G_IS_TASK (task), FALSE);
+
+  return g_task_propagate_boolean (task, error);
+}
+
+static void
+ide_buffer_manager_dispose (GObject *object)
+{
+  IdeBufferManager *self = (IdeBufferManager *)object;
+
+  while (self->buffers->len)
+    {
+      IdeBuffer *buffer;
+
+      buffer = g_ptr_array_index (self->buffers, 0);
+      ide_buffer_manager_remove_buffer (self, buffer);
+    }
+
+  G_OBJECT_CLASS (ide_buffer_manager_parent_class)->dispose (object);
+}
+
+static void
+ide_buffer_manager_finalize (GObject *object)
+{
+  IdeBufferManager *self = (IdeBufferManager *)object;
+
+  if (g_hash_table_size (self->timeouts))
+    g_warning ("Not all auto save timeouts have been removed.");
+
+  if (self->buffers->len)
+    g_warning ("Not all buffers have been destroyed.");
+
+  g_clear_pointer (&self->buffers, g_ptr_array_unref);
+  g_clear_pointer (&self->timeouts, g_hash_table_unref);
+
+  G_OBJECT_CLASS (ide_buffer_manager_parent_class)->finalize (object);
+}
+
+static void
+ide_buffer_manager_get_property (GObject    *object,
+                                 guint       prop_id,
+                                 GValue     *value,
+                                 GParamSpec *pspec)
+{
+  IdeBufferManager *self = IDE_BUFFER_MANAGER (object);
+
+  switch (prop_id)
+    {
+    case PROP_AUTO_SAVE:
+      g_value_set_boolean (value, ide_buffer_manager_get_auto_save (self));
+      break;
+
+    case PROP_AUTO_SAVE_TIMEOUT:
+      g_value_set_uint (value, ide_buffer_manager_get_auto_save_timeout (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_buffer_manager_set_property (GObject      *object,
+                                 guint         prop_id,
+                                 const GValue *value,
+                                 GParamSpec   *pspec)
+{
+  IdeBufferManager *self = IDE_BUFFER_MANAGER (object);
+
+  switch (prop_id)
+    {
+    case PROP_AUTO_SAVE:
+      ide_buffer_manager_set_auto_save (self, g_value_get_boolean (value));
+      break;
+
+    case PROP_AUTO_SAVE_TIMEOUT:
+      ide_buffer_manager_set_auto_save_timeout (self, g_value_get_uint (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_buffer_manager_class_init (IdeBufferManagerClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = ide_buffer_manager_dispose;
+  object_class->finalize = ide_buffer_manager_finalize;
+  object_class->get_property = ide_buffer_manager_get_property;
+  object_class->set_property = ide_buffer_manager_set_property;
+
+  gParamSpecs [PROP_AUTO_SAVE] =
+    g_param_spec_boolean ("auto-save",
+                          _("Auto Save"),
+                          _("If the documents should auto save after a configured timeout."),
+                          TRUE,
+                          (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_AUTO_SAVE,
+                                   gParamSpecs [PROP_AUTO_SAVE]);
+
+  gParamSpecs [PROP_AUTO_SAVE_TIMEOUT] =
+    g_param_spec_uint ("auto-save-timeout",
+                       _("Auto Save Timeout"),
+                       _("The number of seconds after modification before auto saving."),
+                       0,
+                       G_MAXUINT,
+                       AUTO_SAVE_TIMEOUT_DEFAULT,
+                       (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_AUTO_SAVE_TIMEOUT,
+                                   gParamSpecs [PROP_AUTO_SAVE_TIMEOUT]);
+
+  /**
+   * IdeBufferManager::save-buffer:
+   * @self: An #IdeBufferManager.
+   * @buffer: an #IdeBuffer.
+   *
+   * This signal is emitted when a request has been made to save a buffer. Connect to this signal
+   * if you'd like to perform mutation of the buffer before it is persisted to storage.
+   */
+  gSignals [SAVE_BUFFER] = g_signal_new ("save-buffer",
+                                         G_TYPE_FROM_CLASS (object_class),
+                                         G_SIGNAL_RUN_LAST,
+                                         0,
+                                         NULL, NULL,
+                                         g_cclosure_marshal_generic,
+                                         G_TYPE_NONE,
+                                         1,
+                                         IDE_TYPE_BUFFER);
+
+  /**
+   * IdeBufferManager::buffer-saved:
+   * @self: An #IdeBufferManager.
+   * @buffer: an #IdeBuffer.
+   *
+   * This signal is emitted when a buffer has finished saving to storage. You might connect to
+   * this signal if you want to know when the modifications have successfully been written to
+   * storage.
+   */
+  gSignals [BUFFER_SAVED] = g_signal_new ("buffer-saved",
+                                          G_TYPE_FROM_CLASS (object_class),
+                                          G_SIGNAL_RUN_LAST,
+                                          0,
+                                          NULL, NULL,
+                                          g_cclosure_marshal_generic,
+                                          G_TYPE_NONE,
+                                          1,
+                                          IDE_TYPE_BUFFER);
+
+  /**
+   * IdeBufferManager::load-buffer:
+   * @self: An #IdeBufferManager.
+   * @buffer: an #IdeBuffer.
+   *
+   * This signal is emitted when a request has been made to load a buffer from storage. You might
+   * connect to this signal to be notified when loading of a buffer has begun.
+   */
+  gSignals [LOAD_BUFFER] = g_signal_new ("load-buffer",
+                                         G_TYPE_FROM_CLASS (object_class),
+                                         G_SIGNAL_RUN_LAST,
+                                         0,
+                                         NULL, NULL,
+                                         g_cclosure_marshal_generic,
+                                         G_TYPE_NONE,
+                                         1,
+                                         IDE_TYPE_BUFFER);
+
+  /**
+   * IdeBufferManager::buffer-loaded:
+   * @self: An #IdeBufferManager.
+   * @buffer: an #IdeBuffer.
+   *
+   * This signal is emitted when a buffer has been successfully loaded. You might connect to this
+   * signal to be notified when a buffer has completed loading.
+   */
+  gSignals [BUFFER_LOADED] = g_signal_new ("buffer-loaded",
+                                           G_TYPE_FROM_CLASS (object_class),
+                                           G_SIGNAL_RUN_LAST,
+                                           0,
+                                           NULL, NULL,
+                                           g_cclosure_marshal_generic,
+                                           G_TYPE_NONE,
+                                           1,
+                                           IDE_TYPE_BUFFER);
+
+  /**
+   * IdeBufferManager::buffer-focus-enter:
+   * @self: An #IdeBufferManager.
+   * @buffer: an #IdeBuffer.
+   *
+   * This signal is emitted when a view for @buffer has received focus. You might connect to this
+   * signal when you want to perform an operation while a buffer is in focus.
+   */
+  gSignals [BUFFER_FOCUS_ENTER] = g_signal_new ("buffer-focus-enter",
+                                                G_TYPE_FROM_CLASS (object_class),
+                                                G_SIGNAL_RUN_LAST,
+                                                0,
+                                                NULL, NULL,
+                                                g_cclosure_marshal_generic,
+                                                G_TYPE_NONE,
+                                                1,
+                                                IDE_TYPE_BUFFER);
+
+  /**
+   * IdeBufferManager::buffer-focus-leave:
+   * @self: An #IdeBufferManager.
+   * @buffer: an #IdeBuffer.
+   *
+   * This signal is emitted when the focus has left the view containing @buffer. You might connect
+   * to this signal to stop any work you were performing while the buffer was focused.
+   */
+  gSignals [BUFFER_FOCUS_LEAVE] = g_signal_new ("buffer-focus-leave",
+                                                G_TYPE_FROM_CLASS (object_class),
+                                                G_SIGNAL_RUN_LAST,
+                                                0,
+                                                NULL, NULL,
+                                                g_cclosure_marshal_generic,
+                                                G_TYPE_NONE,
+                                                1,
+                                                IDE_TYPE_BUFFER);
+}
+
+static void
+ide_buffer_manager_init (IdeBufferManager *self)
+{
+  self->auto_save = TRUE;
+  self->auto_save_timeout = AUTO_SAVE_TIMEOUT_DEFAULT;
+  self->buffers = g_ptr_array_new ();
+  self->timeouts = g_hash_table_new (g_direct_hash, g_direct_equal);
+}
+
+static void
+register_auto_save (IdeBufferManager *self,
+                    IdeBuffer        *buffer)
+{
+  g_return_if_fail (IDE_IS_BUFFER_MANAGER (self));
+  g_return_if_fail (IDE_IS_BUFFER (buffer));
+  g_return_if_fail (!g_hash_table_lookup (self->timeouts, buffer));
+  g_return_if_fail (self->auto_save_timeout > 0);
+
+  if (gtk_text_buffer_get_modified (GTK_TEXT_BUFFER (buffer)))
+    {
+      AutoSave *state;
+
+      state = g_slice_new0 (AutoSave);
+      ide_set_weak_pointer (&state->buffer, buffer);
+      ide_set_weak_pointer (&state->self, self);
+      state->source_id = g_timeout_add_seconds (self->auto_save_timeout,
+                                                ide_buffer_manager_auto_save_cb,
+                                                state);
+      g_hash_table_insert (self->timeouts, buffer, state);
+    }
+}
+
+static void
+unregister_auto_save (IdeBufferManager *self,
+                      IdeBuffer        *buffer)
+{
+  AutoSave *state;
+
+  g_assert (IDE_IS_BUFFER_MANAGER (self));
+  g_assert (IDE_IS_BUFFER (buffer));
+
+  state = g_hash_table_lookup (self->timeouts, buffer);
+
+  if (state != NULL)
+    {
+      g_hash_table_remove (self->timeouts, buffer);
+      if (state->source_id > 0)
+        g_source_remove (state->source_id);
+      g_object_unref (state->buffer);
+      ide_clear_weak_pointer (&state->self);
+      g_slice_free (AutoSave, state);
+    }
+}
diff --git a/libide/ide-buffer-manager.h b/libide/ide-buffer-manager.h
new file mode 100644
index 0000000..bdf0655
--- /dev/null
+++ b/libide/ide-buffer-manager.h
@@ -0,0 +1,58 @@
+/* ide-buffer-manager.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This file 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_BUFFER_MANAGER_H
+#define IDE_BUFFER_MANAGER_H
+
+#include <gtk/gtk.h>
+
+#include "ide-object.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_BUFFER_MANAGER (ide_buffer_manager_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeBufferManager, ide_buffer_manager, IDE, BUFFER_MANAGER, IdeObject)
+
+void       ide_buffer_manager_load_file_async  (IdeBufferManager     *self,
+                                                IdeFile              *file,
+                                                gboolean              force_reload,
+                                                IdeProgress         **progress,
+                                                GCancellable         *cancellable,
+                                                GAsyncReadyCallback   callback,
+                                                gpointer              user_data);
+IdeBuffer *ide_buffer_manager_load_file_finish (IdeBufferManager     *self,
+                                                GAsyncResult         *result,
+                                                GError              **error);
+void       ide_buffer_manager_save_file_async  (IdeBufferManager     *self,
+                                                IdeBuffer            *buffer,
+                                                IdeFile              *file,
+                                                IdeProgress         **progress,
+                                                GCancellable         *cancellable,
+                                                GAsyncReadyCallback   callback,
+                                                gpointer              user_data);
+gboolean   ide_buffer_manager_save_file_finish (IdeBufferManager     *self,
+                                                GAsyncResult         *result,
+                                                GError              **error);
+IdeBuffer *ide_buffer_manager_get_focus_buffer (IdeBufferManager     *self);
+void       ide_buffer_manager_set_focus_buffer (IdeBufferManager     *self,
+                                                IdeBuffer            *buffer);
+
+G_END_DECLS
+
+#endif /* IDE_BUFFER_MANAGER_H */
diff --git a/libide/ide-buffer.c b/libide/ide-buffer.c
new file mode 100644
index 0000000..cc49af5
--- /dev/null
+++ b/libide/ide-buffer.c
@@ -0,0 +1,197 @@
+/* ide-buffer.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This file 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
+ * Lesser General Public License for more details.
+ *
+ * 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"
+
+#include <glib/gi18n.h>
+
+#include "ide-buffer.h"
+#include "ide-context.h"
+#include "ide-file.h"
+
+struct _IdeBufferClass
+{
+  GtkSourceBufferClass parent_class;
+};
+
+struct _IdeBuffer
+{
+  GtkSourceBuffer  parent_instance;
+
+  IdeContext      *context;
+  IdeFile         *file;
+};
+
+G_DEFINE_TYPE (IdeBuffer, ide_buffer, GTK_SOURCE_TYPE_BUFFER)
+
+enum {
+  PROP_0,
+  PROP_CONTEXT,
+  PROP_FILE,
+  LAST_PROP
+};
+
+static GParamSpec *gParamSpecs [LAST_PROP];
+
+/**
+ * ide_buffer_get_context:
+ *
+ * Gets the #IdeBuffer:context property. This is the #IdeContext that owns the buffer.
+ *
+ * Returns: (transfer none): An #IdeContext.
+ */
+IdeContext *
+ide_buffer_get_context (IdeBuffer *self)
+{
+  g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
+
+  return self->context;
+}
+
+static void
+ide_buffer_set_context (IdeBuffer  *self,
+                        IdeContext *context)
+{
+  g_return_if_fail (IDE_IS_BUFFER (self));
+  g_return_if_fail (IDE_IS_CONTEXT (context));
+  g_return_if_fail (self->context == NULL);
+
+  self->context = g_object_ref (context);
+}
+
+/**
+ * ide_buffer_get_file:
+ *
+ * Gets the underlying file behind the buffer.
+ *
+ * Returns: (transfer none): An #IdeFile.
+ */
+IdeFile *
+ide_buffer_get_file (IdeBuffer *self)
+{
+  g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
+
+  return self->file;
+}
+
+/**
+ * ide_buffer_set_file:
+ *
+ * Sets the underlying file to use when saving and loading @self to and and from storage.
+ */
+void
+ide_buffer_set_file (IdeBuffer *self,
+                     IdeFile   *file)
+{
+  g_return_if_fail (IDE_IS_BUFFER (self));
+  g_return_if_fail (IDE_IS_FILE (file));
+
+  if (g_set_object (&self->file, file))
+    g_object_notify_by_pspec (G_OBJECT (self), gParamSpecs [PROP_FILE]);
+}
+
+static void
+ide_buffer_finalize (GObject *object)
+{
+  IdeBuffer *self = (IdeBuffer *)object;
+
+  g_clear_object (&self->context);
+  g_clear_object (&self->file);
+
+  G_OBJECT_CLASS (ide_buffer_parent_class)->finalize (object);
+}
+
+static void
+ide_buffer_get_property (GObject    *object,
+                         guint       prop_id,
+                         GValue     *value,
+                         GParamSpec *pspec)
+{
+  IdeBuffer *self = IDE_BUFFER (object);
+
+  switch (prop_id)
+    {
+    case PROP_CONTEXT:
+      g_value_set_object (value, ide_buffer_get_context (self));
+      break;
+
+    case PROP_FILE:
+      g_value_set_object (value, ide_buffer_get_file (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_buffer_set_property (GObject      *object,
+                         guint         prop_id,
+                         const GValue *value,
+                         GParamSpec   *pspec)
+{
+  IdeBuffer *self = IDE_BUFFER (object);
+
+  switch (prop_id)
+    {
+    case PROP_CONTEXT:
+      ide_buffer_set_context (self, g_value_get_object (value));
+      break;
+
+    case PROP_FILE:
+      ide_buffer_set_file (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_buffer_class_init (IdeBufferClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_buffer_finalize;
+  object_class->get_property = ide_buffer_get_property;
+  object_class->set_property = ide_buffer_set_property;
+
+  gParamSpecs [PROP_CONTEXT] =
+    g_param_spec_object ("context",
+                         _("Context"),
+                         _("The IdeContext for the buffer."),
+                         IDE_TYPE_CONTEXT,
+                         (G_PARAM_READWRITE |
+                          G_PARAM_CONSTRUCT_ONLY |
+                          G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_CONTEXT,
+                                   gParamSpecs [PROP_CONTEXT]);
+
+  gParamSpecs [PROP_FILE] =
+    g_param_spec_object ("file",
+                         _("File"),
+                         _("The file represented by the buffer."),
+                         IDE_TYPE_FILE,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+  g_object_class_install_property (object_class, PROP_FILE, gParamSpecs [PROP_FILE]);
+}
+
+static void
+ide_buffer_init (IdeBuffer *self)
+{
+}
diff --git a/libide/ide-buffer.h b/libide/ide-buffer.h
new file mode 100644
index 0000000..d6dafa6
--- /dev/null
+++ b/libide/ide-buffer.h
@@ -0,0 +1,48 @@
+/* ide-buffer.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This file 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_BUFFER_H
+#define IDE_BUFFER_H
+
+#include <gtksourceview/gtksourcebuffer.h>
+
+#include "ide-types.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_BUFFER             (ide_buffer_get_type ())
+#define IDE_BUFFER(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), IDE_TYPE_BUFFER, IdeBuffer))
+#define IDE_BUFFER_CONST(obj)       (G_TYPE_CHECK_INSTANCE_CAST ((obj), IDE_TYPE_BUFFER, IdeBuffer const))
+#define IDE_BUFFER_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass),  IDE_TYPE_BUFFER, IdeBufferClass))
+#define IDE_IS_BUFFER(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), IDE_TYPE_BUFFER))
+#define IDE_IS_BUFFER_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass),  IDE_TYPE_BUFFER))
+#define IDE_BUFFER_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj),  IDE_TYPE_BUFFER, IdeBufferClass))
+
+typedef struct _IdeBufferClass  IdeBufferClass;
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (IdeBuffer, g_object_unref)
+
+GType       ide_buffer_get_type    (void);
+IdeContext *ide_buffer_get_context (IdeBuffer *self);
+IdeFile    *ide_buffer_get_file    (IdeBuffer *self);
+void        ide_buffer_set_file    (IdeBuffer *self,
+                                    IdeFile   *file);
+
+G_END_DECLS
+
+#endif /* IDE_BUFFER_H */
diff --git a/libide/ide-types.h b/libide/ide-types.h
index 828d6ae..b132b44 100644
--- a/libide/ide-types.h
+++ b/libide/ide-types.h
@@ -33,6 +33,10 @@ typedef struct _IdeBackForwardItem             IdeBackForwardItem;
 
 typedef struct _IdeBackForwardList             IdeBackForwardList;
 
+typedef struct _IdeBuffer                      IdeBuffer;
+
+typedef struct _IdeBufferManager               IdeBufferManager;
+
 typedef struct _IdeBuilder                     IdeBuilder;
 
 typedef struct _IdeBuildResult                 IdeBuildResult;
diff --git a/libide/ide.h b/libide/ide.h
index 3b20443..c8e3ad4 100644
--- a/libide/ide.h
+++ b/libide/ide.h
@@ -30,6 +30,8 @@ G_BEGIN_DECLS
 #include "ide-build-result.h"
 #include "ide-build-system.h"
 #include "ide-builder.h"
+#include "ide-buffer.h"
+#include "ide-buffer-manager.h"
 #include "ide-context.h"
 #include "ide-debugger.h"
 #include "ide-deployer.h"
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 8399191..047ff79 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -28,6 +28,8 @@ libide/gjs/ide-gjs-script.cpp
 libide/gsettings/ide-gsettings-file-settings.c
 libide/ide-back-forward-item.c
 libide/ide-back-forward-list.c
+libide/ide-buffer-manager.c
+libide/ide-buffer.c
 libide/ide-build-result.c
 libide/ide-build-system.c
 libide/ide-builder.c



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]