[gnome-builder: 27/139] code: add new libide-code static library



commit ebf36737683deab5bf82e9d1a3b93a7f0618541e
Author: Christian Hergert <chergert redhat com>
Date:   Wed Jan 9 15:32:37 2019 -0800

    code: add new libide-code static library
    
    This new library depends on libide-core, libide-io, and others to create
    a static library containing utilities and types for managing code. It also
    includes the buffer manager since that is so intertwined with the other
    components contained herein.
    
    Symbols, Diagnostics, Trees, Locations, and Ranges have all been converted
    into GObject so that we have an easier time binding things in the future.

 src/libide/{files => code}/defaults.ini            |    0
 src/libide/code/ide-code-global.c                  |   44 +
 .../{symbols => code}/ide-code-index-entries.c     |   11 +-
 .../{symbols => code}/ide-code-index-entries.h     |   11 +-
 .../{symbols => code}/ide-code-index-entry.c       |    5 +-
 .../{symbols => code}/ide-code-index-entry.h       |   12 +-
 src/libide/{symbols => code}/ide-code-indexer.c    |   12 +-
 src/libide/{symbols => code}/ide-code-indexer.h    |   13 +-
 src/libide/code/ide-code-types.h                   |   60 +
 .../ide-diagnostic-provider.c                      |  105 +-
 .../ide-diagnostic-provider.h                      |   34 +-
 src/libide/code/ide-diagnostic.c                   |  748 ++++++++++++
 src/libide/{diagnostics => code}/ide-diagnostic.h  |   74 +-
 src/libide/code/ide-diagnostics-manager-private.h  |   41 +
 .../ide-diagnostics-manager.c                      |  688 ++++-------
 .../ide-diagnostics-manager.h                      |   12 +-
 src/libide/code/ide-diagnostics.c                  |  506 +++++++++
 src/libide/code/ide-diagnostics.h                  |   97 ++
 .../ide-doc-seq.h => code/ide-doc-seq-private.h}   |    2 +-
 src/libide/{util => code}/ide-doc-seq.c            |    8 +-
 src/libide/{files => code}/ide-file-settings.c     |  145 ++-
 src/libide/{files => code}/ide-file-settings.defs  |    0
 src/libide/{files => code}/ide-file-settings.h     |   27 +-
 .../{formatting => code}/ide-formatter-options.c   |    2 +-
 .../{formatting => code}/ide-formatter-options.h   |    6 +-
 src/libide/{formatting => code}/ide-formatter.c    |    5 +-
 src/libide/{formatting => code}/ide-formatter.h    |   10 +-
 .../ide-gsettings-file-settings.c                  |   70 +-
 .../ide-gsettings-file-settings.h                  |    5 +-
 src/libide/code/ide-highlight-engine.c             | 1189 ++++++++++++++++++++
 src/libide/code/ide-highlight-engine.h             |   62 +
 src/libide/code/ide-highlight-index.c              |  244 ++++
 src/libide/code/ide-highlight-index.h              |   61 +
 src/libide/code/ide-highlighter.c                  |   93 ++
 src/libide/code/ide-highlighter.h                  |   91 ++
 src/libide/{files => code}/ide-indent-style.h      |    4 +
 .../{gsettings => code}/ide-language-defaults.c    |   10 +-
 .../{gsettings => code}/ide-language-defaults.h    |    0
 src/libide/code/ide-language.c                     |  109 ++
 src/libide/code/ide-language.h                     |   36 +
 src/libide/code/ide-location.c                     |  503 +++++++++
 src/libide/code/ide-location.h                     |   75 ++
 src/libide/code/ide-range.c                        |  290 +++++
 src/libide/code/ide-range.h                        |   58 +
 src/libide/{rename => code}/ide-rename-provider.c  |   40 +-
 src/libide/{rename => code}/ide-rename-provider.h  |   17 +-
 src/libide/{sourceview => code}/ide-source-iter.c  |    6 +-
 src/libide/code/ide-source-iter.h                  |   68 ++
 .../{sourceview => code}/ide-source-style-scheme.c |    2 +-
 .../{sourceview => code}/ide-source-style-scheme.h |    7 +-
 src/libide/{files => code}/ide-spaces-style.h      |    4 +
 src/libide/{symbols => code}/ide-symbol-node.c     |   26 +-
 src/libide/code/ide-symbol-node.h                  |   73 ++
 src/libide/{symbols => code}/ide-symbol-resolver.c |   73 +-
 src/libide/{symbols => code}/ide-symbol-resolver.h |   31 +-
 src/libide/{symbols => code}/ide-symbol-tree.c     |    3 +-
 src/libide/{symbols => code}/ide-symbol-tree.h     |    8 +-
 src/libide/code/ide-symbol.c                       |  533 +++++++++
 src/libide/code/ide-symbol.h                       |  129 +++
 src/libide/code/ide-text-edit-private.h            |   32 +
 src/libide/code/ide-text-edit.c                    |  347 ++++++
 src/libide/code/ide-text-edit.h                    |   64 ++
 src/libide/{sourceview => code}/ide-text-iter.c    |  147 +--
 src/libide/{sourceview => code}/ide-text-iter.h    |   45 +-
 src/libide/code/libide-code.gresource.xml          |    6 +
 src/libide/code/libide-code.h                      |   70 ++
 src/libide/code/meson.build                        |  189 ++++
 src/libide/diagnostics/ide-diagnostic.c            |  604 ----------
 src/libide/diagnostics/ide-diagnostics.c           |  206 ----
 src/libide/diagnostics/ide-diagnostics.h           |   56 -
 src/libide/diagnostics/ide-fixit.c                 |  201 ----
 src/libide/diagnostics/ide-fixit.h                 |   52 -
 src/libide/diagnostics/ide-source-location.c       |  379 -------
 src/libide/diagnostics/ide-source-location.h       |   73 --
 src/libide/diagnostics/ide-source-range.c          |  214 ----
 src/libide/diagnostics/ide-source-range.h          |   51 -
 src/libide/diagnostics/meson.build                 |   29 -
 src/libide/files/ide-file.c                        |  826 --------------
 src/libide/files/ide-file.h                        |   90 --
 src/libide/files/meson.build                       |   23 -
 src/libide/formatting/meson.build                  |   14 -
 src/libide/gsettings/meson.build                   |    8 -
 src/libide/rename/meson.build                      |   12 -
 src/libide/sourceview/ide-source-iter.h            |   87 --
 src/libide/sourceview/ide-text-util.c              |    4 +-
 src/libide/symbols/ide-symbol-node.h               |   70 --
 src/libide/symbols/ide-symbol.c                    |  453 --------
 src/libide/symbols/ide-symbol.h                    |  125 --
 src/libide/symbols/ide-tags-builder.c              |   58 -
 src/libide/symbols/ide-tags-builder.h              |   61 -
 src/libide/symbols/meson.build                     |   31 -
 91 files changed, 6637 insertions(+), 4588 deletions(-)
---
diff --git a/src/libide/files/defaults.ini b/src/libide/code/defaults.ini
similarity index 100%
rename from src/libide/files/defaults.ini
rename to src/libide/code/defaults.ini
diff --git a/src/libide/code/ide-code-global.c b/src/libide/code/ide-code-global.c
new file mode 100644
index 000000000..c623adef7
--- /dev/null
+++ b/src/libide/code/ide-code-global.c
@@ -0,0 +1,44 @@
+/* ide-code-global.c
+ *
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "ide-file-settings.h"
+#include "ide-gsettings-file-settings.h"
+
+#include "../../gconstructor.h"
+
+#if defined (G_HAS_CONSTRUCTORS)
+# ifdef G_DEFINE_CONSTRUCTOR_NEEDS_PRAGMA
+#  pragma G_DEFINE_CONSTRUCTOR_PRAGMA_ARGS(ide_init_ctor)
+# endif
+G_DEFINE_CONSTRUCTOR(ide_code_init_ctor)
+#else
+# error Your platform/compiler is missing constructor support
+#endif
+
+static void
+ide_code_init_ctor (void)
+{
+  g_io_extension_point_register (IDE_FILE_SETTINGS_EXTENSION_POINT);
+
+  g_io_extension_point_implement (IDE_FILE_SETTINGS_EXTENSION_POINT,
+                                  IDE_TYPE_GSETTINGS_FILE_SETTINGS,
+                                  IDE_FILE_SETTINGS_EXTENSION_POINT".gsettings",
+                                  -300);
+}
diff --git a/src/libide/symbols/ide-code-index-entries.c b/src/libide/code/ide-code-index-entries.c
similarity index 96%
rename from src/libide/symbols/ide-code-index-entries.c
rename to src/libide/code/ide-code-index-entries.c
index 33c44b677..aac522e97 100644
--- a/src/libide/symbols/ide-code-index-entries.c
+++ b/src/libide/code/ide-code-index-entries.c
@@ -1,5 +1,6 @@
 /* ide-code-index-entries.c
  *
+ * Copyright 2017-2019 Christian Hergert <chergert redhat com>
  * Copyright 2017 Anoop Chandu <anoopchandu96 gmail com>
  *
  * This program is free software: you can redistribute it and/or modify
@@ -22,10 +23,10 @@
 
 #include "config.h"
 
-#include "application/ide-application.h"
-#include "symbols/ide-code-index-entries.h"
-#include "threading/ide-task.h"
-#include "util/ide-glib.h"
+#include <libide-threading.h>
+
+#include "ide-code-index-entry.h"
+#include "ide-code-index-entries.h"
 
 G_DEFINE_INTERFACE (IdeCodeIndexEntries, ide_code_index_entries, G_TYPE_OBJECT)
 
@@ -159,7 +160,7 @@ ide_code_index_entries_next_entries_async (IdeCodeIndexEntries *self,
  *
  * Completes an asynchronous request for the next set of entries from the index.
  *
- * Returns: (transfer full) (element-type Ide.CodeIndexEntry): a #GPtrArray
+ * Returns: (transfer full) (element-type IdeCodeIndexEntry): a #GPtrArray
  *   of #IdeCodeIndexEntry.
  *
  * Since: 3.32
diff --git a/src/libide/symbols/ide-code-index-entries.h b/src/libide/code/ide-code-index-entries.h
similarity index 91%
rename from src/libide/symbols/ide-code-index-entries.h
rename to src/libide/code/ide-code-index-entries.h
index d2d46e803..176e36ab2 100644
--- a/src/libide/symbols/ide-code-index-entries.h
+++ b/src/libide/code/ide-code-index-entries.h
@@ -1,5 +1,6 @@
 /* ide-code-index-entries.h
  *
+ * Copyright 2017-2019 Christian Hergert <chergert redhat com>
  * Copyright 2017 Anoop Chandu <anoopchandu96 gmail com>
  *
  * This program is free software: you can redistribute it and/or modify
@@ -20,11 +21,13 @@
 
 #pragma once
 
-#include "ide-object.h"
-#include "ide-version-macros.h"
+#if !defined (IDE_CODE_INSIDE) && !defined (IDE_CODE_COMPILATION)
+# error "Only <libide-core.h> can be included directly."
+#endif
 
-#include "symbols/ide-code-index-entry.h"
-#include "symbols/ide-symbol.h"
+#include <libide-core.h>
+
+#include "ide-code-types.h"
 
 G_BEGIN_DECLS
 
diff --git a/src/libide/symbols/ide-code-index-entry.c b/src/libide/code/ide-code-index-entry.c
similarity index 97%
rename from src/libide/symbols/ide-code-index-entry.c
rename to src/libide/code/ide-code-index-entry.c
index bc7896c4f..70cd5bd95 100644
--- a/src/libide/symbols/ide-code-index-entry.c
+++ b/src/libide/code/ide-code-index-entry.c
@@ -1,5 +1,6 @@
 /* ide-code-index-entry.c
  *
+ * Copyright 2017-2019 Christian Hergert <chergert redhat com>
  * Copyright 2017 Anoop Chandu <anoopchandu96 gmail com>
  *
  * This program is free software: you can redistribute it and/or modify
@@ -22,7 +23,7 @@
 
 #include "config.h"
 
-#include "symbols/ide-code-index-entry.h"
+#include "ide-code-index-entry.h"
 
 /**
  * SECTION:ide-code-index-entry
@@ -110,7 +111,7 @@ ide_code_index_entry_get_name (const IdeCodeIndexEntry *self)
 IdeSymbolKind
 ide_code_index_entry_get_kind (const IdeCodeIndexEntry *self)
 {
-  g_return_val_if_fail (self != NULL, IDE_SYMBOL_NONE);
+  g_return_val_if_fail (self != NULL, IDE_SYMBOL_KIND_NONE);
 
   return self->kind;
 }
diff --git a/src/libide/symbols/ide-code-index-entry.h b/src/libide/code/ide-code-index-entry.h
similarity index 94%
rename from src/libide/symbols/ide-code-index-entry.h
rename to src/libide/code/ide-code-index-entry.h
index 36a34b39b..be13a4666 100644
--- a/src/libide/symbols/ide-code-index-entry.h
+++ b/src/libide/code/ide-code-index-entry.h
@@ -20,14 +20,18 @@
 
 #pragma once
 
-#include "ide-object.h"
-#include "ide-version-macros.h"
+#if !defined (IDE_CODE_INSIDE) && !defined (IDE_CODE_COMPILATION)
+# error "Only <libide-code.h> can be included directly."
+#endif
 
-#include "symbols/ide-symbol.h"
+#include <libide-core.h>
+
+#include "ide-code-types.h"
+#include "ide-symbol.h"
 
 G_BEGIN_DECLS
 
-#define IDE_TYPE_CODE_INDEX_ENTRY (ide_code_index_entry_get_type())
+#define IDE_TYPE_CODE_INDEX_ENTRY         (ide_code_index_entry_get_type())
 #define IDE_TYPE_CODE_INDEX_ENTRY_BUILDER (ide_code_index_entry_builder_get_type())
 
 typedef struct _IdeCodeIndexEntry        IdeCodeIndexEntry;
diff --git a/src/libide/symbols/ide-code-indexer.c b/src/libide/code/ide-code-indexer.c
similarity index 96%
rename from src/libide/symbols/ide-code-indexer.c
rename to src/libide/code/ide-code-indexer.c
index f3d02a3eb..0c1c903ce 100644
--- a/src/libide/symbols/ide-code-indexer.c
+++ b/src/libide/code/ide-code-indexer.c
@@ -23,10 +23,8 @@
 
 #include "config.h"
 
-#include "ide-debug.h"
-
-#include "application/ide-application.h"
-#include "symbols/ide-code-indexer.h"
+#include "ide-code-indexer.h"
+#include "ide-location.h"
 
 /**
  * SECTION:ide-code-indexer
@@ -78,7 +76,7 @@ ide_code_indexer_real_index_file_finish (IdeCodeIndexer  *self,
 
 static void
 ide_code_indexer_real_generate_key_async (IdeCodeIndexer      *self,
-                                          IdeSourceLocation   *location,
+                                          IdeLocation   *location,
                                           const gchar * const *build_flags,
                                           GCancellable        *cancellable,
                                           GAsyncReadyCallback  callback,
@@ -197,7 +195,7 @@ ide_code_indexer_index_file_finish (IdeCodeIndexer  *self,
  */
 void
 ide_code_indexer_generate_key_async (IdeCodeIndexer      *self,
-                                     IdeSourceLocation   *location,
+                                     IdeLocation         *location,
                                      const gchar * const *build_flags,
                                      GCancellable        *cancellable,
                                      GAsyncReadyCallback  callback,
@@ -205,7 +203,7 @@ ide_code_indexer_generate_key_async (IdeCodeIndexer      *self,
 {
   g_return_if_fail (IDE_IS_MAIN_THREAD ());
   g_return_if_fail (IDE_IS_CODE_INDEXER (self));
-  g_return_if_fail (location != NULL);
+  g_return_if_fail (IDE_IS_LOCATION (location));
   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
 
   IDE_CODE_INDEXER_GET_IFACE (self)->generate_key_async (self, location, build_flags, cancellable, callback, 
user_data);
diff --git a/src/libide/symbols/ide-code-indexer.h b/src/libide/code/ide-code-indexer.h
similarity index 91%
rename from src/libide/symbols/ide-code-indexer.h
rename to src/libide/code/ide-code-indexer.h
index a77b1fce8..46972f8f9 100644
--- a/src/libide/symbols/ide-code-indexer.h
+++ b/src/libide/code/ide-code-indexer.h
@@ -20,10 +20,13 @@
 
 #pragma once
 
-#include "ide-object.h"
-#include "ide-version-macros.h"
+#if !defined (IDE_CODE_INSIDE) && !defined (IDE_CODE_COMPILATION)
+# error "Only <libide-code.h> can be included directly."
+#endif
 
-#include "symbols/ide-code-index-entries.h"
+#include <libide-core.h>
+
+#include "ide-code-types.h"
 
 G_BEGIN_DECLS
 
@@ -37,7 +40,7 @@ struct _IdeCodeIndexerInterface
   GTypeInterface         parent_iface;
 
   void                 (*generate_key_async)     (IdeCodeIndexer       *self,
-                                                  IdeSourceLocation    *location,
+                                                  IdeLocation          *location,
                                                   const gchar * const  *build_flags,
                                                   GCancellable         *cancellable,
                                                   GAsyncReadyCallback   callback,
@@ -69,7 +72,7 @@ IdeCodeIndexEntries  *ide_code_indexer_index_file_finish   (IdeCodeIndexer
                                                             GError              **error);
 IDE_AVAILABLE_IN_3_32
 void                  ide_code_indexer_generate_key_async  (IdeCodeIndexer       *self,
-                                                            IdeSourceLocation    *location,
+                                                            IdeLocation          *location,
                                                             const gchar * const  *build_flags,
                                                             GCancellable         *cancellable,
                                                             GAsyncReadyCallback   callback,
diff --git a/src/libide/code/ide-code-types.h b/src/libide/code/ide-code-types.h
new file mode 100644
index 000000000..4684bbc19
--- /dev/null
+++ b/src/libide/code/ide-code-types.h
@@ -0,0 +1,60 @@
+/* ide-code-types.h
+ *
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#if !defined (IDE_CODE_INSIDE) && !defined (IDE_CODE_COMPILATION)
+# error "Only <libide-code.h> can be included directly."
+#endif
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct _IdeBuffer IdeBuffer;
+typedef struct _IdeBufferAddin IdeBufferAddin;
+typedef struct _IdeBufferChangeMonitor IdeBufferChangeMonitor;
+typedef struct _IdeCodeIndexEntries IdeCodeIndexEntries;
+typedef struct _IdeCodeIndexEntry IdeCodeIndexEntry;
+typedef struct _IdeCodeIndexer IdeCodeIndexer;
+typedef struct _IdeBufferManager IdeBufferManager;
+typedef struct _IdeDiagnostic IdeDiagnostic;
+typedef struct _IdeDiagnosticProvider IdeDiagnosticProvider;
+typedef struct _IdeDiagnostics IdeDiagnostics;
+typedef struct _IdeDiagnosticsManager IdeDiagnosticsManager;
+typedef struct _IdeFile IdeFile;
+typedef struct _IdeFileSettings IdeFileSettings;
+typedef struct _IdeFormatter IdeFormatter;
+typedef struct _IdeFormatterOptions IdeFormatterOptions;
+typedef struct _IdeHighlightEngine IdeHighlightEngine;
+typedef struct _IdeHighlightIndex IdeHighlightIndex;
+typedef struct _IdeHighlighter IdeHighlighter;
+typedef struct _IdeLocation IdeLocation;
+typedef struct _IdeRange IdeRange;
+typedef struct _IdeRenameProvider IdeRenameProvider;
+typedef struct _IdeSymbol IdeSymbol;
+typedef struct _IdeSymbolNode IdeSymbolNode;
+typedef struct _IdeSymbolResolver IdeSymbolResolver;
+typedef struct _IdeSymbolTree IdeSymbolTree;
+typedef struct _IdeTextEdit IdeTextEdit;
+typedef struct _IdeUnsavedFile IdeUnsavedFile;
+typedef struct _IdeUnsavedFiles IdeUnsavedFiles;
+
+G_END_DECLS
diff --git a/src/libide/diagnostics/ide-diagnostic-provider.c b/src/libide/code/ide-diagnostic-provider.c
similarity index 61%
rename from src/libide/diagnostics/ide-diagnostic-provider.c
rename to src/libide/code/ide-diagnostic-provider.c
index 93f70006b..134901356 100644
--- a/src/libide/diagnostics/ide-diagnostic-provider.c
+++ b/src/libide/code/ide-diagnostic-provider.c
@@ -1,6 +1,6 @@
 /* ide-diagnostic-provider.c
  *
- * Copyright 2015-2019 Christian Hergert <christian hergert me>
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
  *
  * 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
@@ -22,13 +22,8 @@
 
 #include "config.h"
 
-#include "buffers/ide-buffer.h"
-#include "ide-context.h"
-#include "ide-debug.h"
-
-#include "diagnostics/ide-diagnostic-provider.h"
-#include "diagnostics/ide-diagnostics.h"
-#include "files/ide-file.h"
+#include "ide-buffer.h"
+#include "ide-diagnostic-provider.h"
 
 G_DEFINE_INTERFACE (IdeDiagnosticProvider, ide_diagnostic_provider, IDE_TYPE_OBJECT)
 
@@ -55,41 +50,89 @@ ide_diagnostic_provider_default_init (IdeDiagnosticProviderInterface *iface)
                   G_TYPE_FROM_INTERFACE (iface),
                   G_SIGNAL_RUN_LAST,
                   0, NULL, NULL, NULL, G_TYPE_NONE, 0);
+}
+
+/**
+ * ide_diagnostic_provider_load:
+ * @self: a #IdeDiagnosticProvider
+ *
+ * Loads the provider, discovering any necessary resources.
+ *
+ * Since: 3.32
+ */
+void
+ide_diagnostic_provider_load (IdeDiagnosticProvider *self)
+{
+  g_return_if_fail (IDE_IS_DIAGNOSTIC_PROVIDER (self));
+
+  if (IDE_DIAGNOSTIC_PROVIDER_GET_IFACE (self)->load)
+    IDE_DIAGNOSTIC_PROVIDER_GET_IFACE (self)->load (self);
+}
+
+/**
+ * ide_diagnostic_provider_unload:
+ * @self: a #IdeDiagnosticProvider
+ *
+ * Unloads the provider and any allocated resources.
+ *
+ * Since: 3.32
+ */
+void
+ide_diagnostic_provider_unload (IdeDiagnosticProvider *self)
+{
+  g_return_if_fail (IDE_IS_DIAGNOSTIC_PROVIDER (self));
 
+  if (IDE_DIAGNOSTIC_PROVIDER_GET_IFACE (self)->unload)
+    IDE_DIAGNOSTIC_PROVIDER_GET_IFACE (self)->unload (self);
 }
 
-/* If the file does not match a loaded buffer, buffer is %NULL */
+/**
+ * ide_diagnostic_provider_diagnose_async:
+ * @self: a #IdeDiagnosticProvider
+ * @file: a #GFile
+ * @contents: (nullable): the content for the buffer
+ * @lang_id: (nullable): the language id for the buffer
+ * @cancellable: (nullable): a #GCancellable or %NULL
+ * @callback: a callback to execute upon completion
+ * @user_data: closure data for @callback
+ *
+ * Requests the provider diagnose @file using @contents as the contents of
+ * the file.
+ *
+ * @callback is executed upon completion, and the caller should call
+ * ide_diagnostic_provider_diagnose_finish() to get the result.
+ *
+ * Since: 3.32
+ */
 void
 ide_diagnostic_provider_diagnose_async (IdeDiagnosticProvider *self,
-                                        IdeFile               *file,
-                                        IdeBuffer             *buffer,
+                                        GFile                 *file,
+                                        GBytes                *contents,
+                                        const gchar           *lang_id,
                                         GCancellable          *cancellable,
                                         GAsyncReadyCallback    callback,
                                         gpointer               user_data)
 {
-  IDE_ENTRY;
-
   g_return_if_fail (IDE_IS_DIAGNOSTIC_PROVIDER (self));
-  g_return_if_fail (IDE_IS_FILE (file));
-  g_return_if_fail (IDE_IS_BUFFER (buffer) || buffer == NULL);
+  g_return_if_fail (G_IS_FILE (file));
   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
 
   IDE_DIAGNOSTIC_PROVIDER_GET_IFACE (self)->diagnose_async (self,
                                                             file,
-                                                            buffer,
+                                                            contents,
+                                                            lang_id,
                                                             cancellable,
                                                             callback,
                                                             user_data);
-
-  IDE_EXIT;
 }
 
 /**
  * ide_diagnostic_provider_diagnose_finish:
+ * @self: a #IdeDiagnosticProvider
  *
- * Completes an asynchronous call to ide_diagnostic_provider_diagnose_async().
+ * Completes an asynchronous request to diagnose a file.
  *
- * Returns: (transfer full) (nullable): #IdeDiagnostics or %NULL and @error is set.
+ * Returns: (transfer full): an #IdeDiagnostics or %NULL and @error is set.
  *
  * Since: 3.32
  */
@@ -98,21 +141,10 @@ ide_diagnostic_provider_diagnose_finish (IdeDiagnosticProvider  *self,
                                          GAsyncResult           *result,
                                          GError                **error)
 {
-  IdeDiagnostics *ret;
-
-  IDE_ENTRY;
-
   g_return_val_if_fail (IDE_IS_DIAGNOSTIC_PROVIDER (self), NULL);
   g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
 
-  ret = IDE_DIAGNOSTIC_PROVIDER_GET_IFACE (self)->diagnose_finish (self, result, error);
-
-  IDE_TRACE_MSG ("%s diagnosis completed (%p) with %"G_GSIZE_FORMAT" diagnostics",
-                 G_OBJECT_TYPE_NAME (self),
-                 ret,
-                 ret ? ide_diagnostics_get_size (ret) : 0);
-
-  IDE_RETURN (ret);
+  return IDE_DIAGNOSTIC_PROVIDER_GET_IFACE (self)->diagnose_finish (self, result, error);
 }
 
 void
@@ -122,12 +154,3 @@ ide_diagnostic_provider_emit_invalidated (IdeDiagnosticProvider *self)
 
   g_signal_emit (self, signals [INVALIDATED], 0);
 }
-
-void
-ide_diagnostic_provider_load (IdeDiagnosticProvider *self)
-{
-  g_return_if_fail (IDE_IS_DIAGNOSTIC_PROVIDER (self));
-
-  if (IDE_DIAGNOSTIC_PROVIDER_GET_IFACE (self)->load)
-    IDE_DIAGNOSTIC_PROVIDER_GET_IFACE (self)->load (self);
-}
diff --git a/src/libide/diagnostics/ide-diagnostic-provider.h b/src/libide/code/ide-diagnostic-provider.h
similarity index 72%
rename from src/libide/diagnostics/ide-diagnostic-provider.h
rename to src/libide/code/ide-diagnostic-provider.h
index d4154dd69..6f5c1b71b 100644
--- a/src/libide/diagnostics/ide-diagnostic-provider.h
+++ b/src/libide/code/ide-diagnostic-provider.h
@@ -1,6 +1,6 @@
 /* ide-diagnostic-provider.h
  *
- * Copyright 2015-2019 Christian Hergert <christian hergert me>
+ * Copyright 2015-2019 Christian Hergert <chergert redhat com>
  *
  * 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
@@ -20,9 +20,13 @@
 
 #pragma once
 
-#include "ide-version-macros.h"
+#if !defined (IDE_CODE_INSIDE) && !defined (IDE_CODE_COMPILATION)
+# error "Only <libide-code.h> can be included directly."
+#endif
 
-#include "ide-object.h"
+#include <libide-core.h>
+
+#include "ide-code-types.h"
 
 G_BEGIN_DECLS
 
@@ -33,12 +37,14 @@ G_DECLARE_INTERFACE (IdeDiagnosticProvider, ide_diagnostic_provider, IDE, DIAGNO
 
 struct _IdeDiagnosticProviderInterface
 {
-  GTypeInterface parent_interface;
+  GTypeInterface parent_iface;
 
   void            (*load)            (IdeDiagnosticProvider  *self);
+  void            (*unload)          (IdeDiagnosticProvider  *self);
   void            (*diagnose_async)  (IdeDiagnosticProvider  *self,
-                                      IdeFile                *file,
-                                      IdeBuffer              *buffer,
+                                      GFile                  *file,
+                                      GBytes                 *contents,
+                                      const gchar            *lang_id,
                                       GCancellable           *cancellable,
                                       GAsyncReadyCallback     callback,
                                       gpointer                user_data);
@@ -47,10 +53,17 @@ struct _IdeDiagnosticProviderInterface
                                       GError                **error);
 };
 
+IDE_AVAILABLE_IN_3_32
+void            ide_diagnostic_provider_load             (IdeDiagnosticProvider  *self);
+IDE_AVAILABLE_IN_3_32
+void            ide_diagnostic_provider_unload           (IdeDiagnosticProvider  *self);
+IDE_AVAILABLE_IN_3_32
+void            ide_diagnostic_provider_emit_invalidated (IdeDiagnosticProvider  *self);
 IDE_AVAILABLE_IN_3_32
 void            ide_diagnostic_provider_diagnose_async   (IdeDiagnosticProvider  *self,
-                                                          IdeFile                *file,
-                                                          IdeBuffer              *buffer,
+                                                          GFile                  *file,
+                                                          GBytes                 *contents,
+                                                          const gchar            *lang_id,
                                                           GCancellable           *cancellable,
                                                           GAsyncReadyCallback     callback,
                                                           gpointer                user_data);
@@ -58,9 +71,6 @@ IDE_AVAILABLE_IN_3_32
 IdeDiagnostics *ide_diagnostic_provider_diagnose_finish  (IdeDiagnosticProvider  *self,
                                                           GAsyncResult           *result,
                                                           GError                **error);
-IDE_AVAILABLE_IN_3_32
-void            ide_diagnostic_provider_emit_invalidated (IdeDiagnosticProvider  *self);
-IDE_AVAILABLE_IN_3_32
-void            ide_diagnostic_provider_load             (IdeDiagnosticProvider  *self);
+
 
 G_END_DECLS
diff --git a/src/libide/code/ide-diagnostic.c b/src/libide/code/ide-diagnostic.c
new file mode 100644
index 000000000..83e9b3c13
--- /dev/null
+++ b/src/libide/code/ide-diagnostic.c
@@ -0,0 +1,748 @@
+/* ide-diagnostic.c
+ *
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-diagnostic"
+
+#include "config.h"
+
+#include "ide-code-enums.h"
+#include "ide-diagnostic.h"
+#include "ide-location.h"
+#include "ide-range.h"
+#include "ide-text-edit.h"
+
+typedef struct
+{
+  IdeDiagnosticSeverity  severity;
+  guint                  hash;
+  gchar                 *text;
+  IdeLocation           *location;
+  GPtrArray             *ranges;
+  GPtrArray             *fixits;
+} IdeDiagnosticPrivate;
+
+enum {
+  PROP_0,
+  PROP_LOCATION,
+  PROP_SEVERITY,
+  PROP_TEXT,
+  PROP_DISPLAY_TEXT,
+  N_PROPS
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (IdeDiagnostic, ide_diagnostic, IDE_TYPE_OBJECT)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+ide_diagnostic_set_location (IdeDiagnostic *self,
+                             IdeLocation   *location)
+{
+  IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_DIAGNOSTIC (self));
+
+  g_set_object (&priv->location, location);
+}
+
+static void
+ide_diagnostic_set_text (IdeDiagnostic *self,
+                         const gchar   *text)
+{
+  IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_DIAGNOSTIC (self));
+
+  priv->text = g_strdup (text);
+}
+
+static void
+ide_diagnostic_set_severity (IdeDiagnostic         *self,
+                             IdeDiagnosticSeverity  severity)
+{
+  IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_DIAGNOSTIC (self));
+
+  priv->severity = severity;
+}
+
+static void
+ide_diagnostic_finalize (GObject *object)
+{
+  IdeDiagnostic *self = (IdeDiagnostic *)object;
+  IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
+
+  g_clear_pointer (&priv->text, g_free);
+  g_clear_pointer (&priv->ranges, g_ptr_array_unref);
+  g_clear_pointer (&priv->fixits, g_ptr_array_unref);
+  g_clear_object (&priv->location);
+
+  G_OBJECT_CLASS (ide_diagnostic_parent_class)->finalize (object);
+}
+
+static void
+ide_diagnostic_get_property (GObject    *object,
+                             guint       prop_id,
+                             GValue     *value,
+                             GParamSpec *pspec)
+{
+  IdeDiagnostic *self = IDE_DIAGNOSTIC (object);
+
+  switch (prop_id)
+    {
+    case PROP_LOCATION:
+      g_value_set_object (value, ide_diagnostic_get_location (self));
+      break;
+
+    case PROP_SEVERITY:
+      g_value_set_enum (value, ide_diagnostic_get_severity (self));
+      break;
+
+    case PROP_DISPLAY_TEXT:
+      g_value_take_string (value, ide_diagnostic_get_text_for_display (self));
+      break;
+
+    case PROP_TEXT:
+      g_value_set_string (value, ide_diagnostic_get_text (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_diagnostic_set_property (GObject      *object,
+                             guint         prop_id,
+                             const GValue *value,
+                             GParamSpec   *pspec)
+{
+  IdeDiagnostic *self = IDE_DIAGNOSTIC (object);
+
+  switch (prop_id)
+    {
+    case PROP_LOCATION:
+      ide_diagnostic_set_location (self, g_value_get_object (value));
+      break;
+
+    case PROP_SEVERITY:
+      ide_diagnostic_set_severity (self, g_value_get_enum (value));
+      break;
+
+    case PROP_TEXT:
+      ide_diagnostic_set_text (self, g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_diagnostic_class_init (IdeDiagnosticClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_diagnostic_finalize;
+  object_class->get_property = ide_diagnostic_get_property;
+  object_class->set_property = ide_diagnostic_set_property;
+
+  properties [PROP_LOCATION] =
+    g_param_spec_object ("location",
+                         "Location",
+                         "The location of the diagnostic",
+                         IDE_TYPE_LOCATION,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_SEVERITY] =
+    g_param_spec_enum ("severity",
+                       "Severity",
+                       "The severity of the diagnostic",
+                       IDE_TYPE_DIAGNOSTIC_SEVERITY,
+                       IDE_DIAGNOSTIC_IGNORED,
+                       (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_TEXT] =
+    g_param_spec_string ("text",
+                         "Text",
+                         "The text of the diagnostic",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_DISPLAY_TEXT] =
+    g_param_spec_string ("display-text",
+                         "Display Text",
+                         "The text formatted for display",
+                         NULL,
+                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_diagnostic_init (IdeDiagnostic *self)
+{
+}
+
+/**
+ * ide_diagnostic_get_location:
+ * @self: a #IdeDiagnostic
+ *
+ * Gets the location of the diagnostic.
+ *
+ * See also: ide_diagnostic_get_range().
+ *
+ * Returns: (transfer none) (nullable): an #IdeLocation or %NULL
+ *
+ * Since: 3.32
+ */
+IdeLocation *
+ide_diagnostic_get_location (IdeDiagnostic *self)
+{
+  IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DIAGNOSTIC (self), NULL);
+
+  if (priv->location != NULL)
+    return priv->location;
+
+  if (priv->ranges != NULL && priv->ranges->len > 0)
+    {
+      IdeRange *range = g_ptr_array_index (priv->ranges, 0);
+      return ide_range_get_begin (range);
+    }
+
+  return NULL;
+}
+
+/**
+ * ide_diagnostic_get_file:
+ * @self: a #IdeDiagnostic
+ *
+ * Gets the file containing the diagnostic, if any.
+ *
+ * Returns: (transfer none) (nullable): an #IdeLocation or %NULL
+ *
+ * Since: 3.32
+ */
+GFile *
+ide_diagnostic_get_file (IdeDiagnostic *self)
+{
+  IdeLocation *location;
+
+  g_return_val_if_fail (IDE_IS_DIAGNOSTIC (self), NULL);
+
+  if ((location = ide_diagnostic_get_location (self)))
+    return ide_location_get_file (location);
+
+  return NULL;
+}
+
+/**
+ * ide_diagnostic_get_text_for_display:
+ * @self: an #IdeDiagnostic
+ *
+ * This creates a new string that is formatted using the diagnostics
+ * line number, column, severity, and message text in the format
+ * "line:column: severity: message".
+ *
+ * This can be convenient when wanting to quickly display a
+ * diagnostic such as in a tooltip.
+ *
+ * Returns: (transfer full): string containing the text formatted for
+ *   display.
+ *
+ * Since: 3.32
+ */
+gchar *
+ide_diagnostic_get_text_for_display (IdeDiagnostic *self)
+{
+  IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
+  IdeLocation *location;
+  const gchar *severity;
+  guint line = 0;
+  guint column = 0;
+
+  g_return_val_if_fail (IDE_IS_DIAGNOSTIC (self), NULL);
+
+  severity = ide_diagnostic_severity_to_string (priv->severity);
+  location = ide_diagnostic_get_location (self);
+
+  if (location != NULL)
+    {
+      line = ide_location_get_line (location) + 1;
+      column = ide_location_get_line_offset (location) + 1;
+    }
+
+  return g_strdup_printf ("%u:%u: %s: %s", line, column, severity, priv->text);
+}
+
+/**
+ * ide_diagnostic_severity_to_string:
+ * @severity: a #IdeDiagnosticSeverity
+ *
+ * Returns a string suitable to represent the diagnsotic severity.
+ *
+ * Returns: a string
+ *
+ * Since: 3.32
+ */
+const gchar *
+ide_diagnostic_severity_to_string (IdeDiagnosticSeverity severity)
+{
+  switch (severity)
+    {
+    case IDE_DIAGNOSTIC_IGNORED:
+      return "ignored";
+
+    case IDE_DIAGNOSTIC_NOTE:
+      return "note";
+
+    case IDE_DIAGNOSTIC_DEPRECATED:
+      return "deprecated";
+
+    case IDE_DIAGNOSTIC_WARNING:
+      return "warning";
+
+    case IDE_DIAGNOSTIC_ERROR:
+      return "error";
+
+    case IDE_DIAGNOSTIC_FATAL:
+      return "fatal";
+
+    default:
+      return "unknown";
+    }
+}
+
+guint
+ide_diagnostic_get_n_ranges (IdeDiagnostic *self)
+{
+  IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DIAGNOSTIC (self), 0);
+
+  return priv->ranges ? priv->ranges->len : 0;
+}
+
+/**
+ * ide_diagnostic_get_range:
+ *
+ * Retrieves the range found at @index. It is a programming error to call this
+ * function with a value greater or equal to ide_diagnostic_get_n_ranges().
+ *
+ * Returns: (transfer none) (nullable): An #IdeRange
+ *
+ * Since: 3.32
+ */
+IdeRange *
+ide_diagnostic_get_range (IdeDiagnostic *self,
+                          guint          index)
+{
+  IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DIAGNOSTIC (self), NULL);
+
+  if (priv->ranges)
+    {
+      if (index < priv->ranges->len)
+        return g_ptr_array_index (priv->ranges, index);
+    }
+
+  return NULL;
+}
+
+guint
+ide_diagnostic_get_n_fixits (IdeDiagnostic *self)
+{
+  IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DIAGNOSTIC (self), 0);
+
+  return (priv->fixits != NULL) ? priv->fixits->len : 0;
+}
+
+/**
+ * ide_diagnostic_get_fixit:
+ * @self: an #IdeDiagnostic.
+ * @index: The index of the fixit.
+ *
+ * Gets the fixit denoted by @index. This value should be less than the value
+ * returned from ide_diagnostic_get_n_fixits().
+ *
+ * Returns: (transfer none) (nullable): An #IdeTextEdit
+ *
+ * Since: 3.32
+ */
+IdeTextEdit *
+ide_diagnostic_get_fixit (IdeDiagnostic *self,
+                          guint          index)
+{
+  IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
+
+  g_return_val_if_fail (self, NULL);
+  g_return_val_if_fail (IDE_IS_DIAGNOSTIC (self), NULL);
+  g_return_val_if_fail (priv->fixits, NULL);
+
+  if (priv->fixits != NULL)
+    {
+      if (index < priv->fixits->len)
+        return g_ptr_array_index (priv->fixits, index);
+    }
+
+  return NULL;
+}
+
+gint
+ide_diagnostic_compare (IdeDiagnostic *a,
+                        IdeDiagnostic *b)
+{
+  IdeDiagnosticPrivate *priv_a = ide_diagnostic_get_instance_private (a);
+  IdeDiagnosticPrivate *priv_b = ide_diagnostic_get_instance_private (b);
+  gint ret;
+
+  g_assert (IDE_IS_DIAGNOSTIC (a));
+  g_assert (IDE_IS_DIAGNOSTIC (b));
+
+  /* Severity is 0..N where N is more important. So reverse comparator. */
+  if (0 != (ret = (gint)priv_b->severity - (gint)priv_a->severity))
+    return ret;
+
+  if (priv_a->location && priv_b->location)
+    {
+      if (0 != (ret = ide_location_compare (priv_a->location, priv_b->location)))
+        return ret;
+    }
+
+  return g_strcmp0 (priv_a->text, priv_b->text);
+}
+
+const gchar *
+ide_diagnostic_get_text (IdeDiagnostic *self)
+{
+  IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DIAGNOSTIC (self), NULL);
+
+  return priv->text;
+}
+
+IdeDiagnosticSeverity
+ide_diagnostic_get_severity (IdeDiagnostic *self)
+{
+  IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DIAGNOSTIC (self), 0);
+
+  return priv->severity;
+}
+
+/**
+ * ide_diagnostic_add_range:
+ * @self: a #IdeDiagnostic
+ * @range: an #IdeRange
+ *
+ * Adds a source range to the diagnostic.
+ *
+ * Since: 3.32
+ */
+void
+ide_diagnostic_add_range (IdeDiagnostic *self,
+                          IdeRange      *range)
+{
+  IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_DIAGNOSTIC (self));
+  g_return_if_fail (IDE_IS_RANGE (range));
+
+  if (priv->ranges == NULL)
+    priv->ranges = g_ptr_array_new_with_free_func (g_object_unref);
+  g_ptr_array_add (priv->ranges, g_object_ref (range));
+}
+
+/**
+ * ide_diagnostic_take_range:
+ * @self: a #IdeDiagnostic
+ * @range: (transfer full): an #IdeRange
+ *
+ * Adds a source range to the diagnostic, but does not increment the
+ * reference count of @range.
+ *
+ * Since: 3.32
+ */
+void
+ide_diagnostic_take_range (IdeDiagnostic *self,
+                           IdeRange      *range)
+{
+  IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_DIAGNOSTIC (self));
+  g_return_if_fail (IDE_IS_RANGE (range));
+
+  if (priv->ranges == NULL)
+    priv->ranges = g_ptr_array_new_with_free_func (g_object_unref);
+  g_ptr_array_add (priv->ranges, g_steal_pointer (&range));
+}
+
+/**
+ * ide_diagnostic_add_fixit:
+ * @self: a #IdeDiagnostic
+ * @fixit: an #IdeTextEdit
+ *
+ * Adds a source fixit to the diagnostic.
+ *
+ * Since: 3.32
+ */
+void
+ide_diagnostic_add_fixit (IdeDiagnostic *self,
+                          IdeTextEdit   *fixit)
+{
+  IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_DIAGNOSTIC (self));
+  g_return_if_fail (IDE_IS_TEXT_EDIT (fixit));
+
+  if (priv->fixits == NULL)
+    priv->fixits = g_ptr_array_new_with_free_func (g_object_unref);
+  g_ptr_array_add (priv->fixits, g_object_ref (fixit));
+}
+
+/**
+ * ide_diagnostic_take_fixit:
+ * @self: a #IdeDiagnostic
+ * @fixit: (transfer full): an #IdeTextEdit
+ *
+ * Adds a source fixit to the diagnostic, but does not increment the
+ * reference count of @fixit.
+ *
+ * Since: 3.32
+ */
+void
+ide_diagnostic_take_fixit (IdeDiagnostic *self,
+                           IdeTextEdit   *fixit)
+{
+  IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_DIAGNOSTIC (self));
+  g_return_if_fail (IDE_IS_TEXT_EDIT (fixit));
+
+  if (priv->fixits == NULL)
+    priv->fixits = g_ptr_array_new_with_free_func (g_object_unref);
+  g_ptr_array_add (priv->fixits, g_steal_pointer (&fixit));
+}
+
+IdeDiagnostic *
+ide_diagnostic_new (IdeDiagnosticSeverity  severity,
+                    const gchar           *message,
+                    IdeLocation           *location)
+{
+  return g_object_new (IDE_TYPE_DIAGNOSTIC,
+                       "severity", severity,
+                       "location", location,
+                       "text", message,
+                       NULL);
+}
+
+guint
+ide_diagnostic_hash (IdeDiagnostic *self)
+{
+  IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DIAGNOSTIC (self), 0);
+
+  if (priv->hash == 0)
+    {
+      guint hash = g_str_hash (priv->text ?: "");
+      if (priv->location)
+        hash ^= ide_location_hash (priv->location);
+      if (priv->fixits)
+        hash ^= g_int_hash (&priv->fixits->len);
+      if (priv->ranges)
+        hash ^= g_int_hash (&priv->ranges->len);
+      priv->hash = hash;
+    }
+
+  return priv->hash;
+}
+
+/**
+ * ide_diagnostic_to_variant:
+ * @self: a #IdeDiagnostic
+ *
+ * Creates a #GVariant to represent the diagnostic. This can be useful when
+ * working in subprocesses to serialize the diagnostic.
+ *
+ * This function will never return a floating variant.
+ *
+ * Returns: (transfer full): a #GVariant
+ *
+ * Since: 3.32
+ */
+GVariant *
+ide_diagnostic_to_variant (IdeDiagnostic *self)
+{
+  IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
+  GVariantDict dict;
+
+  g_return_val_if_fail (self != NULL, NULL);
+  g_return_val_if_fail (IDE_IS_DIAGNOSTIC (self), NULL);
+
+  g_variant_dict_init (&dict, NULL);
+
+  g_variant_dict_insert (&dict, "text", "s", priv->text ?: "");
+  g_variant_dict_insert (&dict, "severity", "u", priv->severity);
+
+  if (priv->location != NULL)
+    {
+      g_autoptr(GVariant) vloc = ide_location_to_variant (priv->location);
+
+      if (vloc != NULL)
+        g_variant_dict_insert_value (&dict, "location", vloc);
+    }
+
+  if (priv->ranges != NULL && priv->ranges->len > 0)
+    {
+      GVariantBuilder builder;
+
+      g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
+
+      for (guint i = 0; i < priv->ranges->len; i++)
+        {
+          IdeRange *range = g_ptr_array_index (priv->ranges, i);
+          g_autoptr(GVariant) vrange = ide_range_to_variant (range);
+
+          g_variant_builder_add_value (&builder, vrange);
+        }
+
+      g_variant_dict_insert_value (&dict, "ranges", g_variant_builder_end (&builder));
+    }
+
+  if (priv->fixits != NULL && priv->fixits->len > 0)
+    {
+      GVariantBuilder builder;
+
+      g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
+
+      for (guint i = 0; i < priv->fixits->len; i++)
+        {
+          IdeTextEdit *fixit = g_ptr_array_index (priv->fixits, i);
+          g_autoptr(GVariant) vfixit = ide_text_edit_to_variant (fixit);
+
+          g_variant_builder_add_value (&builder, vfixit);
+        }
+
+      g_variant_dict_insert_value (&dict, "fixits", g_variant_builder_end (&builder));
+    }
+
+  return g_variant_take_ref (g_variant_dict_end (&dict));
+}
+
+/**
+ * ide_diagnostic_new_from_variant:
+ * @variant: (nullable): a #GVariant or %NULL
+ *
+ * Creates a new #GVariant using the data contained in @variant.
+ *
+ * If @variant is %NULL or Upon failure, %NULL is returned.
+ *
+ * Returns: (nullable) (transfer full): a #IdeDiagnostic or %NULL
+ *
+ * Since: 3.32
+ */
+IdeDiagnostic *
+ide_diagnostic_new_from_variant (GVariant *variant)
+{
+  g_autoptr(IdeLocation) loc = NULL;
+  g_autoptr(GVariant) vloc = NULL;
+  g_autoptr(GVariant) unboxed = NULL;
+  g_autoptr(GVariant) ranges = NULL;
+  g_autoptr(GVariant) fixits = NULL;
+  IdeDiagnostic *self;
+  GVariantDict dict;
+  GVariantIter iter;
+  const gchar *text;
+  guint32 severity;
+
+  if (variant == NULL)
+    return NULL;
+
+  if (g_variant_is_of_type (variant, G_VARIANT_TYPE_VARIANT))
+    variant = unboxed = g_variant_get_variant (variant);
+
+  if (!g_variant_is_of_type (variant, G_VARIANT_TYPE_VARDICT))
+    return NULL;
+
+  g_variant_dict_init (&dict, variant);
+
+  if (!g_variant_dict_lookup (&dict, "text", "&s", &text))
+    text = NULL;
+
+  if (!g_variant_dict_lookup (&dict, "severity", "u", &severity))
+    severity = 0;
+
+  if ((vloc = g_variant_dict_lookup_value (&dict, "location", NULL)))
+    loc = ide_location_new_from_variant (vloc);
+
+  if (!(self = ide_diagnostic_new (severity, text, loc)))
+    goto failure;
+
+  /* Ranges */
+  if ((ranges = g_variant_dict_lookup_value (&dict, "ranges", NULL)))
+    {
+      GVariant *vrange;
+
+      g_variant_iter_init (&iter, ranges);
+
+      while ((vrange = g_variant_iter_next_value (&iter)))
+        {
+          IdeRange *range;
+
+          if ((range = ide_range_new_from_variant (vrange)))
+            ide_diagnostic_take_range (self, g_steal_pointer (&range));
+
+          g_variant_unref (vrange);
+        }
+    }
+
+  /* Fixits */
+  if ((fixits = g_variant_dict_lookup_value (&dict, "fixits", NULL)))
+    {
+      GVariant *vfixit;
+
+      g_variant_iter_init (&iter, fixits);
+
+      while ((vfixit = g_variant_iter_next_value (&iter)))
+        {
+          IdeTextEdit *fixit;
+
+          if ((fixit = ide_text_edit_new_from_variant (vfixit)))
+            ide_diagnostic_take_fixit (self, g_steal_pointer (&fixit));
+
+          g_variant_unref (vfixit);
+        }
+    }
+
+failure:
+
+  g_variant_dict_clear (&dict);
+
+  return self;
+}
diff --git a/src/libide/diagnostics/ide-diagnostic.h b/src/libide/code/ide-diagnostic.h
similarity index 64%
rename from src/libide/diagnostics/ide-diagnostic.h
rename to src/libide/code/ide-diagnostic.h
index fb2b0f337..6f9b093a9 100644
--- a/src/libide/diagnostics/ide-diagnostic.h
+++ b/src/libide/code/ide-diagnostic.h
@@ -1,6 +1,6 @@
 /* ide-diagnostic.h
  *
- * Copyright 2015-2019 Christian Hergert <christian hergert me>
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
  *
  * 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
@@ -20,12 +20,13 @@
 
 #pragma once
 
-#include <gio/gio.h>
+#if !defined (IDE_CODE_INSIDE) && !defined (IDE_CODE_COMPILATION)
+# error "Only <libide-code.h> can be included directly."
+#endif
 
-#include "ide-version-macros.h"
+#include <libide-core.h>
 
-#include "diagnostics/ide-fixit.h"
-#include "ide-types.h"
+#include "ide-code-types.h"
 
 G_BEGIN_DECLS
 
@@ -42,57 +43,62 @@ typedef enum
 } IdeDiagnosticSeverity;
 
 IDE_AVAILABLE_IN_3_32
-IdeSourceLocation     *ide_diagnostic_get_location         (IdeDiagnostic         *self);
+G_DECLARE_DERIVABLE_TYPE (IdeDiagnostic, ide_diagnostic, IDE, DIAGNOSTIC, IdeObject)
+
+struct _IdeDiagnosticClass
+{
+  IdeObjectClass parent_class;
+
+  /*< private >*/
+  gpointer _reserved[16];
+};
+
 IDE_AVAILABLE_IN_3_32
-GFile                 *ide_diagnostic_get_file             (IdeDiagnostic         *self);
+IdeDiagnostic         *ide_diagnostic_new                  (IdeDiagnosticSeverity  severity,
+                                                            const gchar           *message,
+                                                            IdeLocation           *location);
 IDE_AVAILABLE_IN_3_32
-guint                  ide_diagnostic_get_num_fixits       (IdeDiagnostic         *self);
+IdeDiagnostic         *ide_diagnostic_new_from_variant     (GVariant              *variant);
 IDE_AVAILABLE_IN_3_32
-IdeFixit              *ide_diagnostic_get_fixit            (IdeDiagnostic         *self,
-                                                            guint                  index);
+guint                  ide_diagnostic_hash                 (IdeDiagnostic         *self);
 IDE_AVAILABLE_IN_3_32
-guint                  ide_diagnostic_get_num_ranges       (IdeDiagnostic         *self);
+IdeLocation           *ide_diagnostic_get_location         (IdeDiagnostic         *self);
 IDE_AVAILABLE_IN_3_32
-IdeSourceRange        *ide_diagnostic_get_range            (IdeDiagnostic         *self,
-                                                            guint                  index);
+const gchar           *ide_diagnostic_get_text             (IdeDiagnostic         *self);
 IDE_AVAILABLE_IN_3_32
 IdeDiagnosticSeverity  ide_diagnostic_get_severity         (IdeDiagnostic         *self);
 IDE_AVAILABLE_IN_3_32
-const gchar           *ide_diagnostic_get_text             (IdeDiagnostic         *self);
+GFile                 *ide_diagnostic_get_file             (IdeDiagnostic         *self);
 IDE_AVAILABLE_IN_3_32
 gchar                 *ide_diagnostic_get_text_for_display (IdeDiagnostic         *self);
 IDE_AVAILABLE_IN_3_32
-GType                  ide_diagnostic_get_type             (void);
+const gchar           *ide_diagnostic_severity_to_string   (IdeDiagnosticSeverity  severity);
 IDE_AVAILABLE_IN_3_32
-IdeDiagnostic         *ide_diagnostic_ref                  (IdeDiagnostic         *self);
+guint                  ide_diagnostic_get_n_ranges         (IdeDiagnostic         *self);
 IDE_AVAILABLE_IN_3_32
-void                   ide_diagnostic_unref                (IdeDiagnostic         *self);
+IdeRange              *ide_diagnostic_get_range            (IdeDiagnostic         *self,
+                                                            guint                  index);
 IDE_AVAILABLE_IN_3_32
-IdeDiagnostic         *ide_diagnostic_new                  (IdeDiagnosticSeverity  severity,
-                                                            const gchar           *text,
-                                                            IdeSourceLocation     *location);
+guint                  ide_diagnostic_get_n_fixits         (IdeDiagnostic         *self);
 IDE_AVAILABLE_IN_3_32
-IdeDiagnostic         *ide_diagnostic_new_from_variant     (GVariant              *variant);
+IdeTextEdit           *ide_diagnostic_get_fixit            (IdeDiagnostic         *self,
+                                                            guint                  index);
 IDE_AVAILABLE_IN_3_32
 void                   ide_diagnostic_add_range            (IdeDiagnostic         *self,
-                                                            IdeSourceRange        *range);
-IDE_AVAILABLE_IN_3_32
-void                   ide_diagnostic_take_fixit           (IdeDiagnostic         *self,
-                                                            IdeFixit              *fixit);
+                                                            IdeRange              *range);
 IDE_AVAILABLE_IN_3_32
 void                   ide_diagnostic_take_range           (IdeDiagnostic         *self,
-                                                            IdeSourceRange        *range);
+                                                            IdeRange              *range);
 IDE_AVAILABLE_IN_3_32
-gint                   ide_diagnostic_compare              (const IdeDiagnostic   *a,
-                                                            const IdeDiagnostic   *b);
+void                   ide_diagnostic_add_fixit            (IdeDiagnostic         *self,
+                                                            IdeTextEdit           *fixit);
 IDE_AVAILABLE_IN_3_32
-guint                  ide_diagnostic_hash                 (IdeDiagnostic         *self);
+void                   ide_diagnostic_take_fixit           (IdeDiagnostic         *self,
+                                                            IdeTextEdit           *fixit);
 IDE_AVAILABLE_IN_3_32
-GVariant              *ide_diagnostic_to_variant           (const IdeDiagnostic   *self);
-
+gboolean               ide_diagnostic_compare              (IdeDiagnostic         *a,
+                                                            IdeDiagnostic         *b);
 IDE_AVAILABLE_IN_3_32
-const gchar           *ide_diagnostic_severity_to_string   (IdeDiagnosticSeverity severity);
-
-G_DEFINE_AUTOPTR_CLEANUP_FUNC (IdeDiagnostic, ide_diagnostic_unref)
+GVariant              *ide_diagnostic_to_variant           (IdeDiagnostic         *self);
 
 G_END_DECLS
diff --git a/src/libide/code/ide-diagnostics-manager-private.h 
b/src/libide/code/ide-diagnostics-manager-private.h
new file mode 100644
index 000000000..d431bd9b2
--- /dev/null
+++ b/src/libide/code/ide-diagnostics-manager-private.h
@@ -0,0 +1,41 @@
+/* ide-diagnostics-manager-private.h
+ *
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include "ide-buffer.h"
+#include "ide-diagnostics-manager.h"
+
+G_BEGIN_DECLS
+
+void _ide_diagnostics_manager_file_opened      (IdeDiagnosticsManager *self,
+                                                GFile                 *file,
+                                                const gchar           *lang_id);
+void _ide_diagnostics_manager_file_closed      (IdeDiagnosticsManager *self,
+                                                GFile                 *file);
+void _ide_diagnostics_manager_language_changed (IdeDiagnosticsManager *self,
+                                                GFile                 *file,
+                                                const gchar           *lang_id);
+void _ide_diagnostics_manager_file_changed     (IdeDiagnosticsManager *self,
+                                                GFile                 *file,
+                                                GBytes                *contents,
+                                                const gchar           *lang_id);
+
+G_END_DECLS
diff --git a/src/libide/diagnostics/ide-diagnostics-manager.c b/src/libide/code/ide-diagnostics-manager.c
similarity index 71%
rename from src/libide/diagnostics/ide-diagnostics-manager.c
rename to src/libide/code/ide-diagnostics-manager.c
index 0702559e8..64627e3f7 100644
--- a/src/libide/diagnostics/ide-diagnostics-manager.c
+++ b/src/libide/code/ide-diagnostics-manager.c
@@ -23,18 +23,16 @@
 #include "config.h"
 
 #include <gtksourceview/gtksource.h>
+#include <libide-plugins.h>
 
-#include "ide-context.h"
-#include "ide-debug.h"
-
-#include "application/ide-application.h"
-#include "buffers/ide-buffer.h"
-#include "buffers/ide-buffer-manager.h"
-#include "diagnostics/ide-diagnostic.h"
-#include "diagnostics/ide-diagnostic-provider.h"
-#include "diagnostics/ide-diagnostics.h"
-#include "diagnostics/ide-diagnostics-manager.h"
-#include "plugins/ide-extension-set-adapter.h"
+#include "ide-buffer.h"
+#include "ide-buffer-manager.h"
+#include "ide-buffer-private.h"
+#include "ide-diagnostic.h"
+#include "ide-diagnostic-provider.h"
+#include "ide-diagnostics.h"
+#include "ide-diagnostics-manager.h"
+#include "ide-diagnostics-manager-private.h"
 
 #define DEFAULT_DIAGNOSE_DELAY 333
 #define DIAG_GROUP_MAGIC       0xF1282727
@@ -56,16 +54,6 @@ typedef struct
    */
   GFile *file;
 
-  /*
-   * If there is a buffer open for the file, then the buffer will be found
-   * here. This is useful when we detect that a IdeBuffer:file property has
-   * changed and we need to invalidate things. There is a weak reference
-   * to the buffer here, but that is dropped in our ::buffer-unloaded
-   * callback. It's not really necessary other than for some additional
-   * insurance.
-   */
-  GWeakRef buffer_wr;
-
   /*
    * This hash table uses the given provider as the key and the last
    * reported IdeDiagnostics as the value.
@@ -81,6 +69,12 @@ typedef struct
    */
   IdeExtensionSetAdapter *adapter;
 
+  /* The most recent bytes we received for a future diagnosis. */
+  GBytes *contents;
+
+  /* The last language id we were notified about */
+  const gchar *lang_id;
+
   /*
    * This is our sequence number for diagnostics. It is monotonically
    * increasing with every diagnostic discovered.
@@ -148,7 +142,6 @@ enum {
 };
 
 
-static void     initable_iface_init                       (GInitableIface        *iface);
 static gboolean ide_diagnostics_manager_clear_by_provider (IdeDiagnosticsManager *self,
                                                            IdeDiagnosticProvider *provider);
 static void     ide_diagnostics_manager_add_diagnostic    (IdeDiagnosticsManager *self,
@@ -161,21 +154,24 @@ static void     ide_diagnostics_group_queue_diagnose      (IdeDiagnosticsGroup
 static GParamSpec *properties [N_PROPS];
 static guint signals [N_SIGNALS];
 
-
-G_DEFINE_TYPE_WITH_CODE (IdeDiagnosticsManager, ide_diagnostics_manager, IDE_TYPE_OBJECT,
-                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init))
-
+G_DEFINE_TYPE (IdeDiagnosticsManager, ide_diagnostics_manager, IDE_TYPE_OBJECT)
 
 static void
 free_diagnostics (gpointer data)
 {
   IdeDiagnostics *diagnostics = data;
 
-  g_clear_pointer (&diagnostics, ide_diagnostics_unref);
+  g_clear_object (&diagnostics);
+}
+
+static inline guint
+diagnostics_get_size (IdeDiagnostics *diags)
+{
+  return diags ? g_list_model_get_n_items (G_LIST_MODEL (diags)) : 0;
 }
 
 static void
-ide_diagnostics_group_free (IdeDiagnosticsGroup *group)
+ide_diagnostics_group_finalize (IdeDiagnosticsGroup *group)
 {
   g_assert (IDE_IS_MAIN_THREAD ());
   g_assert (group != NULL);
@@ -184,8 +180,8 @@ ide_diagnostics_group_free (IdeDiagnosticsGroup *group)
   group->magic = 0;
 
   g_clear_pointer (&group->diagnostics_by_provider, g_hash_table_unref);
-  g_weak_ref_clear (&group->buffer_wr);
-  g_clear_object (&group->adapter);
+  g_clear_pointer (&group->contents, g_bytes_unref);
+  ide_clear_and_destroy_object (&group->adapter);
   g_clear_object (&group->file);
 }
 
@@ -201,8 +197,6 @@ ide_diagnostics_group_new (GFile *file)
   group->magic = DIAG_GROUP_MAGIC;
   group->file = g_object_ref (file);
 
-  g_weak_ref_init (&group->buffer_wr, NULL);
-
   return group;
 }
 
@@ -223,7 +217,7 @@ ide_diagnostics_group_unref (IdeDiagnosticsGroup *group)
   g_assert (group != NULL);
   g_assert (IS_DIAGNOSTICS_GROUP (group));
 
-  g_rc_box_release_full (group, (GDestroyNotify)ide_diagnostics_group_free);
+  g_rc_box_release_full (group, (GDestroyNotify)ide_diagnostics_group_finalize);
 }
 
 static guint
@@ -244,7 +238,7 @@ ide_diagnostics_group_has_diagnostics (IdeDiagnosticsGroup *group)
         {
           IdeDiagnostics *diagnostics = value;
 
-          if (diagnostics != NULL && ide_diagnostics_get_size (diagnostics) > 0)
+          if (diagnostics_get_size (diagnostics) > 0)
             return TRUE;
         }
     }
@@ -255,8 +249,6 @@ ide_diagnostics_group_has_diagnostics (IdeDiagnosticsGroup *group)
 static gboolean
 ide_diagnostics_group_can_dispose (IdeDiagnosticsGroup *group)
 {
-  g_autoptr(IdeBuffer) buffer = NULL;
-
   g_assert (IDE_IS_MAIN_THREAD ());
   g_assert (group != NULL);
   g_assert (IS_DIAGNOSTICS_GROUP (group));
@@ -267,11 +259,8 @@ ide_diagnostics_group_can_dispose (IdeDiagnosticsGroup *group)
    * registered for the group.
    */
 
-  buffer = g_weak_ref_get (&group->buffer_wr);
-
-  return (buffer == NULL) &&
-         (group->adapter == NULL) &&
-         (group->has_diagnostics == FALSE);
+  return group->adapter == NULL &&
+         group->has_diagnostics == FALSE;
 }
 
 static void
@@ -294,7 +283,7 @@ ide_diagnostics_group_add (IdeDiagnosticsGroup   *group,
 
   if (diagnostics == NULL)
     {
-      diagnostics = ide_diagnostics_new (NULL);
+      diagnostics = ide_diagnostics_new ();
       g_hash_table_insert (group->diagnostics_by_provider, provider, diagnostics);
     }
 
@@ -364,16 +353,16 @@ ide_diagnostics_group_diagnose_cb (GObject      *object,
    */
   if (diagnostics != NULL)
     {
-      guint length = ide_diagnostics_get_size (diagnostics);
+      guint length = diagnostics_get_size (diagnostics);
 
       for (guint i = 0; i < length; i++)
         {
-          IdeDiagnostic *diagnostic = ide_diagnostics_index (diagnostics, i);
+          g_autoptr(IdeDiagnostic) diagnostic = g_list_model_get_item (G_LIST_MODEL (diagnostics), i);
           GFile *file = ide_diagnostic_get_file (diagnostic);
 
-          if G_LIKELY (file != NULL)
+          if (file != NULL)
             {
-              if G_LIKELY (g_file_equal (file, group->file))
+              if (g_file_equal (file, group->file))
                 ide_diagnostics_group_add (group, provider, diagnostic);
               else
                 ide_diagnostics_manager_add_diagnostic (self, provider, diagnostic);
@@ -433,9 +422,6 @@ ide_diagnostics_group_diagnose_foreach (IdeExtensionSetAdapter *adapter,
   IdeDiagnosticProvider *provider = (IdeDiagnosticProvider *)exten;
   IdeDiagnosticsManager *self = user_data;
   IdeDiagnosticsGroup *group;
-  IdeContext *context;
-  g_autoptr (IdeBuffer) buffer = NULL;
-  g_autoptr(IdeFile) file = NULL;
 
   IDE_ENTRY;
 
@@ -452,10 +438,6 @@ ide_diagnostics_group_diagnose_foreach (IdeExtensionSetAdapter *adapter,
 
   group->in_diagnose++;
 
-  context = ide_object_get_context (IDE_OBJECT (self));
-
-  file = ide_file_new (context, group->file);
-
 #ifdef IDE_ENABLE_TRACE
   {
     g_autofree gchar *uri = g_file_get_uri (group->file);
@@ -464,10 +446,10 @@ ide_diagnostics_group_diagnose_foreach (IdeExtensionSetAdapter *adapter,
   }
 #endif
 
-  buffer = g_weak_ref_get (&group->buffer_wr);
   ide_diagnostic_provider_diagnose_async (provider,
-                                          file,
-                                          buffer,
+                                          group->file,
+                                          group->contents,
+                                          group->lang_id,
                                           NULL,
                                           ide_diagnostics_group_diagnose_cb,
                                           g_object_ref (self));
@@ -477,8 +459,6 @@ static void
 ide_diagnostics_group_diagnose (IdeDiagnosticsGroup   *group,
                                 IdeDiagnosticsManager *self)
 {
-  g_autoptr(IdeBuffer) buffer = NULL;
-
   IDE_ENTRY;
 
   g_assert (IDE_IS_DIAGNOSTICS_MANAGER (self));
@@ -489,14 +469,6 @@ ide_diagnostics_group_diagnose (IdeDiagnosticsGroup   *group,
   group->needs_diagnose = FALSE;
   group->has_diagnostics = FALSE;
 
-  /*
-   * We need to ensure that all the diagnostic providers have access to the
-   * proper data within the unsaved files. So sync the content once to avoid
-   * all providers from having to do this manually.
-   */
-  if (NULL != (buffer = g_weak_ref_get (&group->buffer_wr)))
-    ide_buffer_sync_to_unsaved_files (buffer);
-
   ide_extension_set_adapter_foreach (group->adapter,
                                      ide_diagnostics_group_diagnose_foreach,
                                      self);
@@ -564,7 +536,7 @@ ide_diagnostics_manager_finalize (GObject *object)
 {
   IdeDiagnosticsManager *self = (IdeDiagnosticsManager *)object;
 
-  dzl_clear_source (&self->queued_diagnose_source);
+  g_clear_handle_id (&self->queued_diagnose_source, g_source_remove);
   g_clear_pointer (&self->groups_by_file, g_hash_table_unref);
 
   G_OBJECT_CLASS (ide_diagnostics_manager_parent_class)->finalize (object);
@@ -668,24 +640,18 @@ ide_diagnostics_manager_add_diagnostic (IdeDiagnosticsManager *self,
 }
 
 static IdeDiagnosticsGroup *
-ide_diagnostics_manager_find_group_from_buffer (IdeDiagnosticsManager *self,
-                                                IdeBuffer             *buffer)
+ide_diagnostics_manager_find_group (IdeDiagnosticsManager *self,
+                                    GFile                 *file)
 {
   IdeDiagnosticsGroup *group;
-  IdeFile *ifile;
-  GFile *gfile;
 
   g_assert (IDE_IS_MAIN_THREAD ());
   g_assert (IDE_IS_DIAGNOSTICS_MANAGER (self));
-  g_assert (IDE_IS_BUFFER (buffer));
-
-  ifile = ide_buffer_get_file (buffer);
-  gfile = ide_file_get_file (ifile);
-  group = g_hash_table_lookup (self->groups_by_file, gfile);
+  g_assert (G_IS_FILE (file));
 
-  if (group == NULL)
+  if (!(group = g_hash_table_lookup (self->groups_by_file, file)))
     {
-      group = ide_diagnostics_group_new (gfile);
+      group = ide_diagnostics_group_new (file);
       g_hash_table_insert (self->groups_by_file, group->file, group);
     }
 
@@ -875,365 +841,6 @@ ide_diagnostics_manager_extension_removed (IdeExtensionSetAdapter *adapter,
   IDE_EXIT;
 }
 
-static void
-ide_diagnostics_manager_buffer_changed (IdeDiagnosticsManager *self,
-                                        IdeBuffer             *buffer)
-{
-  IdeDiagnosticsGroup *group;
-
-  IDE_ENTRY;
-
-  g_assert (IDE_IS_MAIN_THREAD ());
-  g_assert (IDE_IS_DIAGNOSTICS_MANAGER (self));
-  g_assert (IDE_IS_BUFFER (buffer));
-
-  group = ide_diagnostics_manager_find_group_from_buffer (self, buffer);
-
-  g_assert (group != NULL);
-  g_assert (IS_DIAGNOSTICS_GROUP (group));
-
-  ide_diagnostics_group_queue_diagnose (group, self);
-
-  IDE_EXIT;
-}
-
-static void
-ide_diagnostics_manager_buffer_notify_language (IdeDiagnosticsManager *self,
-                                                GParamSpec            *pspec,
-                                                IdeBuffer             *buffer)
-{
-  IdeDiagnosticsGroup *group;
-  const gchar *language_id = NULL;
-
-  IDE_ENTRY;
-
-  g_assert (IDE_IS_MAIN_THREAD ());
-  g_assert (IDE_IS_DIAGNOSTICS_MANAGER (self));
-  g_assert (pspec != NULL);
-  g_assert (g_str_equal (pspec->name, "language"));
-  g_assert (IDE_IS_BUFFER (buffer));
-
-  /*
-   * The goal here is to get the new language_id for the buffer and
-   * alter the set of loaded diagnostic providers to match those registered
-   * for the particular language_id. IdeExtensionSetAdapter does most of
-   * the hard work, we just need to update the "match value".
-   */
-  language_id = ide_buffer_get_language_id (buffer);
-  group = ide_diagnostics_manager_find_group_from_buffer (self, buffer);
-
-  g_assert (group != NULL);
-  g_assert (IS_DIAGNOSTICS_GROUP (group));
-
-  if (group->adapter != NULL)
-    ide_extension_set_adapter_set_value (group->adapter, language_id);
-
-  IDE_EXIT;
-}
-
-void
-ide_diagnostics_manager_update_group_by_file (IdeDiagnosticsManager *self,
-                                              IdeBuffer             *buffer,
-                                              GFile                 *new_file)
-{
-  GHashTableIter iter;
-  gpointer value;
-
-  g_assert (IDE_IS_MAIN_THREAD ());
-  g_assert (IDE_IS_DIAGNOSTICS_MANAGER (self));
-  g_assert (IDE_IS_BUFFER (buffer));
-  g_assert (G_IS_FILE (new_file));
-
-  IDE_ENTRY;
-
-  /*
-   * The goal here is to steal the group that is in the hash table using
-   * the old GFile, and replace it with the new GFile. That means removing
-   * the group from the hashtable, changing the file field, and then
-   * reinserting with our new file key.
-   */
-  g_hash_table_iter_init (&iter, self->groups_by_file);
-
-  while (g_hash_table_iter_next (&iter, NULL, &value))
-    {
-      IdeDiagnosticsGroup *group = value;
-      g_autoptr(IdeBuffer) group_buffer = g_weak_ref_get (&group->buffer_wr);
-
-      g_assert (group != NULL);
-      g_assert (IS_DIAGNOSTICS_GROUP (group));
-      g_assert (G_IS_FILE (group->file));
-
-      if (buffer == group_buffer)
-        {
-          if (!g_file_equal (new_file, group->file))
-            {
-              g_hash_table_steal (self->groups_by_file, group->file);
-              g_set_object (&group->file, new_file);
-              g_hash_table_insert (self->groups_by_file, group->file, group);
-            }
-
-          IDE_EXIT;
-        }
-    }
-
-  IDE_EXIT;
-}
-
-static void
-ide_diagnostics_manager_buffer_notify_file (IdeDiagnosticsManager *self,
-                                            GParamSpec            *pspec,
-                                            IdeBuffer             *buffer)
-{
-  IdeFile *ifile;
-  GFile *gfile;
-
-  g_assert (IDE_IS_MAIN_THREAD ());
-  g_assert (IDE_IS_DIAGNOSTICS_MANAGER (self));
-  g_assert (IDE_IS_BUFFER (buffer));
-  g_assert (pspec != NULL);
-  g_assert (g_str_equal (pspec->name, "file"));
-
-  ifile = ide_buffer_get_file (buffer);
-  gfile = ide_file_get_file (ifile);
-
-  g_assert (IDE_IS_FILE (ifile));
-  g_assert (G_IS_FILE (gfile));
-
-  ide_diagnostics_manager_update_group_by_file (self, buffer, gfile);
-}
-
-static void
-ide_diagnostics_manager_buffer_loaded (IdeDiagnosticsManager *self,
-                                       IdeBuffer             *buffer,
-                                       IdeBufferManager      *buffer_manager)
-{
-  IdeDiagnosticsGroup *group;
-  IdeContext *context;
-  IdeFile *ifile;
-  GFile *gfile;
-
-  IDE_ENTRY;
-
-  g_assert (IDE_IS_MAIN_THREAD ());
-  g_assert (IDE_IS_DIAGNOSTICS_MANAGER (self));
-  g_assert (IDE_IS_BUFFER (buffer));
-  g_assert (IDE_IS_BUFFER_MANAGER (buffer_manager));
-
-  /*
-   * The goal below is to setup all of our state needed for tracking
-   * diagnostics during the lifetime of the buffer. That includes tracking
-   * a few properties to update our providers at runtime, along with
-   * lifecycle tracking. At the end, we fire off a diagnosis so that we
-   * have up to date diagnostics as soon as we can.
-   */
-
-  context = ide_object_get_context (IDE_OBJECT (self));
-
-  g_signal_connect_object (buffer,
-                           "changed",
-                           G_CALLBACK (ide_diagnostics_manager_buffer_changed),
-                           self,
-                           G_CONNECT_SWAPPED);
-
-  g_signal_connect_object (buffer,
-                           "notify::file",
-                           G_CALLBACK (ide_diagnostics_manager_buffer_notify_file),
-                           self,
-                           G_CONNECT_SWAPPED);
-
-  g_signal_connect_object (buffer,
-                           "notify::language",
-                           G_CALLBACK (ide_diagnostics_manager_buffer_notify_language),
-                           self,
-                           G_CONNECT_SWAPPED);
-
-  ifile = ide_buffer_get_file (buffer);
-  gfile = ide_file_get_file (ifile);
-
-  g_assert (IDE_IS_FILE (ifile));
-  g_assert (G_IS_FILE (gfile));
-
-  group = g_hash_table_lookup (self->groups_by_file, gfile);
-
-  if (group == NULL)
-    {
-      group = ide_diagnostics_group_new (gfile);
-      g_hash_table_insert (self->groups_by_file, group->file, group);
-    }
-
-  g_assert (group != NULL);
-  g_assert (IS_DIAGNOSTICS_GROUP (group));
-
-  g_weak_ref_set (&group->buffer_wr, buffer);
-
-  if (group->diagnostics_by_provider == NULL)
-    {
-      group->diagnostics_by_provider =
-        g_hash_table_new_full (NULL, NULL, NULL, free_diagnostics);
-    }
-
-  if (group->adapter == NULL)
-    {
-      const gchar *language_id = ide_buffer_get_language_id (buffer);
-
-      group->adapter = ide_extension_set_adapter_new (context,
-                                                      peas_engine_get_default (),
-                                                      IDE_TYPE_DIAGNOSTIC_PROVIDER,
-                                                      "Diagnostic-Provider-Languages",
-                                                      language_id);
-
-      g_signal_connect_object (group->adapter,
-                               "extension-added",
-                               G_CALLBACK (ide_diagnostics_manager_extension_added),
-                               self,
-                               0);
-
-      g_signal_connect_object (group->adapter,
-                               "extension-removed",
-                               G_CALLBACK (ide_diagnostics_manager_extension_removed),
-                               self,
-                               0);
-
-      ide_extension_set_adapter_foreach (group->adapter,
-                                         ide_diagnostics_manager_extension_added,
-                                         self);
-    }
-
-  g_assert (IDE_IS_EXTENSION_SET_ADAPTER (group->adapter));
-  g_assert (g_hash_table_lookup (self->groups_by_file, gfile) == group);
-
-  ide_diagnostics_group_queue_diagnose (group, self);
-
-  IDE_EXIT;
-}
-
-static void
-ide_diagnostics_manager_buffer_unloaded (IdeDiagnosticsManager *self,
-                                         IdeBuffer             *buffer,
-                                         IdeBufferManager      *buffer_manager)
-{
-  IdeDiagnosticsGroup *group;
-  gboolean has_diagnostics;
-
-  IDE_ENTRY;
-
-  g_assert (IDE_IS_MAIN_THREAD ());
-  g_assert (IDE_IS_DIAGNOSTICS_MANAGER (self));
-  g_assert (IDE_IS_BUFFER (buffer));
-  g_assert (IDE_IS_BUFFER_MANAGER (buffer_manager));
-
-  /*
-   * The goal here is to cleanup everything we can about this group that
-   * is part of a loaded buffer. We might want to keep the group around
-   * in case it is useful from other providers.
-   */
-
-  group = ide_diagnostics_manager_find_group_from_buffer (self, buffer);
-
-  g_assert (group != NULL);
-  g_assert (IS_DIAGNOSTICS_GROUP (group));
-
-  /*
-   * We track if we have diagnostics now so that after we unload the
-   * the providers, we can save that bit for later.
-   */
-  has_diagnostics = ide_diagnostics_group_has_diagnostics (group);
-
-  /*
-   * Force our diagnostic providers to unload. This will cause them
-   * extension-removed signal to be called for each provider which
-   * in turn will perform per-provider cleanup including the removal
-   * of its diagnostics from all groups. (A provider can in practice
-   * affect another group since a .c file could create a diagnostic
-   * for a .h).
-   */
-  g_clear_object (&group->adapter);
-
-  /*
-   * Even after unloading the diagnostic providers, we might still have
-   * diagnostics that were created from other files (this could happen when
-   * one diagnostic is created for a header from a source file). So we don't
-   * want to wipe out the hashtable unless everything was unloaded. The other
-   * provider will cleanup during its own destruction.
-   */
-  if (group->diagnostics_by_provider != NULL &&
-      g_hash_table_size (group->diagnostics_by_provider) == 0)
-    g_clear_pointer (&group->diagnostics_by_provider, g_hash_table_unref);
-
-  g_signal_handlers_disconnect_by_func (buffer,
-                                        G_CALLBACK (ide_diagnostics_manager_buffer_changed),
-                                        self);
-
-  g_signal_handlers_disconnect_by_func (buffer,
-                                        G_CALLBACK (ide_diagnostics_manager_buffer_notify_file),
-                                        self);
-
-  g_signal_handlers_disconnect_by_func (buffer,
-                                        G_CALLBACK (ide_diagnostics_manager_buffer_notify_language),
-                                        self);
-
-  g_weak_ref_set (&group->buffer_wr, NULL);
-
-  group->has_diagnostics = has_diagnostics;
-
-  IDE_EXIT;
-}
-
-static gboolean
-ide_diagnostics_manager_initable_init (GInitable     *initable,
-                                       GCancellable  *cancellable,
-                                       GError       **error)
-{
-  IdeDiagnosticsManager *self = (IdeDiagnosticsManager *)initable;
-  IdeBufferManager *buffer_manager;
-  IdeContext *context;
-  guint n_items;
-
-  IDE_ENTRY;
-
-  g_assert (IDE_IS_MAIN_THREAD ());
-  g_assert (IDE_IS_DIAGNOSTICS_MANAGER (self));
-  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
-
-  context = ide_object_get_context (IDE_OBJECT (self));
-  buffer_manager = ide_context_get_buffer_manager (context);
-
-  /* We can start processing things when the buffer is loaded,
-   * but we do so after buffer-loaded because we don't really
-   * care to be before other subscribers. Our plugins might be
-   * dependent on other things that are buffer specific.
-   */
-  g_signal_connect_object (buffer_manager,
-                           "buffer-loaded",
-                           G_CALLBACK (ide_diagnostics_manager_buffer_loaded),
-                           self,
-                           G_CONNECT_SWAPPED | G_CONNECT_AFTER);
-
-  g_signal_connect_object (buffer_manager,
-                           "buffer-unloaded",
-                           G_CALLBACK (ide_diagnostics_manager_buffer_unloaded),
-                           self,
-                           G_CONNECT_SWAPPED);
-
-  n_items = g_list_model_get_n_items (G_LIST_MODEL (buffer_manager));
-
-  for (guint i = 0; i < n_items; i++)
-    {
-      g_autoptr(IdeBuffer) buffer = NULL;
-
-      buffer = g_list_model_get_item (G_LIST_MODEL (buffer_manager), i);
-      ide_diagnostics_manager_buffer_loaded (self, buffer, buffer_manager);
-    }
-
-  IDE_RETURN (TRUE);
-}
-
-static void
-initable_iface_init (GInitableIface *iface)
-{
-  iface->init = ide_diagnostics_manager_initable_init;
-}
-
 /**
  * ide_diagnostics_manager_get_busy:
  *
@@ -1295,7 +902,7 @@ ide_diagnostics_manager_get_diagnostics_for_file (IdeDiagnosticsManager *self,
   g_return_val_if_fail (IDE_IS_DIAGNOSTICS_MANAGER (self), NULL);
   g_return_val_if_fail (G_IS_FILE (file), NULL);
 
-  ret = ide_diagnostics_new (NULL);
+  ret = ide_diagnostics_new ();
 
   group = g_hash_table_lookup (self->groups_by_file, file);
 
@@ -1314,12 +921,13 @@ ide_diagnostics_manager_get_diagnostics_for_file (IdeDiagnosticsManager *self,
           if (diagnostics == NULL)
             continue;
 
-          length = ide_diagnostics_get_size (diagnostics);
+          length = g_list_model_get_n_items (G_LIST_MODEL (diagnostics));
 
           for (guint i = 0; i < length; i++)
             {
-              IdeDiagnostic *diagnostic = ide_diagnostics_index (diagnostics, i);
+              g_autoptr(IdeDiagnostic) diagnostic = NULL;
 
+              diagnostic = g_list_model_get_item (G_LIST_MODEL (diagnostics), i);
               ide_diagnostics_add (ret, diagnostic);
             }
         }
@@ -1362,20 +970,208 @@ ide_diagnostics_manager_get_sequence_for_file (IdeDiagnosticsManager *self,
  * You may want to call this if you changed something that a buffer depends on,
  * and want to seamlessly update its diagnostics with that updated information.
  *
- * Internally, this is the same as @buffer emitting the #IdeBuffer::changed
- * signal.
- *
  * Since: 3.32
  */
 void
 ide_diagnostics_manager_rediagnose (IdeDiagnosticsManager *self,
                                     IdeBuffer             *buffer)
 {
+  IdeDiagnosticsGroup *group;
+  GFile *file;
+
   g_return_if_fail (IDE_IS_MAIN_THREAD ());
   g_return_if_fail (IDE_IS_DIAGNOSTICS_MANAGER (self));
   g_return_if_fail (IDE_IS_BUFFER (buffer));
-  g_return_if_fail (ide_buffer_get_context (buffer) ==
-                    ide_object_get_context (IDE_OBJECT (self)));
 
-  ide_diagnostics_manager_buffer_changed (self, buffer);
+  file = ide_buffer_get_file (buffer);
+  group = ide_diagnostics_manager_find_group (self, file);
+
+  ide_diagnostics_group_queue_diagnose (group, self);
+}
+
+/**
+ * ide_diagnostics_manager_from_context:
+ * @context: an #IdeContext
+ *
+ * Gets the diagnostics manager for the context.
+ *
+ * Returns: (transfer none): an #IdeDiagnosticsManager
+ *
+ * Since: 3.32
+ */
+IdeDiagnosticsManager *
+ide_diagnostics_manager_from_context (IdeContext *context)
+{
+  IdeDiagnosticsManager *self;
+
+  g_return_val_if_fail (IDE_IS_CONTEXT (context), NULL);
+
+  ide_object_lock (IDE_OBJECT (context));
+  if (!(self = ide_context_peek_child_typed (context, IDE_TYPE_DIAGNOSTICS_MANAGER)))
+    {
+      g_autoptr(IdeDiagnosticsManager) created = NULL;
+      created = ide_object_ensure_child_typed (IDE_OBJECT (context),
+                                               IDE_TYPE_DIAGNOSTICS_MANAGER);
+      self = ide_context_peek_child_typed (context, IDE_TYPE_DIAGNOSTICS_MANAGER);
+    }
+  ide_object_unlock (IDE_OBJECT (context));
+
+  return self;
+}
+
+void
+_ide_diagnostics_manager_file_closed (IdeDiagnosticsManager *self,
+                                      GFile                 *file)
+{
+  IdeDiagnosticsGroup *group;
+  gboolean has_diagnostics;
+
+  IDE_ENTRY;
+
+  g_return_if_fail (IDE_IS_MAIN_THREAD ());
+  g_return_if_fail (IDE_IS_DIAGNOSTICS_MANAGER (self));
+  g_return_if_fail (G_IS_FILE (file));
+
+  /*
+   * The goal here is to cleanup everything we can about this group that
+   * is part of a loaded buffer. We might want to keep the group around
+   * in case it is useful from other providers.
+   */
+
+  group = ide_diagnostics_manager_find_group (self, file);
+
+  g_assert (group != NULL);
+  g_assert (IS_DIAGNOSTICS_GROUP (group));
+
+  /* Clear some state we've been tracking */
+  g_clear_pointer (&group->contents, g_bytes_unref);
+  group->lang_id = NULL;
+  group->needs_diagnose = FALSE;
+
+  /*
+   * We track if we have diagnostics now so that after we unload the
+   * the providers, we can save that bit for later.
+   */
+  has_diagnostics = ide_diagnostics_group_has_diagnostics (group);
+
+  /*
+   * Force our diagnostic providers to unload. This will cause them
+   * extension-removed signal to be called for each provider which
+   * in turn will perform per-provider cleanup including the removal
+   * of its diagnostics from all groups. (A provider can in practice
+   * affect another group since a .c file could create a diagnostic
+   * for a .h).
+   */
+  ide_clear_and_destroy_object (&group->adapter);
+
+  /*
+   * Even after unloading the diagnostic providers, we might still have
+   * diagnostics that were created from other files (this could happen when
+   * one diagnostic is created for a header from a source file). So we don't
+   * want to wipe out the hashtable unless everything was unloaded. The other
+   * provider will cleanup during its own destruction.
+   */
+  if (group->diagnostics_by_provider != NULL &&
+      g_hash_table_size (group->diagnostics_by_provider) == 0)
+    g_clear_pointer (&group->diagnostics_by_provider, g_hash_table_unref);
+
+  group->has_diagnostics = has_diagnostics;
+
+  IDE_EXIT;
+}
+
+void
+_ide_diagnostics_manager_file_changed (IdeDiagnosticsManager *self,
+                                       GFile                 *file,
+                                       GBytes                *contents,
+                                       const gchar           *lang_id)
+{
+  IdeDiagnosticsGroup *group;
+
+  g_return_if_fail (IDE_IS_MAIN_THREAD ());
+  g_return_if_fail (IDE_IS_DIAGNOSTICS_MANAGER (self));
+  g_return_if_fail (G_IS_FILE (file));
+
+  group = ide_diagnostics_manager_find_group (self, file);
+
+  g_clear_pointer (&group->contents, g_bytes_unref);
+
+  group->lang_id = g_intern_string (lang_id);
+  group->contents = contents ? g_bytes_ref (contents) : NULL;
+
+  ide_diagnostics_group_queue_diagnose (group, self);
+}
+
+void
+_ide_diagnostics_manager_language_changed (IdeDiagnosticsManager *self,
+                                           GFile                 *file,
+                                           const gchar           *lang_id)
+{
+  IdeDiagnosticsGroup *group;
+
+  g_return_if_fail (IDE_IS_MAIN_THREAD ());
+  g_return_if_fail (IDE_IS_DIAGNOSTICS_MANAGER (self));
+
+  group = ide_diagnostics_manager_find_group (self, file);
+  group->lang_id = g_intern_string (lang_id);
+
+  if (group->adapter != NULL)
+    ide_extension_set_adapter_set_value (group->adapter, lang_id);
+
+  ide_diagnostics_group_queue_diagnose (group, self);
+}
+
+void
+_ide_diagnostics_manager_file_opened (IdeDiagnosticsManager *self,
+                                      GFile                 *file,
+                                      const gchar           *lang_id)
+{
+  IdeDiagnosticsGroup *group;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_DIAGNOSTICS_MANAGER (self));
+  g_assert (G_IS_FILE (file));
+
+  group = ide_diagnostics_manager_find_group (self, file);
+
+  if (group->diagnostics_by_provider == NULL)
+    group->diagnostics_by_provider =
+      g_hash_table_new_full (NULL, NULL, NULL, free_diagnostics);
+
+  group->lang_id = g_intern_string (lang_id);
+
+  if (group->adapter == NULL)
+    {
+      group->adapter = ide_extension_set_adapter_new (IDE_OBJECT (self),
+                                                      peas_engine_get_default (),
+                                                      IDE_TYPE_DIAGNOSTIC_PROVIDER,
+                                                      "Diagnostic-Provider-Languages",
+                                                      lang_id);
+
+      g_signal_connect_object (group->adapter,
+                               "extension-added",
+                               G_CALLBACK (ide_diagnostics_manager_extension_added),
+                               self,
+                               0);
+
+      g_signal_connect_object (group->adapter,
+                               "extension-removed",
+                               G_CALLBACK (ide_diagnostics_manager_extension_removed),
+                               self,
+                               0);
+
+      ide_extension_set_adapter_foreach (group->adapter,
+                                         ide_diagnostics_manager_extension_added,
+                                         self);
+    }
+
+  g_assert (IDE_IS_EXTENSION_SET_ADAPTER (group->adapter));
+  g_assert (g_hash_table_lookup (self->groups_by_file, file) == group);
+
+  ide_diagnostics_group_queue_diagnose (group, self);
+
+  IDE_EXIT;
 }
+
diff --git a/src/libide/diagnostics/ide-diagnostics-manager.h b/src/libide/code/ide-diagnostics-manager.h
similarity index 83%
rename from src/libide/diagnostics/ide-diagnostics-manager.h
rename to src/libide/code/ide-diagnostics-manager.h
index 9f3451605..2653b43d9 100644
--- a/src/libide/diagnostics/ide-diagnostics-manager.h
+++ b/src/libide/code/ide-diagnostics-manager.h
@@ -20,11 +20,9 @@
 
 #pragma once
 
-#include <gio/gio.h>
+#include <libide-core.h>
 
-#include "ide-version-macros.h"
-
-#include "ide-object.h"
+#include "ide-code-types.h"
 
 G_BEGIN_DECLS
 
@@ -33,6 +31,8 @@ G_BEGIN_DECLS
 IDE_AVAILABLE_IN_3_32
 G_DECLARE_FINAL_TYPE (IdeDiagnosticsManager, ide_diagnostics_manager, IDE, DIAGNOSTICS_MANAGER, IdeObject)
 
+IDE_AVAILABLE_IN_3_32
+IdeDiagnosticsManager *ide_diagnostics_manager_from_context (IdeContext *context);
 IDE_AVAILABLE_IN_3_32
 gboolean        ide_diagnostics_manager_get_busy                 (IdeDiagnosticsManager *self);
 IDE_AVAILABLE_IN_3_32
@@ -42,10 +42,6 @@ IDE_AVAILABLE_IN_3_32
 guint           ide_diagnostics_manager_get_sequence_for_file    (IdeDiagnosticsManager *self,
                                                                   GFile                 *file);
 IDE_AVAILABLE_IN_3_32
-void            ide_diagnostics_manager_update_group_by_file     (IdeDiagnosticsManager *self,
-                                                                  IdeBuffer             *buffer,
-                                                                  GFile                 *new_file);
-IDE_AVAILABLE_IN_3_32
 void            ide_diagnostics_manager_rediagnose               (IdeDiagnosticsManager *self,
                                                                   IdeBuffer             *buffer);
 
diff --git a/src/libide/code/ide-diagnostics.c b/src/libide/code/ide-diagnostics.c
new file mode 100644
index 000000000..0bb462465
--- /dev/null
+++ b/src/libide/code/ide-diagnostics.c
@@ -0,0 +1,506 @@
+/* ide-diagnostics.c
+ *
+ * Copyright 2015-2019 Christian Hergert <chergert redhat com>
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-diagnostics"
+
+#include "config.h"
+
+#include "ide-diagnostic.h"
+#include "ide-diagnostics.h"
+#include "ide-location.h"
+
+typedef struct
+{
+  GPtrArray  *items;
+  GHashTable *caches;
+  guint       n_warnings;
+  guint       n_errors;
+} IdeDiagnosticsPrivate;
+
+typedef struct
+{
+  gint                  line : 28;
+  IdeDiagnosticSeverity severity : 4;
+} IdeDiagnosticsCacheLine;
+
+typedef struct
+{
+  GFile  *file;
+  GArray *lines;
+} IdeDiagnosticsCache;
+
+typedef struct
+{
+  guint begin;
+  guint end;
+} LookupKey;
+
+enum {
+  PROP_0,
+  PROP_HAS_ERRORS,
+  PROP_HAS_WARNINGS,
+  PROP_N_ERRORS,
+  PROP_N_WARNINGS,
+  N_PROPS
+};
+
+static void list_model_iface_init (GListModelInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (IdeDiagnostics, ide_diagnostics, IDE_TYPE_OBJECT,
+                         G_ADD_PRIVATE (IdeDiagnostics)
+                         G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init))
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+ide_diagnostics_cache_free (gpointer data)
+{
+  IdeDiagnosticsCache *cache = data;
+
+  g_clear_object (&cache->file);
+  g_clear_pointer (&cache->lines, g_array_unref);
+  g_slice_free (IdeDiagnosticsCache, cache);
+}
+
+static void
+ide_diagnostics_finalize (GObject *object)
+{
+  IdeDiagnostics *self = (IdeDiagnostics *)object;
+  IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
+
+  g_clear_pointer (&priv->items, g_ptr_array_unref);
+  g_clear_pointer (&priv->caches, g_hash_table_unref);
+
+  G_OBJECT_CLASS (ide_diagnostics_parent_class)->finalize (object);
+}
+
+static void
+ide_diagnostics_get_property (GObject    *object,
+                              guint       prop_id,
+                              GValue     *value,
+                              GParamSpec *pspec)
+{
+  IdeDiagnostics *self = IDE_DIAGNOSTICS (object);
+
+  switch (prop_id)
+    {
+    case PROP_HAS_WARNINGS:
+      g_value_set_boolean (value, ide_diagnostics_get_has_warnings (self));
+      break;
+
+    case PROP_HAS_ERRORS:
+      g_value_set_boolean (value, ide_diagnostics_get_has_errors (self));
+      break;
+
+    case PROP_N_ERRORS:
+      g_value_set_uint (value, ide_diagnostics_get_n_errors (self));
+      break;
+
+    case PROP_N_WARNINGS:
+      g_value_set_uint (value, ide_diagnostics_get_n_warnings (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_diagnostics_class_init (IdeDiagnosticsClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_diagnostics_finalize;
+  object_class->get_property = ide_diagnostics_get_property;
+
+  properties [PROP_HAS_WARNINGS] =
+    g_param_spec_boolean ("has-warnings",
+                         "Has Warnings",
+                         "If there are any warnings in the diagnostic set",
+                         FALSE,
+                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_HAS_ERRORS] =
+    g_param_spec_boolean ("has-errors",
+                         "Has Errors",
+                         "If there are any errors in the diagnostic set",
+                         FALSE,
+                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_N_WARNINGS] =
+    g_param_spec_uint ("n-warnings",
+                       "N Warnings",
+                       "Number of warnings in diagnostic set",
+                       0, G_MAXUINT, 0,
+                       (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_N_ERRORS] =
+    g_param_spec_uint ("n-errors",
+                       "N Errors",
+                       "Number of errors in diagnostic set",
+                       0, G_MAXUINT, 0,
+                       (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_diagnostics_init (IdeDiagnostics *self)
+{
+  IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
+
+  priv->items = g_ptr_array_new_with_free_func (g_object_unref);
+}
+
+IdeDiagnostics *
+ide_diagnostics_new (void)
+{
+  return g_object_new (IDE_TYPE_DIAGNOSTICS, NULL);
+}
+
+void
+ide_diagnostics_add (IdeDiagnostics *self,
+                     IdeDiagnostic  *diagnostic)
+{
+  g_return_if_fail (IDE_IS_DIAGNOSTICS (self));
+  g_return_if_fail (IDE_IS_DIAGNOSTIC (diagnostic));
+
+  ide_diagnostics_take (self, g_object_ref (diagnostic));
+}
+
+void
+ide_diagnostics_take (IdeDiagnostics *self,
+                      IdeDiagnostic  *diagnostic)
+{
+  IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
+  IdeDiagnosticSeverity severity;
+  guint position;
+
+  g_return_if_fail (IDE_IS_DIAGNOSTICS (self));
+  g_return_if_fail (IDE_IS_DIAGNOSTIC (diagnostic));
+
+  severity = ide_diagnostic_get_severity (diagnostic);
+
+  position = priv->items->len;
+  g_ptr_array_add (priv->items, g_steal_pointer (&diagnostic));
+  g_list_model_items_changed (G_LIST_MODEL (self), position, 0, 1);
+
+  switch (severity)
+    {
+    case IDE_DIAGNOSTIC_ERROR:
+    case IDE_DIAGNOSTIC_FATAL:
+      priv->n_errors++;
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_HAS_ERRORS]);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_N_ERRORS]);
+      break;
+
+    case IDE_DIAGNOSTIC_WARNING:
+    case IDE_DIAGNOSTIC_DEPRECATED:
+      priv->n_warnings++;
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_HAS_WARNINGS]);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_N_WARNINGS]);
+      break;
+
+    case IDE_DIAGNOSTIC_IGNORED:
+    case IDE_DIAGNOSTIC_NOTE:
+    default:
+      break;
+    }
+}
+
+void
+ide_diagnostics_merge (IdeDiagnostics *self,
+                       IdeDiagnostics *other)
+{
+  IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
+  IdeDiagnosticsPrivate *other_priv = ide_diagnostics_get_instance_private (other);
+  guint position;
+
+  g_return_if_fail (IDE_IS_DIAGNOSTICS (self));
+  g_return_if_fail (IDE_IS_DIAGNOSTICS (other));
+
+  position = priv->items->len;
+
+  for (guint i = 0; i < other_priv->items->len; i++)
+    {
+      IdeDiagnostic *diagnostic = g_ptr_array_index (other_priv->items, i);
+      ide_diagnostics_take (self, g_object_ref (diagnostic));
+    }
+
+  g_list_model_items_changed (G_LIST_MODEL (self), position, 0, other_priv->items->len);
+}
+
+gboolean
+ide_diagnostics_get_has_errors (IdeDiagnostics *self)
+{
+  IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DIAGNOSTICS (self), FALSE);
+
+  return priv->n_errors > 0;
+}
+
+guint
+ide_diagnostics_get_n_errors (IdeDiagnostics *self)
+{
+  IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DIAGNOSTICS (self), 0);
+
+  return priv->n_errors;
+}
+
+gboolean
+ide_diagnostics_get_has_warnings (IdeDiagnostics *self)
+{
+  IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DIAGNOSTICS (self), FALSE);
+
+  return priv->n_warnings > 0;
+}
+
+guint
+ide_diagnostics_get_n_warnings (IdeDiagnostics *self)
+{
+  IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DIAGNOSTICS (self), 0);
+
+  return priv->n_warnings;
+}
+
+static GType
+ide_diagnostics_get_item_type (GListModel *model)
+{
+  return IDE_TYPE_DIAGNOSTIC;
+}
+
+static guint
+ide_diagnostics_get_n_items (GListModel *model)
+{
+  IdeDiagnostics *self = (IdeDiagnostics *)model;
+  IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DIAGNOSTICS (self), 0);
+
+  return priv->items->len;
+}
+
+static gpointer
+ide_diagnostics_get_item (GListModel *model,
+                          guint       position)
+{
+  IdeDiagnostics *self = (IdeDiagnostics *)model;
+  IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DIAGNOSTICS (self), NULL);
+
+  if (position < priv->items->len)
+    return g_object_ref (g_ptr_array_index (priv->items, position));
+
+  return NULL;
+}
+
+static void
+list_model_iface_init (GListModelInterface *iface)
+{
+  iface->get_n_items = ide_diagnostics_get_n_items;
+  iface->get_item_type = ide_diagnostics_get_item_type;
+  iface->get_item = ide_diagnostics_get_item;
+}
+
+static gint
+compare_lines (gconstpointer a,
+               gconstpointer b)
+{
+  const IdeDiagnosticsCacheLine *line_a = a;
+  const IdeDiagnosticsCacheLine *line_b = b;
+
+  return line_a->line - line_b->line;
+}
+
+static void
+ide_diagnostics_build_caches (IdeDiagnostics *self)
+{
+  IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
+  g_autoptr(GHashTable) caches = NULL;
+  IdeDiagnosticsCache *cache;
+  GHashTableIter iter;
+  GFile *file;
+
+  g_assert (IDE_IS_DIAGNOSTICS (self));
+  g_assert (priv->caches == NULL);
+
+  caches = g_hash_table_new_full (g_file_hash,
+                                  (GEqualFunc)g_file_equal,
+                                  g_object_unref,
+                                  ide_diagnostics_cache_free);
+
+  for (guint i = 0; i < priv->items->len; i++)
+    {
+      IdeDiagnostic *diag = g_ptr_array_index (priv->items, i);
+      IdeDiagnosticsCacheLine val;
+      IdeLocation *location;
+
+      if (!(file = ide_diagnostic_get_file (diag)))
+        continue;
+
+      if (!(location = ide_diagnostic_get_location (diag)))
+        continue;
+
+      if (!(cache = g_hash_table_lookup (caches, file)))
+        {
+          cache = g_slice_new0 (IdeDiagnosticsCache);
+          cache->file = g_object_ref (file);
+          cache->lines = g_array_new (FALSE, FALSE, sizeof (IdeDiagnosticsCacheLine));
+          g_hash_table_insert (caches, g_object_ref (file), cache);
+        }
+
+      val.severity = ide_diagnostic_get_severity (diag);
+      val.line = ide_location_get_line (location);
+
+      g_array_append_val (cache->lines, val);
+    }
+
+  g_hash_table_iter_init (&iter, caches);
+
+  while (g_hash_table_iter_next (&iter, (gpointer *)&file, (gpointer *)&cache))
+    g_array_sort (cache->lines, compare_lines);
+
+  priv->caches = g_steal_pointer (&caches);
+}
+
+/**
+ * ide_diagnostics_foreach_line_in_range:
+ * @self: an #IdeDiagnostics
+ * @file: a #GFile
+ * @begin_line: the starting line
+ * @end_line: the ending line
+ * @callback: (scope call): a callback to execute for each matching line
+ * @user_data: user data for @callback
+ *
+ * This function calls @callback for every line with diagnostics between
+ * @begin_line and @end_line. This is useful when drawing information about
+ * diagnostics in an editor where a known number of lines are visible.
+ *
+ * Since: 3.32
+ */
+void
+ide_diagnostics_foreach_line_in_range (IdeDiagnostics             *self,
+                                       GFile                      *file,
+                                       guint                       begin_line,
+                                       guint                       end_line,
+                                       IdeDiagnosticsLineCallback  callback,
+                                       gpointer                    user_data)
+{
+  IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
+  const IdeDiagnosticsCache *cache;
+
+  g_return_if_fail (IDE_IS_DIAGNOSTICS (self));
+  g_return_if_fail (G_IS_FILE (file));
+
+  if (priv->items->len == 0)
+    return;
+
+  if (priv->caches == NULL)
+    ide_diagnostics_build_caches (self);
+
+  if (!(cache = g_hash_table_lookup (priv->caches, file)))
+    return;
+
+  for (guint i = 0; i < cache->lines->len; i++)
+    {
+      const IdeDiagnosticsCacheLine *line = &g_array_index (cache->lines, IdeDiagnosticsCacheLine, i);
+
+      if (line->line < begin_line)
+        continue;
+
+      if (line->line > end_line)
+        break;
+
+      callback (line->line, line->severity, user_data);
+    }
+}
+
+/**
+ * ide_diagnostics_get_diagnostic_at_line:
+ * @self: a #IdeDiagnostics
+ * @file: the target file
+ * @line: a line number
+ *
+ * Locates an #IdeDiagnostic in @file at @line.
+ *
+ * Returns: (transfer none) (nullable): an #IdeDiagnostic or %NULL
+ *
+ * Since: 3.32
+ */
+IdeDiagnostic *
+ide_diagnostics_get_diagnostic_at_line (IdeDiagnostics *self,
+                                        GFile          *file,
+                                        guint           line)
+{
+  IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_DIAGNOSTICS (self), NULL);
+  g_return_val_if_fail (G_IS_FILE (file), NULL);
+
+  for (guint i = 0; i < priv->items->len; i++)
+    {
+      IdeDiagnostic *diag = g_ptr_array_index (priv->items, i);
+      IdeLocation *loc = ide_diagnostic_get_location (diag);
+      GFile *loc_file;
+      guint loc_line;
+
+      if (loc == NULL)
+        continue;
+
+      loc_file = ide_location_get_file (loc);
+      loc_line = ide_location_get_line (loc);
+
+      if (loc_line == line && g_file_equal (file, loc_file))
+        return diag;
+    }
+
+  return NULL;
+}
+
+/**
+ * ide_diagnostics_new_from_array:
+ * @array: (nullable) (element-type IdeDiagnostic): optional array
+ *   of diagnostics to add.
+ *
+ * Returns: (transfer full): an #IdeDiagnostics
+ *
+ * Since: 3.32
+ */
+IdeDiagnostics *
+ide_diagnostics_new_from_array (GPtrArray *array)
+{
+  IdeDiagnostics *ret = ide_diagnostics_new ();
+
+  if (array != NULL)
+    {
+      for (guint i = 0; i < array->len; i++)
+        ide_diagnostics_add (ret, g_ptr_array_index (array, i));
+    }
+
+  return g_steal_pointer (&ret);
+}
diff --git a/src/libide/code/ide-diagnostics.h b/src/libide/code/ide-diagnostics.h
new file mode 100644
index 000000000..258dda731
--- /dev/null
+++ b/src/libide/code/ide-diagnostics.h
@@ -0,0 +1,97 @@
+/* ide-diagnostics.h
+ *
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#if !defined (IDE_CODE_INSIDE) && !defined (IDE_CODE_COMPILATION)
+# error "Only <libide-code.h> can be included directly."
+#endif
+
+#include <libide-core.h>
+
+#include "ide-code-types.h"
+#include "ide-diagnostic.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_DIAGNOSTICS (ide_diagnostics_get_type())
+
+IDE_AVAILABLE_IN_3_32
+G_DECLARE_DERIVABLE_TYPE (IdeDiagnostics, ide_diagnostics, IDE, DIAGNOSTICS, IdeObject)
+
+/**
+ * IdeDiagnosticsLineCallback:
+ * @line: the line number, starting from 0
+ * @severity: the severity of the diagnostic
+ * @user_data: user data provided with callback
+ *
+ * This function prototype is used to notify a caller of every line that has a
+ * diagnostic, and the most severe #IdeDiagnosticSeverity for that line.
+ *
+ * Since: 3.32
+ */
+typedef void (*IdeDiagnosticsLineCallback) (guint                 line,
+                                            IdeDiagnosticSeverity severity,
+                                            gpointer              user_data);
+
+struct _IdeDiagnosticsClass
+{
+  IdeObjectClass parent_class;
+
+  /*< private >*/
+  gpointer _reserved[16];
+};
+
+IDE_AVAILABLE_IN_3_32
+IdeDiagnostics        *ide_diagnostics_new                   (void);
+IDE_AVAILABLE_IN_3_32
+IdeDiagnostics        *ide_diagnostics_new_from_array        (GPtrArray                  *array);
+IDE_AVAILABLE_IN_3_32
+void                   ide_diagnostics_add                   (IdeDiagnostics             *self,
+                                                              IdeDiagnostic              *diagnostic);
+IDE_AVAILABLE_IN_3_32
+void                   ide_diagnostics_take                  (IdeDiagnostics             *self,
+                                                              IdeDiagnostic              *diagnostic);
+IDE_AVAILABLE_IN_3_32
+void                   ide_diagnostics_merge                 (IdeDiagnostics             *self,
+                                                              IdeDiagnostics             *other);
+IDE_AVAILABLE_IN_3_32
+guint                  ide_diagnostics_get_n_errors          (IdeDiagnostics             *self);
+IDE_AVAILABLE_IN_3_32
+gboolean               ide_diagnostics_get_has_errors        (IdeDiagnostics             *self);
+IDE_AVAILABLE_IN_3_32
+guint                  ide_diagnostics_get_n_warnings        (IdeDiagnostics             *self);
+IDE_AVAILABLE_IN_3_32
+gboolean               ide_diagnostics_get_has_warnings      (IdeDiagnostics             *self);
+IDE_AVAILABLE_IN_3_32
+void                   ide_diagnostics_foreach_line_in_range (IdeDiagnostics             *self,
+                                                              GFile                      *file,
+                                                              guint                       begin_line,
+                                                              guint                       end_line,
+                                                              IdeDiagnosticsLineCallback  callback,
+                                                              gpointer                    user_data);
+IDE_AVAILABLE_IN_3_32
+IdeDiagnostic         *ide_diagnostics_get_diagnostic_at_line (IdeDiagnostics             *self,
+                                                               GFile                      *file,
+                                                               guint                       line);
+
+#define ide_diagnostics_get_size(d) ((gsize)g_list_model_get_n_items(G_LIST_MODEL(d)))
+
+G_END_DECLS
diff --git a/src/libide/util/ide-doc-seq.h b/src/libide/code/ide-doc-seq-private.h
similarity index 97%
rename from src/libide/util/ide-doc-seq.h
rename to src/libide/code/ide-doc-seq-private.h
index 78535768d..e426333df 100644
--- a/src/libide/util/ide-doc-seq.h
+++ b/src/libide/code/ide-doc-seq-private.h
@@ -1,4 +1,4 @@
-/* ide-doc-seq.h
+/* ide-doc-seq-private.h
  *
  * Copyright 2014-2019 Christian Hergert <christian hergert me>
  *
diff --git a/src/libide/util/ide-doc-seq.c b/src/libide/code/ide-doc-seq.c
similarity index 89%
rename from src/libide/util/ide-doc-seq.c
rename to src/libide/code/ide-doc-seq.c
index 7a4f4410b..3e307f53d 100644
--- a/src/libide/util/ide-doc-seq.c
+++ b/src/libide/code/ide-doc-seq.c
@@ -1,6 +1,6 @@
 /* ide-doc-seq.c
  *
- * Copyright 2014-2019 Christian Hergert <christian hergert me>
+ * Copyright 2014-2019 Christian Hergert <chergert redhat com>
  *
  * 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
@@ -18,7 +18,11 @@
  * SPDX-License-Identifier: GPL-3.0-or-later
  */
 
-#include "util/ide-doc-seq.h"
+#define G_LOG_DOMAIN "ide-doc-seq"
+
+#include "config.h"
+
+#include "ide-doc-seq-private.h"
 
 static GHashTable *seq;
 
diff --git a/src/libide/files/ide-file-settings.c b/src/libide/code/ide-file-settings.c
similarity index 79%
rename from src/libide/files/ide-file-settings.c
rename to src/libide/code/ide-file-settings.c
index fcd873d70..97aa85577 100644
--- a/src/libide/files/ide-file-settings.c
+++ b/src/libide/code/ide-file-settings.c
@@ -25,30 +25,28 @@
 #include <glib/gi18n.h>
 #include <gtksourceview/gtksource.h>
 
-#include "dazzle.h"
-
-#include "ide-enums.h"
-#include "files/ide-file.h"
-#include "files/ide-file-settings.h"
+#include "ide-code-enums.h"
+#include "ide-file-settings.h"
 
 /*
  * WARNING: This file heavily uses XMACROS.
  *
- * XMACROS are not as difficult as you might imagine. It's basically just an inverstion
- * of macros. We have a defs file (in this case ide-file-settings.defs) which defines
- * information we need about properties. Then we define the macro called from that defs file
- * to do something we need, then include the .defs file.
+ * XMACROS are not as difficult as you might imagine. It's basically just an
+ * inverstion of macros. We have a defs file (in this case
+ * ide-file-settings.defs) which defines information we need about properties.
+ * Then we define the macro called from that defs file to do something we need,
+ * then include the .defs file.
  *
- * We do that over and over again until we have all the aspects of the object defined.
+ * We do that over and over again until we have all the aspects of the object
+ * defined.
  */
 
-DZL_DEFINE_COUNTER (instances, "IdeFileSettings", "Instances", "Number of IdeFileSettings instances.")
-
 typedef struct
 {
-  GPtrArray *children;
-  IdeFile   *file;
-  guint      unsettled_count;
+  GPtrArray   *children;
+  GFile       *file;
+  const gchar *language;
+  guint        unsettled_count;
 
 #define IDE_FILE_SETTINGS_PROPERTY(_1, name, field_type, _3, _pname, _4, _5, _6) \
   field_type name;
@@ -66,6 +64,7 @@ G_DEFINE_TYPE_WITH_PRIVATE (IdeFileSettings, ide_file_settings, IDE_TYPE_OBJECT)
 enum {
   PROP_0,
   PROP_FILE,
+  PROP_LANGUAGE,
   PROP_SETTLED,
 #define IDE_FILE_SETTINGS_PROPERTY(NAME, _1, _2, _3, _pname, _4, _5, _6) \
   PROP_##NAME, \
@@ -147,7 +146,7 @@ void ide_file_settings_set_##name##_set (IdeFileSettings *self, \
  *
  * Since: 3.32
  */
-IdeFile *
+GFile *
 ide_file_settings_get_file (IdeFileSettings *self)
 {
   IdeFileSettingsPrivate *priv = ide_file_settings_get_instance_private (self);
@@ -159,15 +158,47 @@ ide_file_settings_get_file (IdeFileSettings *self)
 
 static void
 ide_file_settings_set_file (IdeFileSettings *self,
-                            IdeFile         *file)
+                            GFile           *file)
+{
+  IdeFileSettingsPrivate *priv = ide_file_settings_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_FILE_SETTINGS (self));
+  g_return_if_fail (G_IS_FILE (file));
+  g_return_if_fail (priv->file == NULL);
+
+  priv->file = g_object_ref (file);
+}
+
+/**
+ * ide_file_settings_get_language:
+ * @self: a #IdeFileSettings
+ *
+ * If the language for file settings is known up-front, this will indicate
+ * the language identifier known to GtkSourceView such as "c" or "sh".
+ *
+ * Returns: (nullable): a string containing the language id or %NULL
+ *
+ * Since: 3.32
+ */
+const gchar *
+ide_file_settings_get_language (IdeFileSettings *self)
+{
+  IdeFileSettingsPrivate *priv = ide_file_settings_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_FILE_SETTINGS (self), NULL);
+
+  return priv->language;
+}
+
+static void
+ide_file_settings_set_language (IdeFileSettings *self,
+                                const gchar     *language)
 {
   IdeFileSettingsPrivate *priv = ide_file_settings_get_instance_private (self);
 
   g_return_if_fail (IDE_IS_FILE_SETTINGS (self));
-  g_return_if_fail (IDE_IS_FILE (file));
 
-  if (g_set_weak_pointer (&priv->file, file))
-    g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_FILE]);
+  priv->language = g_intern_string (language);
 }
 
 /**
@@ -201,19 +232,39 @@ ide_file_settings_get_settled (IdeFileSettings *self)
   return (priv->unsettled_count == 0);
 }
 
+static gchar *
+ide_file_settings_repr (IdeObject *object)
+{
+  IdeFileSettings *self = (IdeFileSettings *)object;
+  IdeFileSettingsPrivate *priv = ide_file_settings_get_instance_private (self);
+
+  if (priv->file != NULL)
+    {
+      g_autofree gchar *uri = NULL;
+
+      if (g_file_is_native (priv->file))
+        return g_strdup_printf ("%s path=\"%s\"",
+                                G_OBJECT_TYPE_NAME (self),
+                                g_file_peek_path (priv->file));
+
+      uri = g_file_get_uri (priv->file);
+      return g_strdup_printf ("%s uri=\"%s\"", G_OBJECT_TYPE_NAME (self), uri);
+    }
+
+  return IDE_OBJECT_CLASS (ide_file_settings_parent_class)->repr (object);
+}
+
 static void
-ide_file_settings_finalize (GObject *object)
+ide_file_settings_destroy (IdeObject *object)
 {
   IdeFileSettings *self = (IdeFileSettings *)object;
   IdeFileSettingsPrivate *priv = ide_file_settings_get_instance_private (self);
 
   g_clear_pointer (&priv->children, g_ptr_array_unref);
   g_clear_pointer (&priv->encoding, g_free);
-  g_clear_weak_pointer (&priv->file);
-
-  G_OBJECT_CLASS (ide_file_settings_parent_class)->finalize (object);
+  g_clear_object (&priv->file);
 
-  DZL_COUNTER_DEC (instances);
+  IDE_OBJECT_CLASS (ide_file_settings_parent_class)->destroy (object);
 }
 
 static void
@@ -230,6 +281,10 @@ ide_file_settings_get_property (GObject    *object,
       g_value_set_object (value, ide_file_settings_get_file (self));
       break;
 
+    case PROP_LANGUAGE:
+      g_value_set_static_string (value, ide_file_settings_get_language (self));
+      break;
+
     case PROP_SETTLED:
       g_value_set_boolean (value, ide_file_settings_get_settled (self));
       break;
@@ -267,6 +322,10 @@ ide_file_settings_set_property (GObject      *object,
       ide_file_settings_set_file (self, g_value_get_object (value));
       break;
 
+    case PROP_LANGUAGE:
+      ide_file_settings_set_language (self, g_value_get_string (value));
+      break;
+
 #define IDE_FILE_SETTINGS_PROPERTY(NAME, name, _2, _3, _4, _5, _6, value_type) \
     case PROP_##NAME: \
       ide_file_settings_set_##name (self, g_value_get_##value_type (value)); \
@@ -290,22 +349,32 @@ static void
 ide_file_settings_class_init (IdeFileSettingsClass *klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  IdeObjectClass *i_object_class = IDE_OBJECT_CLASS (klass);
 
-  object_class->finalize = ide_file_settings_finalize;
   object_class->get_property = ide_file_settings_get_property;
   object_class->set_property = ide_file_settings_set_property;
 
+  i_object_class->destroy = ide_file_settings_destroy;
+  i_object_class->repr = ide_file_settings_repr;
+
   properties [PROP_FILE] =
     g_param_spec_object ("file",
                          "File",
-                         "The IdeFile the settings represent.",
-                         IDE_TYPE_FILE,
+                         "The GFile the settings represent",
+                         G_TYPE_FILE,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_LANGUAGE] =
+    g_param_spec_string ("language",
+                         "Langauge",
+                         "The language the settings represent",
+                         NULL,
                          (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
 
   properties [PROP_SETTLED] =
     g_param_spec_boolean ("settled",
                           "Settled",
-                          "If the file settings implementations have settled.",
+                          "If the file settings implementations have settled",
                           FALSE,
                           (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
 
@@ -332,8 +401,6 @@ ide_file_settings_init (IdeFileSettings *self)
 {
   IdeFileSettingsPrivate *priv = ide_file_settings_get_instance_private (self);
 
-  DZL_COUNTER_INC (instances);
-
   priv->indent_style = IDE_INDENT_STYLE_SPACES;
   priv->indent_width = -1;
   priv->insert_trailing_newline = TRUE;
@@ -399,23 +466,26 @@ ide_file_settings__init_cb (GObject      *object,
 }
 
 IdeFileSettings *
-ide_file_settings_new (IdeFile *file)
+ide_file_settings_new (IdeObject   *parent,
+                       GFile       *file,
+                       const gchar *language)
 {
   IdeFileSettingsPrivate *priv;
   GIOExtensionPoint *extension_point;
   IdeFileSettings *ret;
-  IdeContext *context;
   GList *list;
 
-  g_return_val_if_fail (IDE_IS_FILE (file), NULL);
+  g_return_val_if_fail (G_IS_FILE (file), NULL);
+  g_return_val_if_fail (IDE_IS_OBJECT (parent), NULL);
 
-  context = ide_object_get_context (IDE_OBJECT (file));
   ret = g_object_new (IDE_TYPE_FILE_SETTINGS,
-                      "context", context,
                       "file", file,
+                      "language", language,
                       NULL);
   priv = ide_file_settings_get_instance_private (ret);
 
+  ide_object_append (parent, IDE_OBJECT (ret));
+
   extension_point = g_io_extension_point_lookup (IDE_FILE_SETTINGS_EXTENSION_POINT);
   list = g_io_extension_point_get_extensions (extension_point);
 
@@ -440,8 +510,9 @@ ide_file_settings_new (IdeFile *file)
 
       child = g_object_new (gtype,
                             "file", file,
-                            "context", context,
+                            "language", language,
                             NULL);
+      ide_object_append (IDE_OBJECT (ret), IDE_OBJECT (child));
 
       if (G_IS_INITABLE (child))
         {
diff --git a/src/libide/files/ide-file-settings.defs b/src/libide/code/ide-file-settings.defs
similarity index 100%
rename from src/libide/files/ide-file-settings.defs
rename to src/libide/code/ide-file-settings.defs
diff --git a/src/libide/files/ide-file-settings.h b/src/libide/code/ide-file-settings.h
similarity index 75%
rename from src/libide/files/ide-file-settings.h
rename to src/libide/code/ide-file-settings.h
index aa96cf7a3..8caf27d63 100644
--- a/src/libide/files/ide-file-settings.h
+++ b/src/libide/code/ide-file-settings.h
@@ -20,14 +20,16 @@
 
 #pragma once
 
-#include <gtksourceview/gtksource.h>
+#if !defined (IDE_CODE_INSIDE) && !defined (IDE_CODE_COMPILATION)
+# error "Only <libide-code.h> can be included directly."
+#endif
 
-#include "ide-object.h"
-#include "ide-version-macros.h"
+#include <libide-core.h>
+#include <gtksourceview/gtksource.h>
 
-#include "files/ide-file.h"
-#include "files/ide-indent-style.h"
-#include "files/ide-spaces-style.h"
+#include "ide-code-types.h"
+#include "ide-indent-style.h"
+#include "ide-spaces-style.h"
 
 G_BEGIN_DECLS
 
@@ -40,14 +42,21 @@ G_DECLARE_DERIVABLE_TYPE (IdeFileSettings, ide_file_settings, IDE, FILE_SETTINGS
 struct _IdeFileSettingsClass
 {
   IdeObjectClass parent;
+
+  /*< private >*/
+  gpointer _reserved[8];
 };
 
 IDE_AVAILABLE_IN_3_32
-IdeFileSettings *ide_file_settings_new         (IdeFile         *file);
+IdeFileSettings *ide_file_settings_new          (IdeObject       *parent,
+                                                 GFile           *file,
+                                                 const gchar     *language);
+IDE_AVAILABLE_IN_3_32
+GFile           *ide_file_settings_get_file     (IdeFileSettings *self);
 IDE_AVAILABLE_IN_3_32
-IdeFile         *ide_file_settings_get_file    (IdeFileSettings *self);
+const gchar     *ide_file_settings_get_language (IdeFileSettings *self);
 IDE_AVAILABLE_IN_3_32
-gboolean         ide_file_settings_get_settled (IdeFileSettings *self);
+gboolean         ide_file_settings_get_settled  (IdeFileSettings *self);
 
 #define IDE_FILE_SETTINGS_PROPERTY(_1, name, _2, ret_type, _3, _4, _5, _6) \
   _IDE_EXTERN ret_type ide_file_settings_get_##name (IdeFileSettings *self);
diff --git a/src/libide/formatting/ide-formatter-options.c b/src/libide/code/ide-formatter-options.c
similarity index 99%
rename from src/libide/formatting/ide-formatter-options.c
rename to src/libide/code/ide-formatter-options.c
index 203d08ca6..5dc199110 100644
--- a/src/libide/formatting/ide-formatter-options.c
+++ b/src/libide/code/ide-formatter-options.c
@@ -22,7 +22,7 @@
 
 #include "config.h"
 
-#include "formatting/ide-formatter-options.h"
+#include "ide-formatter-options.h"
 
 struct _IdeFormatterOptions
 {
diff --git a/src/libide/formatting/ide-formatter-options.h b/src/libide/code/ide-formatter-options.h
similarity index 91%
rename from src/libide/formatting/ide-formatter-options.h
rename to src/libide/code/ide-formatter-options.h
index 0d2573b7e..068a765ba 100644
--- a/src/libide/formatting/ide-formatter-options.h
+++ b/src/libide/code/ide-formatter-options.h
@@ -20,9 +20,11 @@
 
 #pragma once
 
-#include <glib-object.h>
+#if !defined (IDE_CODE_INSIDE) && !defined (IDE_CODE_COMPILATION)
+# error "Only <libide-code.h> can be included directly."
+#endif
 
-#include "ide-version-macros.h"
+#include <libide-core.h>
 
 G_BEGIN_DECLS
 
diff --git a/src/libide/formatting/ide-formatter.c b/src/libide/code/ide-formatter.c
similarity index 98%
rename from src/libide/formatting/ide-formatter.c
rename to src/libide/code/ide-formatter.c
index c00ff6242..2275c6146 100644
--- a/src/libide/formatting/ide-formatter.c
+++ b/src/libide/code/ide-formatter.c
@@ -22,8 +22,9 @@
 
 #include "config.h"
 
-#include "buffers/ide-buffer.h"
-#include "formatting/ide-formatter.h"
+#include "ide-buffer.h"
+#include "ide-formatter.h"
+#include "ide-formatter-options.h"
 
 G_DEFINE_INTERFACE (IdeFormatter, ide_formatter, G_TYPE_OBJECT)
 
diff --git a/src/libide/formatting/ide-formatter.h b/src/libide/code/ide-formatter.h
similarity index 95%
rename from src/libide/formatting/ide-formatter.h
rename to src/libide/code/ide-formatter.h
index eb1246116..fc3213948 100644
--- a/src/libide/formatting/ide-formatter.h
+++ b/src/libide/code/ide-formatter.h
@@ -20,12 +20,14 @@
 
 #pragma once
 
-#include <gtk/gtk.h>
+#if !defined (IDE_CODE_INSIDE) && !defined (IDE_CODE_COMPILATION)
+# error "Only <libide-code.h> can be included directly."
+#endif
 
-#include "ide-object.h"
-#include "ide-version-macros.h"
+#include <gtk/gtk.h>
+#include <libide-core.h>
 
-#include "formatting/ide-formatter-options.h"
+#include "ide-code-types.h"
 
 G_BEGIN_DECLS
 
diff --git a/src/libide/gsettings/ide-gsettings-file-settings.c 
b/src/libide/code/ide-gsettings-file-settings.c
similarity index 70%
rename from src/libide/gsettings/ide-gsettings-file-settings.c
rename to src/libide/code/ide-gsettings-file-settings.c
index c2a1aae2a..fe998a5d7 100644
--- a/src/libide/gsettings/ide-gsettings-file-settings.c
+++ b/src/libide/code/ide-gsettings-file-settings.c
@@ -24,22 +24,16 @@
 
 #include <dazzle.h>
 #include <glib/gi18n.h>
+#include <libide-core.h>
 
-#include "ide-context.h"
-#include "ide-debug.h"
-#include "ide-enums.h"
-
-#include "files/ide-file.h"
-#include "gsettings/ide-gsettings-file-settings.h"
-#include "gsettings/ide-language-defaults.h"
-#include "util/ide-settings.h"
+#include "ide-code-enums.h"
+#include "ide-gsettings-file-settings.h"
+#include "ide-language-defaults.h"
 
 struct _IdeGsettingsFileSettings
 {
   IdeFileSettings  parent_instance;
-
   IdeSettings     *language_settings;
-  DzlSignalGroup  *signal_group;
 };
 
 typedef struct
@@ -111,37 +105,31 @@ static SettingsMapping language_mappings [] = {
 };
 
 static void
-file_notify_language_cb (IdeGsettingsFileSettings *self,
-                         GParamSpec               *pspec,
-                         IdeFile                  *file)
+ide_gsettings_file_settings_apply (IdeGsettingsFileSettings *self)
 {
   g_autofree gchar *relative_path = NULL;
-  GtkSourceLanguage *language;
+  g_autofree gchar *project_id = NULL;
   const gchar *lang_id;
   IdeContext *context;
-  gsize i;
 
   g_assert (IDE_IS_GSETTINGS_FILE_SETTINGS (self));
-  g_assert (IDE_IS_FILE (file));
 
   g_clear_object (&self->language_settings);
 
-  language = ide_file_get_language (file);
-
-  if (language == NULL)
+  if (!(lang_id = ide_file_settings_get_language (IDE_FILE_SETTINGS (self))))
     lang_id = "plain-text";
-  else
-    lang_id = gtk_source_language_get_id (language);
 
   g_assert (lang_id != NULL);
 
   context = ide_object_get_context (IDE_OBJECT (self));
+  project_id = ide_context_dup_project_id (context);
   relative_path = g_strdup_printf ("/editor/language/%s/", lang_id);
-  self->language_settings = ide_context_get_settings (context,
-                                                      "org.gnome.builder.editor.language",
-                                                      relative_path);
+  self->language_settings = ide_settings_new (project_id,
+                                              "org.gnome.builder.editor.language",
+                                              relative_path,
+                                              FALSE);
 
-  for (i = 0; i < G_N_ELEMENTS (language_mappings); i++)
+  for (guint i = 0; i < G_N_ELEMENTS (language_mappings); i++)
     {
       SettingsMapping *mapping = &language_mappings [i];
 
@@ -158,52 +146,42 @@ file_notify_language_cb (IdeGsettingsFileSettings *self,
 }
 
 static void
-ide_gsettings_file_settings_constructed (GObject *object)
+ide_gsettings_file_settings_parent_set (IdeObject *object,
+                                        IdeObject *parent)
 {
   IdeGsettingsFileSettings *self = (IdeGsettingsFileSettings *)object;
-  IdeFile *file;
 
   IDE_ENTRY;
 
-  G_OBJECT_CLASS (ide_gsettings_file_settings_parent_class)->constructed (object);
-
-  file = ide_file_settings_get_file (IDE_FILE_SETTINGS (self));
-  if (file == NULL)
-    IDE_EXIT;
+  g_assert (IDE_IS_GSETTINGS_FILE_SETTINGS (self));
+  g_assert (!parent || IDE_IS_OBJECT (parent));
 
-  dzl_signal_group_set_target (self->signal_group, file);
-  file_notify_language_cb (self, NULL, file);
+  if (parent != NULL)
+    ide_gsettings_file_settings_apply (self);
 
   IDE_EXIT;
 }
 
 static void
-ide_gsettings_file_settings_dispose (GObject *object)
+ide_gsettings_file_settings_destroy (IdeObject *object)
 {
   IdeGsettingsFileSettings *self = (IdeGsettingsFileSettings *)object;
 
-  g_clear_object (&self->signal_group);
   g_clear_object (&self->language_settings);
 
-  G_OBJECT_CLASS (ide_gsettings_file_settings_parent_class)->dispose (object);
+  IDE_OBJECT_CLASS (ide_gsettings_file_settings_parent_class)->destroy (object);
 }
 
 static void
 ide_gsettings_file_settings_class_init (IdeGsettingsFileSettingsClass *klass)
 {
-  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  IdeObjectClass *i_object_class = IDE_OBJECT_CLASS (klass);
 
-  object_class->constructed = ide_gsettings_file_settings_constructed;
-  object_class->dispose = ide_gsettings_file_settings_dispose;
+  i_object_class->parent_set = ide_gsettings_file_settings_parent_set;
+  i_object_class->destroy = ide_gsettings_file_settings_destroy;
 }
 
 static void
 ide_gsettings_file_settings_init (IdeGsettingsFileSettings *self)
 {
-  self->signal_group = dzl_signal_group_new (IDE_TYPE_FILE);
-  dzl_signal_group_connect_object (self->signal_group,
-                                   "notify::language",
-                                   G_CALLBACK (file_notify_language_cb),
-                                   self,
-                                   G_CONNECT_SWAPPED);
 }
diff --git a/src/libide/gsettings/ide-gsettings-file-settings.h 
b/src/libide/code/ide-gsettings-file-settings.h
similarity index 89%
rename from src/libide/gsettings/ide-gsettings-file-settings.h
rename to src/libide/code/ide-gsettings-file-settings.h
index 30f254c1a..630d1ce1c 100644
--- a/src/libide/gsettings/ide-gsettings-file-settings.h
+++ b/src/libide/code/ide-gsettings-file-settings.h
@@ -20,13 +20,12 @@
 
 #pragma once
 
-#include "files/ide-file-settings.h"
+#include "ide-file-settings.h"
 
 G_BEGIN_DECLS
 
 #define IDE_TYPE_GSETTINGS_FILE_SETTINGS (ide_gsettings_file_settings_get_type())
 
-G_DECLARE_FINAL_TYPE (IdeGsettingsFileSettings, ide_gsettings_file_settings,
-                      IDE, GSETTINGS_FILE_SETTINGS, IdeFileSettings)
+G_DECLARE_FINAL_TYPE (IdeGsettingsFileSettings, ide_gsettings_file_settings, IDE, GSETTINGS_FILE_SETTINGS, 
IdeFileSettings)
 
 G_END_DECLS
diff --git a/src/libide/code/ide-highlight-engine.c b/src/libide/code/ide-highlight-engine.c
new file mode 100644
index 000000000..d95b74b00
--- /dev/null
+++ b/src/libide/code/ide-highlight-engine.c
@@ -0,0 +1,1189 @@
+/* ide-highlight-engine.c
+ *
+ * Copyright 2015-2019 Christian Hergert <christian hergert me>
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-highlight-engine"
+
+#include "config.h"
+
+#include <dazzle.h>
+#include <glib/gi18n.h>
+#include <gtksourceview/gtksource.h>
+#include <libide-plugins.h>
+#include <string.h>
+
+#include "ide-buffer.h"
+#include "ide-buffer-private.h"
+#include "ide-highlight-engine.h"
+#include "ide-highlight-index.h"
+#include "ide-highlighter.h"
+
+#define HIGHLIGHT_QUANTA_USEC 5000
+#define PRIVATE_TAG_PREFIX    "gb-private-tag"
+
+struct _IdeHighlightEngine
+{
+  IdeObject            parent_instance;
+
+  GWeakRef             buffer_wref;
+
+  DzlSignalGroup      *signal_group;
+  IdeHighlighter      *highlighter;
+  GSettings           *settings;
+
+  IdeExtensionAdapter *extension;
+
+  GtkTextMark         *invalid_begin;
+  GtkTextMark         *invalid_end;
+
+  GSList              *private_tags;
+  GSList              *public_tags;
+
+  gint64               quanta_expiration;
+
+  guint                work_timeout;
+
+  guint                enabled : 1;
+};
+
+G_DEFINE_TYPE (IdeHighlightEngine, ide_highlight_engine, IDE_TYPE_OBJECT)
+
+enum {
+  PROP_0,
+  PROP_BUFFER,
+  PROP_HIGHLIGHTER,
+  LAST_PROP
+};
+
+static GParamSpec *properties [LAST_PROP];
+
+static gboolean
+get_invalidation_area (GtkTextIter *begin,
+                       GtkTextIter *end)
+{
+  GtkTextIter begin_tmp;
+  GtkTextIter end_tmp;
+
+  g_assert (begin != NULL);
+  g_assert (end != NULL);
+
+  /*
+   * Move to the beginning of line.We dont use gtk_text_iter_backward_line
+   * because if begin is at the beginning of the line we dont want to
+   * move to the previous line
+   */
+  gtk_text_iter_set_line_offset (begin, 0);
+
+  /* Move to the beginning of the next line. */
+  gtk_text_iter_forward_line (end);
+
+  /* Save the original locations. We will need them down the line. */
+  begin_tmp = *begin;
+  end_tmp = *end;
+
+  /*
+   * Fordward begin iter character by character until:
+   * - We reach a non space character
+   * - We reach end iter
+   */
+  while (g_unichar_isspace (gtk_text_iter_get_char (begin)) &&
+         gtk_text_iter_compare (begin, &end_tmp) < 0)
+    gtk_text_iter_forward_char (begin);
+
+
+  /*
+   * If after moving forward the begin iter, we reached the end iter,
+   * there is no need to play with the end iter.
+   */
+  if (gtk_text_iter_compare (begin, end) < 0)
+    {
+      /*
+       * Backward end iter character by character until:
+       * - We reach a non space character
+       * - We reach begin iter
+       */
+      while (g_unichar_isspace (gtk_text_iter_get_char (end)) &&
+             gtk_text_iter_compare (end, &begin_tmp) > 0)
+        gtk_text_iter_backward_char (end);
+
+      /*
+       * If we found the character we are looking for then move one
+       * character forward in order to include it as the last
+       * character of the begin - end range.
+       */
+      if (gtk_text_iter_compare (end, &end_tmp) < 0)
+        gtk_text_iter_forward_char (end);
+    }
+
+  return gtk_text_iter_compare (begin, end) < 0;
+}
+
+static void
+sync_tag_style (GtkSourceStyleScheme *style_scheme,
+                GtkTextTag           *tag)
+{
+  g_autofree gchar *foreground = NULL;
+  g_autofree gchar *background = NULL;
+  g_autofree gchar *tag_name = NULL;
+  gchar *style_name = NULL;
+  const gchar *colon;
+  GtkSourceStyle *style;
+  gboolean foreground_set = FALSE;
+  gboolean background_set = FALSE;
+  gboolean bold = FALSE;
+  gboolean bold_set = FALSE;
+  gboolean underline = FALSE;
+  gboolean underline_set = FALSE;
+  gboolean italic = FALSE;
+  gboolean italic_set = FALSE;
+  gsize tag_name_len;
+  gsize prefix_len;
+
+  g_object_set (tag,
+                "foreground-set", FALSE,
+                "background-set", FALSE,
+                "weight-set", FALSE,
+                "underline-set", FALSE,
+                "style-set", FALSE,
+                NULL);
+
+  g_object_get (tag, "name", &tag_name, NULL);
+
+  if (tag_name == NULL || style_scheme == NULL)
+    return;
+
+  prefix_len = strlen (PRIVATE_TAG_PREFIX);
+  tag_name_len = strlen (tag_name);
+  style_name = tag_name;
+
+  /*
+   * Check if this is a private tag.A tag is private if it starts with
+   * PRIVATE_TAG_PREFIX "gb-private-tag".
+   * ex: gb-private-tag:c:boolean
+   * If the tag is private extract the original style name by moving the string
+   * strlen (PRIVATE_TAG_PREFIX) + 1 (the colon) characters.
+   */
+  if (tag_name_len > prefix_len && memcmp (tag_name, PRIVATE_TAG_PREFIX, prefix_len) == 0)
+    style_name = tag_name + prefix_len + 1;
+
+  style = gtk_source_style_scheme_get_style (style_scheme, style_name);
+  if (style == NULL && (colon = strchr (style_name, ':')))
+    {
+      gchar defname[64];
+      g_snprintf (defname, sizeof defname, "def%s", colon);
+      style = gtk_source_style_scheme_get_style (style_scheme, defname);
+      if (style == NULL)
+        return;
+    }
+
+  g_object_get (style,
+                "background", &background,
+                "background-set", &background_set,
+                "foreground", &foreground,
+                "foreground-set", &foreground_set,
+                "bold", &bold,
+                "bold-set", &bold_set,
+                "pango-underline", &underline,
+                "underline-set", &underline_set,
+                "italic", &italic,
+                "italic-set", &italic_set,
+                NULL);
+
+  if (background_set)
+    g_object_set (tag, "background", background, NULL);
+
+  if (foreground_set)
+    g_object_set (tag, "foreground", foreground, NULL);
+
+  if (bold_set && bold)
+    g_object_set (tag, "weight", PANGO_WEIGHT_BOLD, NULL);
+
+  if (italic_set && italic)
+    g_object_set (tag, "style", PANGO_STYLE_ITALIC, NULL);
+
+  if (underline_set && underline)
+    g_object_set (tag, "underline", PANGO_UNDERLINE_SINGLE, NULL);
+}
+
+static GtkTextTag *
+create_tag_from_style (IdeHighlightEngine *self,
+                       const gchar        *style_name)
+{
+  g_autoptr(IdeBuffer) buffer = NULL;
+  GtkSourceStyleScheme *style_scheme;
+  GtkTextTag *tag = NULL;
+
+  g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
+  g_assert (style_name != NULL);
+
+  buffer = g_weak_ref_get (&self->buffer_wref);
+
+  if (buffer != NULL)
+    {
+      tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer), style_name, NULL);
+      gtk_text_tag_set_priority (tag, 0);
+      style_scheme = gtk_source_buffer_get_style_scheme (GTK_SOURCE_BUFFER (buffer));
+      sync_tag_style (style_scheme, tag);
+    }
+
+  return tag;
+}
+
+static GtkTextTag *
+get_tag_from_style (IdeHighlightEngine *self,
+                    const gchar        *style_name,
+                    gboolean            private_tag)
+{
+  g_autoptr(IdeBuffer) buffer = NULL;
+  g_autofree gchar *tmp_style_name = NULL;
+  GtkTextTagTable *tag_table;
+  GtkTextTag *tag;
+
+  g_return_val_if_fail (IDE_IS_HIGHLIGHT_ENGINE (self), NULL);
+  g_return_val_if_fail (style_name != NULL, NULL);
+
+  buffer = g_weak_ref_get (&self->buffer_wref);
+  if (buffer == NULL)
+    return NULL;
+
+  /*
+   * If is private tag prepend the PRIVATE_TAG_PREFIX (gb-private-tag)
+   * to the string.This is used because tag name is the key used
+   * for saving tags in GtkTextTagTable and we dont want conflicts between
+   * public and private tags.
+   */
+  if (private_tag)
+    tmp_style_name = g_strdup_printf ("%s:%s", PRIVATE_TAG_PREFIX, style_name);
+  else
+    tmp_style_name = g_strdup (style_name);
+
+  tag_table = gtk_text_buffer_get_tag_table (GTK_TEXT_BUFFER (buffer));
+  tag = gtk_text_tag_table_lookup (tag_table, tmp_style_name);
+
+  if (tag == NULL)
+    {
+      tag = create_tag_from_style (self, tmp_style_name);
+      if (private_tag)
+        self->private_tags = g_slist_prepend (self->private_tags, tag);
+      else
+        self->public_tags = g_slist_prepend (self->public_tags, tag);
+    }
+
+  return tag;
+}
+
+
+static IdeHighlightResult
+ide_highlight_engine_apply_style (const GtkTextIter *begin,
+                                  const GtkTextIter *end,
+                                  const gchar       *style_name)
+{
+  IdeHighlightEngine *self;
+  GtkTextBuffer *buffer;
+  GtkTextTag *tag;
+
+  buffer = gtk_text_iter_get_buffer (begin);
+  self = _ide_buffer_get_highlight_engine (IDE_BUFFER (buffer));
+  tag = get_tag_from_style (self, style_name, TRUE);
+
+  gtk_text_buffer_apply_tag (buffer, tag, begin, end);
+
+  if (g_get_monotonic_time () >= self->quanta_expiration)
+    return IDE_HIGHLIGHT_STOP;
+
+  return IDE_HIGHLIGHT_CONTINUE;
+}
+
+static gboolean
+ide_highlight_engine_tick (IdeHighlightEngine *self)
+{
+  g_autoptr(GtkTextBuffer) buffer = NULL;
+  GtkTextIter iter;
+  GtkTextIter invalid_begin;
+  GtkTextIter invalid_end;
+  GSList *tags_iter;
+
+  IDE_PROBE;
+
+  g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
+  g_assert (self->highlighter != NULL);
+  g_assert (self->invalid_begin != NULL);
+  g_assert (self->invalid_end != NULL);
+
+  buffer = g_weak_ref_get (&self->buffer_wref);
+  if (buffer == NULL)
+    return G_SOURCE_REMOVE;
+
+  self->quanta_expiration = g_get_monotonic_time () + HIGHLIGHT_QUANTA_USEC;
+
+  gtk_text_buffer_get_iter_at_mark (buffer, &invalid_begin, self->invalid_begin);
+  gtk_text_buffer_get_iter_at_mark (buffer, &invalid_end, self->invalid_end);
+
+  IDE_TRACE_MSG ("Highlight Range [%u:%u,%u:%u] (%s)",
+                 gtk_text_iter_get_line (&invalid_begin),
+                 gtk_text_iter_get_line_offset (&invalid_begin),
+                 gtk_text_iter_get_line (&invalid_end),
+                 gtk_text_iter_get_line_offset (&invalid_end),
+                 G_OBJECT_TYPE_NAME (self->highlighter));
+
+  if (gtk_text_iter_compare (&invalid_begin, &invalid_end) >= 0)
+    IDE_GOTO (up_to_date);
+
+  /* Clear all our tags */
+  for (tags_iter = self->private_tags; tags_iter; tags_iter = tags_iter->next)
+    gtk_text_buffer_remove_tag (buffer,
+                                GTK_TEXT_TAG (tags_iter->data),
+                                &invalid_begin,
+                                &invalid_end);
+
+  iter = invalid_begin;
+
+  ide_highlighter_update (self->highlighter, ide_highlight_engine_apply_style,
+                          &invalid_begin, &invalid_end, &iter);
+
+  if (gtk_text_iter_compare (&iter, &invalid_end) >= 0)
+    IDE_GOTO (up_to_date);
+
+  /* Stop processing until further instruction if no movement was made */
+  if (gtk_text_iter_equal (&iter, &invalid_begin))
+    return G_SOURCE_REMOVE;
+
+  gtk_text_buffer_move_mark (buffer, self->invalid_begin, &iter);
+
+  return G_SOURCE_CONTINUE;
+
+up_to_date:
+  gtk_text_buffer_get_start_iter (buffer, &iter);
+  gtk_text_buffer_move_mark (buffer, self->invalid_begin, &iter);
+  gtk_text_buffer_move_mark (buffer, self->invalid_end, &iter);
+
+  return G_SOURCE_REMOVE;
+}
+
+static gboolean
+ide_highlight_engine_work_timeout_handler (gpointer data)
+{
+  IdeHighlightEngine *self = data;
+
+  g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
+
+  if (self->enabled)
+    {
+      if (ide_highlight_engine_tick (self))
+        return G_SOURCE_CONTINUE;
+    }
+
+  self->work_timeout = 0;
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+ide_highlight_engine_queue_work (IdeHighlightEngine *self)
+{
+  g_autoptr(GtkTextBuffer) buffer = NULL;
+
+  g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
+
+  buffer = g_weak_ref_get (&self->buffer_wref);
+  if (self->highlighter == NULL || buffer == NULL || self->work_timeout != 0)
+    return;
+
+  /*
+   * NOTE: It would be really nice if we could use the GdkFrameClock here to
+   *       drive the next update instead of a timeout. It's possible that our
+   *       callback could get scheduled right before the frame processing would
+   *       begin. However, since that gets driven by something like a Wayland
+   *       callback, it won't yet be scheduled. So instead our function gets
+   *       called and we potentially cause a frame to drop.
+   */
+
+  self->work_timeout = gdk_threads_add_idle_full (G_PRIORITY_LOW + 1,
+                                                  ide_highlight_engine_work_timeout_handler,
+                                                  self,
+                                                  NULL);
+}
+
+/**
+ * ide_highlight_engine_advance:
+ * @self: a #IdeHighlightEngine
+ *
+ * This function is useful for #IdeHighlighter implementations that need to
+ * asynchronously do work to process the highlighting.
+ *
+ * If they return from their update function without advancing, nothing will
+ * happen until they call this method to proceed.
+ *
+ * Since: 3.32
+ */
+void
+ide_highlight_engine_advance (IdeHighlightEngine *self)
+{
+  g_return_if_fail (IDE_IS_HIGHLIGHT_ENGINE (self));
+
+  ide_highlight_engine_queue_work (self);
+}
+
+static gboolean
+invalidate_and_highlight (IdeHighlightEngine *self,
+                          GtkTextIter        *begin,
+                          GtkTextIter        *end)
+{
+  GtkTextBuffer *text_buffer;
+
+  g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
+  g_assert (begin != NULL);
+  g_assert (end != NULL);
+
+  if (!self->enabled)
+    return FALSE;
+
+  text_buffer = gtk_text_iter_get_buffer (begin);
+
+  if (get_invalidation_area (begin, end))
+    {
+      GtkTextIter begin_tmp;
+      GtkTextIter end_tmp;
+
+      gtk_text_buffer_get_iter_at_mark (text_buffer, &begin_tmp, self->invalid_begin);
+      gtk_text_buffer_get_iter_at_mark (text_buffer, &end_tmp, self->invalid_end);
+
+      if (gtk_text_iter_equal (&begin_tmp, &end_tmp))
+        {
+          gtk_text_buffer_move_mark (text_buffer, self->invalid_begin, begin);
+          gtk_text_buffer_move_mark (text_buffer, self->invalid_end, end);
+        }
+      else
+        {
+          if (gtk_text_iter_compare (begin, &begin_tmp) < 0)
+            gtk_text_buffer_move_mark (text_buffer, self->invalid_begin, begin);
+          if (gtk_text_iter_compare (end, &end_tmp) > 0)
+            gtk_text_buffer_move_mark (text_buffer, self->invalid_end, end);
+        }
+
+      ide_highlight_engine_queue_work (self);
+
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static void
+ide_highlight_engine_reload (IdeHighlightEngine *self)
+{
+  g_autoptr(GtkTextBuffer) buffer = NULL;
+  GtkTextIter begin;
+  GtkTextIter end;
+  GSList *iter;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
+
+  dzl_clear_source (&self->work_timeout);
+
+  buffer = g_weak_ref_get (&self->buffer_wref);
+  if (buffer == NULL)
+    IDE_EXIT;
+
+  gtk_text_buffer_get_bounds (buffer, &begin, &end);
+
+  /*
+   * Invalidate the whole buffer.
+   */
+  gtk_text_buffer_move_mark (buffer, self->invalid_begin, &begin);
+  gtk_text_buffer_move_mark (buffer, self->invalid_end, &end);
+
+  /*
+   * Remove our highlight tags from the buffer.
+   */
+  for (iter = self->private_tags; iter; iter = iter->next)
+    gtk_text_buffer_remove_tag (buffer, iter->data, &begin, &end);
+  g_clear_pointer (&self->private_tags, g_slist_free);
+
+  for (iter = self->public_tags; iter; iter = iter->next)
+    gtk_text_buffer_remove_tag (buffer, iter->data, &begin, &end);
+  g_clear_pointer (&self->public_tags, g_slist_free);
+
+  if (self->highlighter == NULL)
+    IDE_EXIT;
+
+  ide_highlight_engine_queue_work (self);
+
+  IDE_EXIT;
+}
+
+static void
+ide_highlight_engine_set_highlighter (IdeHighlightEngine *self,
+                                      IdeHighlighter     *highlighter)
+{
+  g_return_if_fail (IDE_IS_HIGHLIGHT_ENGINE (self));
+  g_return_if_fail (!highlighter || IDE_IS_HIGHLIGHTER (highlighter));
+
+  if (g_set_object (&self->highlighter, highlighter))
+    {
+      if (highlighter != NULL)
+        {
+          IDE_HIGHLIGHTER_GET_IFACE (highlighter)->set_engine (highlighter, self);
+          ide_highlighter_load (highlighter);
+        }
+
+      ide_highlight_engine_reload (self);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_HIGHLIGHTER]);
+    }
+}
+
+static void
+ide_highlight_engine__buffer_insert_text_cb (IdeHighlightEngine *self,
+                                             GtkTextIter        *location,
+                                             gchar              *text,
+                                             gint                len,
+                                             IdeBuffer          *buffer)
+{
+  GtkTextIter begin;
+  GtkTextIter end;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
+  g_assert (location);
+  g_assert (text);
+  g_assert (IDE_IS_BUFFER (buffer));
+
+  if (!self->enabled)
+    IDE_EXIT;
+
+  /*
+   * Backward the begin iter len characters from location
+   * (location points to the end of the string) in order to get
+   * the iter position where our inserted text was started.
+   */
+  begin = *location;
+  gtk_text_iter_backward_chars (&begin, g_utf8_strlen (text, len));
+
+  end = *location;
+
+  invalidate_and_highlight (self, &begin, &end);
+
+  IDE_EXIT;
+}
+
+static void
+ide_highlight_engine__buffer_delete_range_cb (IdeHighlightEngine *self,
+                                              GtkTextIter        *range_begin,
+                                              GtkTextIter        *range_end,
+                                              IdeBuffer          *buffer)
+{
+  GtkTextIter begin;
+  GtkTextIter end;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
+  g_assert (range_begin);
+  g_assert (IDE_IS_BUFFER (buffer));
+
+  if (!self->enabled)
+    IDE_EXIT;
+
+  /*
+   * No need to use the range_end since everything that
+   * was after range_end will now be after range_begin
+   */
+  begin = *range_begin;
+  end = *range_begin;
+
+  invalidate_and_highlight (self, &begin, &end);
+
+  IDE_EXIT;
+}
+
+static void
+ide_highlight_engine__notify_language_cb (IdeHighlightEngine *self,
+                                          GParamSpec         *pspec,
+                                          IdeBuffer          *buffer)
+{
+  g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
+  g_assert (IDE_IS_BUFFER (buffer));
+
+  if (self->extension != NULL)
+    {
+      GtkSourceLanguage *language;
+      const gchar *lang_id = NULL;
+
+      if ((language = gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (buffer))))
+        lang_id = gtk_source_language_get_id (language);
+
+      ide_extension_adapter_set_value (self->extension, lang_id);
+    }
+}
+
+static void
+ide_highlight_engine__notify_style_scheme_cb (IdeHighlightEngine *self,
+                                              GParamSpec         *pspec,
+                                              IdeBuffer          *buffer)
+{
+  GtkSourceStyleScheme *style_scheme;
+  GSList *iter;
+
+  g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
+  g_assert (IDE_IS_BUFFER (buffer));
+
+  style_scheme = gtk_source_buffer_get_style_scheme (GTK_SOURCE_BUFFER (buffer));
+
+  for (iter = self->private_tags; iter; iter = iter->next)
+    sync_tag_style (style_scheme, iter->data);
+  for (iter = self->public_tags; iter; iter = iter->next)
+    sync_tag_style (style_scheme, iter->data);
+}
+
+void
+ide_highlight_engine_clear (IdeHighlightEngine *self)
+{
+  g_autoptr(GtkTextBuffer) buffer = NULL;
+  GtkTextIter begin;
+  GtkTextIter end;
+
+  g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
+
+  buffer = g_weak_ref_get (&self->buffer_wref);
+
+  if (buffer != NULL)
+    {
+      gtk_text_buffer_get_bounds (buffer, &begin, &end);
+
+      for (const GSList *iter = self->public_tags; iter; iter = iter->next)
+        {
+          GtkTextTag *tag = iter->data;
+          gtk_text_buffer_remove_tag (buffer, tag, &begin, &end);
+        }
+    }
+}
+
+static void
+ide_highlight_engine__bind_buffer_cb (IdeHighlightEngine *self,
+                                      IdeBuffer          *buffer,
+                                      DzlSignalGroup     *group)
+{
+  GtkTextBuffer *text_buffer = (GtkTextBuffer *)buffer;
+  GtkTextIter begin;
+  GtkTextIter end;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
+  g_assert (IDE_IS_BUFFER (buffer));
+  g_assert (DZL_IS_SIGNAL_GROUP (group));
+  g_assert (self->invalid_begin == NULL);
+  g_assert (self->invalid_end == NULL);
+
+  g_weak_ref_set (&self->buffer_wref, buffer);
+
+  gtk_text_buffer_get_bounds (text_buffer, &begin, &end);
+
+  self->invalid_begin = gtk_text_buffer_create_mark (text_buffer, NULL, &begin, TRUE);
+  self->invalid_end = gtk_text_buffer_create_mark (text_buffer, NULL, &end, FALSE);
+
+  /* We can hold a full reference to the text marks, without
+   * taking a reference to the buffer. We want to avoid a reference
+   * to the buffer for cyclic reasons.
+   */
+  g_object_ref (self->invalid_begin);
+  g_object_ref (self->invalid_end);
+
+  ide_highlight_engine__notify_style_scheme_cb (self, NULL, buffer);
+  ide_highlight_engine__notify_language_cb (self, NULL, buffer);
+
+  ide_highlight_engine_reload (self);
+
+  IDE_EXIT;
+}
+
+static void
+ide_highlight_engine__unbind_buffer_cb (IdeHighlightEngine  *self,
+                                        DzlSignalGroup      *group)
+{
+  g_autoptr(GtkTextBuffer) text_buffer = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
+  g_assert (DZL_IS_SIGNAL_GROUP (group));
+
+  text_buffer = g_weak_ref_get (&self->buffer_wref);
+
+  dzl_clear_source (&self->work_timeout);
+
+  if (text_buffer != NULL)
+    {
+      g_autoptr(GSList) private_tags = NULL;
+      g_autoptr(GSList) public_tags = NULL;
+      GtkTextTagTable *tag_table;
+      GtkTextIter begin;
+      GtkTextIter end;
+
+      tag_table = gtk_text_buffer_get_tag_table (text_buffer);
+
+      gtk_text_buffer_delete_mark (text_buffer, self->invalid_begin);
+      gtk_text_buffer_delete_mark (text_buffer, self->invalid_end);
+
+      gtk_text_buffer_get_bounds (text_buffer, &begin, &end);
+
+      private_tags = g_steal_pointer (&self->private_tags);
+      public_tags = g_steal_pointer (&self->public_tags);
+
+      for (const GSList *iter = private_tags; iter; iter = iter->next)
+        {
+          gtk_text_buffer_remove_tag (text_buffer, iter->data, &begin, &end);
+          gtk_text_tag_table_remove (tag_table, iter->data);
+        }
+
+      for (const GSList *iter = public_tags; iter; iter = iter->next)
+        {
+          gtk_text_buffer_remove_tag (text_buffer, iter->data, &begin, &end);
+          gtk_text_tag_table_remove (tag_table, iter->data);
+        }
+    }
+
+  g_clear_pointer (&self->public_tags, g_slist_free);
+  g_clear_pointer (&self->private_tags, g_slist_free);
+
+  g_clear_object (&self->invalid_begin);
+  g_clear_object (&self->invalid_end);
+
+  IDE_EXIT;
+}
+
+static void
+ide_highlight_engine_set_buffer (IdeHighlightEngine *self,
+                                 IdeBuffer          *buffer)
+{
+  g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
+  g_assert (!buffer || GTK_IS_TEXT_BUFFER (buffer));
+
+  /* We can get GtkSourceBuffer intermittently here. */
+  if (!buffer || IDE_IS_BUFFER (buffer))
+    {
+      dzl_signal_group_set_target (self->signal_group, buffer);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_BUFFER]);
+    }
+}
+
+static void
+ide_highlight_engine_settings_changed (IdeHighlightEngine *self,
+                                       const gchar        *key,
+                                       GSettings          *settings)
+{
+  g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
+  g_assert (G_IS_SETTINGS (settings));
+
+  if (g_settings_get_boolean (settings, "semantic-highlighting"))
+    {
+      self->enabled = TRUE;
+      ide_highlight_engine_rebuild (self);
+    }
+  else
+    {
+      self->enabled = FALSE;
+      ide_highlight_engine_clear (self);
+    }
+}
+
+static void
+ide_highlight_engine__notify_extension (IdeHighlightEngine  *self,
+                                        GParamSpec          *pspec,
+                                        IdeExtensionAdapter *adapter)
+{
+  IdeHighlighter *highlighter;
+
+  g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
+  g_assert (IDE_IS_EXTENSION_ADAPTER (adapter));
+
+  highlighter = ide_extension_adapter_get_extension (adapter);
+  g_return_if_fail (!highlighter || IDE_IS_HIGHLIGHTER (highlighter));
+
+  ide_highlight_engine_set_highlighter (self, highlighter);
+}
+
+static void
+ide_highlight_engine_parent_set (IdeObject *object,
+                                 IdeObject *parent)
+{
+  IdeHighlightEngine *self = (IdeHighlightEngine *)object;
+
+  g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
+  g_assert (!parent || IDE_IS_OBJECT (parent));
+
+  if (parent == NULL)
+    {
+      g_clear_object (&self->extension);
+      return;
+    }
+
+  self->extension = ide_extension_adapter_new (IDE_OBJECT (self),
+                                               NULL,
+                                               IDE_TYPE_HIGHLIGHTER,
+                                               "Highlighter-Languages",
+                                               NULL);
+  g_signal_connect_object (self->extension,
+                           "notify::extension",
+                           G_CALLBACK (ide_highlight_engine__notify_extension),
+                           self,
+                           G_CONNECT_SWAPPED);
+}
+
+static void
+ide_highlight_engine_dispose (GObject *object)
+{
+  IdeHighlightEngine *self = (IdeHighlightEngine *)object;
+
+  g_weak_ref_set (&self->buffer_wref, NULL);
+  g_clear_object (&self->signal_group);
+  g_clear_object (&self->extension);
+  g_clear_object (&self->highlighter);
+  g_clear_object (&self->settings);
+
+  G_OBJECT_CLASS (ide_highlight_engine_parent_class)->dispose (object);
+}
+
+static void
+ide_highlight_engine_finalize (GObject *object)
+{
+  IdeHighlightEngine *self = (IdeHighlightEngine *)object;
+
+  g_weak_ref_clear (&self->buffer_wref);
+
+  G_OBJECT_CLASS (ide_highlight_engine_parent_class)->finalize (object);
+}
+
+static void
+ide_highlight_engine_get_property (GObject    *object,
+                                   guint       prop_id,
+                                   GValue     *value,
+                                   GParamSpec *pspec)
+{
+  IdeHighlightEngine *self = IDE_HIGHLIGHT_ENGINE (object);
+
+  switch (prop_id)
+    {
+    case PROP_BUFFER:
+      g_value_set_object (value, ide_highlight_engine_get_buffer (self));
+      break;
+
+    case PROP_HIGHLIGHTER:
+      g_value_set_object (value, ide_highlight_engine_get_highlighter (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_highlight_engine_set_property (GObject      *object,
+                                   guint         prop_id,
+                                   const GValue *value,
+                                   GParamSpec   *pspec)
+{
+  IdeHighlightEngine *self = IDE_HIGHLIGHT_ENGINE (object);
+
+  switch (prop_id)
+    {
+    case PROP_BUFFER:
+      ide_highlight_engine_set_buffer (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_highlight_engine_class_init (IdeHighlightEngineClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  IdeObjectClass *i_object_class = IDE_OBJECT_CLASS (klass);
+
+  object_class->dispose = ide_highlight_engine_dispose;
+  object_class->finalize = ide_highlight_engine_finalize;
+  object_class->get_property = ide_highlight_engine_get_property;
+  object_class->set_property = ide_highlight_engine_set_property;
+
+  i_object_class->parent_set = ide_highlight_engine_parent_set;
+
+  properties [PROP_BUFFER] =
+    g_param_spec_object ("buffer",
+                         "Buffer",
+                         "The buffer to highlight.",
+                         IDE_TYPE_BUFFER,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_HIGHLIGHTER] =
+    g_param_spec_object ("highlighter",
+                         "Highlighter",
+                         "The highlighter to use for type information.",
+                         IDE_TYPE_HIGHLIGHTER,
+                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, LAST_PROP, properties);
+}
+
+static void
+ide_highlight_engine_init (IdeHighlightEngine *self)
+{
+  g_weak_ref_init (&self->buffer_wref, NULL);
+
+  self->settings = g_settings_new ("org.gnome.builder.code-insight");
+  self->enabled = g_settings_get_boolean (self->settings, "semantic-highlighting");
+  self->signal_group = dzl_signal_group_new (IDE_TYPE_BUFFER);
+
+  dzl_signal_group_connect_object (self->signal_group,
+                                   "insert-text",
+                                   G_CALLBACK (ide_highlight_engine__buffer_insert_text_cb),
+                                   self,
+                                   G_CONNECT_SWAPPED | G_CONNECT_AFTER);
+
+  dzl_signal_group_connect_object (self->signal_group,
+                                   "delete-range",
+                                   G_CALLBACK (ide_highlight_engine__buffer_delete_range_cb),
+                                   self,
+                                   G_CONNECT_SWAPPED | G_CONNECT_AFTER);
+
+  dzl_signal_group_connect_object (self->signal_group,
+                                   "notify::language",
+                                   G_CALLBACK (ide_highlight_engine__notify_language_cb),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+
+  dzl_signal_group_connect_object (self->signal_group,
+                                   "notify::style-scheme",
+                                   G_CALLBACK (ide_highlight_engine__notify_style_scheme_cb),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->signal_group,
+                           "bind",
+                           G_CALLBACK (ide_highlight_engine__bind_buffer_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->signal_group,
+                           "unbind",
+                           G_CALLBACK (ide_highlight_engine__unbind_buffer_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->settings,
+                           "changed::semantic-highlighting",
+                           G_CALLBACK (ide_highlight_engine_settings_changed),
+                           self,
+                           G_CONNECT_SWAPPED);
+}
+
+IdeHighlightEngine *
+ide_highlight_engine_new (IdeBuffer *buffer)
+{
+  IdeHighlightEngine *self;
+  IdeObjectBox *box;
+
+  g_return_val_if_fail (IDE_IS_BUFFER (buffer), NULL);
+
+  self = g_object_new (IDE_TYPE_HIGHLIGHT_ENGINE,
+                       "buffer", buffer,
+                       NULL);
+
+  box = ide_object_box_from_object (G_OBJECT (buffer));
+  ide_object_append (IDE_OBJECT (box), IDE_OBJECT (self));
+
+  return g_steal_pointer (&self);
+}
+
+/**
+ * ide_highlight_engine_get_highlighter:
+ * @self: an #IdeHighlightEngine.
+ *
+ * Gets the IdeHighlightEngine:highlighter property.
+ *
+ * Returns: (transfer none): An #IdeHighlighter.
+ *
+ * Since: 3.32
+ */
+IdeHighlighter *
+ide_highlight_engine_get_highlighter (IdeHighlightEngine *self)
+{
+  g_return_val_if_fail (IDE_IS_HIGHLIGHT_ENGINE (self), NULL);
+
+  return self->highlighter;
+}
+
+/**
+ * ide_highlight_engine_get_buffer:
+ * @self: an #IdeHighlightEngine.
+ *
+ * Gets the IdeHighlightEngine:buffer property.
+ *
+ * Returns: (transfer none): An #IdeBuffer.
+ *
+ * Since: 3.32
+ */
+IdeBuffer *
+ide_highlight_engine_get_buffer (IdeHighlightEngine *self)
+{
+  g_autoptr(IdeBuffer) buffer = NULL;
+
+  g_return_val_if_fail (IDE_IS_HIGHLIGHT_ENGINE (self), NULL);
+
+  /* We don't need the "thread-safety" provided by GWeakRef here,
+   * (where it gives us a new object reference). It is safe to
+   * return a borrowed reference instead.
+   */
+  buffer = g_weak_ref_get (&self->buffer_wref);
+  return buffer;
+}
+
+void
+ide_highlight_engine_rebuild (IdeHighlightEngine *self)
+{
+  g_autoptr(GtkTextBuffer) buffer = NULL;
+
+  IDE_ENTRY;
+
+  g_return_if_fail (IDE_IS_HIGHLIGHT_ENGINE (self));
+
+  buffer = g_weak_ref_get (&self->buffer_wref);
+
+  if (buffer != NULL)
+    {
+      GtkTextIter begin;
+      GtkTextIter end;
+
+      gtk_text_buffer_get_bounds (buffer, &begin, &end);
+      gtk_text_buffer_move_mark (buffer, self->invalid_begin, &begin);
+      gtk_text_buffer_move_mark (buffer, self->invalid_end, &end);
+
+      ide_highlight_engine_queue_work (self);
+    }
+
+  IDE_EXIT;
+}
+
+/**
+ * ide_highlight_engine_invalidate:
+ * @self: An #IdeHighlightEngine.
+ * @begin: the beginning of the range to invalidate
+ * @end: the end of the range to invalidate
+ *
+ * This function will extend the invalidated range of the buffer to include
+ * the range of @begin to @end.
+ *
+ * The highlighter will be queued to interactively update the invalidated
+ * region.
+ *
+ * Updating the invalidated region of the buffer may take some time, as it is
+ * important that the highlighter does not block for more than 1-2 milliseconds
+ * to avoid dropping frames.
+ *
+ * Since: 3.32
+ */
+void
+ide_highlight_engine_invalidate (IdeHighlightEngine *self,
+                                 const GtkTextIter  *begin,
+                                 const GtkTextIter  *end)
+{
+  GtkTextBuffer *buffer;
+  GtkTextIter mark_begin;
+  GtkTextIter mark_end;
+
+  IDE_ENTRY;
+
+  g_return_if_fail (IDE_IS_HIGHLIGHT_ENGINE (self));
+  g_return_if_fail (begin != NULL);
+  g_return_if_fail (end != NULL);
+
+  buffer = gtk_text_iter_get_buffer (begin);
+
+  gtk_text_buffer_get_iter_at_mark (buffer, &mark_begin, self->invalid_begin);
+  gtk_text_buffer_get_iter_at_mark (buffer, &mark_end, self->invalid_end);
+
+  if (gtk_text_iter_equal (&mark_begin, &mark_end))
+    {
+      gtk_text_buffer_move_mark (buffer, self->invalid_begin, begin);
+      gtk_text_buffer_move_mark (buffer, self->invalid_end, end);
+    }
+  else
+    {
+      if (gtk_text_iter_compare (begin, &mark_begin) < 0)
+        gtk_text_buffer_move_mark (buffer, self->invalid_begin, begin);
+      if (gtk_text_iter_compare (end, &mark_end) > 0)
+        gtk_text_buffer_move_mark (buffer, self->invalid_end, end);
+    }
+
+  ide_highlight_engine_queue_work (self);
+
+  IDE_EXIT;
+}
+
+/**
+ * ide_highlight_engine_get_style:
+ * @self: the #IdeHighlightEngine
+ * @style_name: the name of the style to retrieve
+ *
+ * A #GtkTextTag for @style_name.
+ *
+ * Returns: (transfer none): a #GtkTextTag.
+ *
+ * Since: 3.32
+ */
+GtkTextTag *
+ide_highlight_engine_get_style (IdeHighlightEngine *self,
+                                const gchar        *style_name)
+{
+  return get_tag_from_style (self, style_name, FALSE);
+}
+
+void
+ide_highlight_engine_pause (IdeHighlightEngine *self)
+{
+  g_return_if_fail (IDE_IS_HIGHLIGHT_ENGINE (self));
+
+  dzl_signal_group_block (self->signal_group);
+}
+
+void
+ide_highlight_engine_unpause (IdeHighlightEngine *self)
+{
+  g_autoptr(IdeBuffer) buffer = NULL;
+
+  g_return_if_fail (IDE_IS_HIGHLIGHT_ENGINE (self));
+  g_return_if_fail (self->signal_group != NULL);
+
+  dzl_signal_group_unblock (self->signal_group);
+
+  buffer = g_weak_ref_get (&self->buffer_wref);
+
+  if (buffer != NULL)
+    {
+      /* Notify of some blocked signals */
+      ide_highlight_engine__notify_style_scheme_cb (self, NULL, buffer);
+      ide_highlight_engine__notify_language_cb (self, NULL, buffer);
+
+      ide_highlight_engine_reload (self);
+    }
+}
diff --git a/src/libide/code/ide-highlight-engine.h b/src/libide/code/ide-highlight-engine.h
new file mode 100644
index 000000000..eb4012278
--- /dev/null
+++ b/src/libide/code/ide-highlight-engine.h
@@ -0,0 +1,62 @@
+/* ide-highlight-engine.h
+ *
+ * Copyright 2015-2019 Christian Hergert <christian hergert me>
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#if !defined (IDE_CODE_INSIDE) && !defined (IDE_CODE_COMPILATION)
+# error "Only <libide-code.h> can be included directly."
+#endif
+
+#include <libide-core.h>
+
+#include "ide-code-types.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_HIGHLIGHT_ENGINE (ide_highlight_engine_get_type())
+
+IDE_AVAILABLE_IN_3_32
+G_DECLARE_FINAL_TYPE (IdeHighlightEngine, ide_highlight_engine, IDE, HIGHLIGHT_ENGINE, IdeObject)
+
+IDE_AVAILABLE_IN_3_32
+IdeHighlightEngine *ide_highlight_engine_new             (IdeBuffer          *buffer);
+IDE_AVAILABLE_IN_3_32
+IdeBuffer          *ide_highlight_engine_get_buffer      (IdeHighlightEngine *self);
+IDE_AVAILABLE_IN_3_32
+IdeHighlighter     *ide_highlight_engine_get_highlighter (IdeHighlightEngine *self);
+IDE_AVAILABLE_IN_3_32
+void                ide_highlight_engine_rebuild         (IdeHighlightEngine *self);
+IDE_AVAILABLE_IN_3_32
+void                ide_highlight_engine_clear           (IdeHighlightEngine *self);
+IDE_AVAILABLE_IN_3_32
+void                ide_highlight_engine_invalidate      (IdeHighlightEngine *self,
+                                                          const GtkTextIter  *begin,
+                                                          const GtkTextIter  *end);
+IDE_AVAILABLE_IN_3_32
+GtkTextTag         *ide_highlight_engine_get_style       (IdeHighlightEngine *self,
+                                                          const gchar        *style_name);
+IDE_AVAILABLE_IN_3_32
+void                ide_highlight_engine_pause           (IdeHighlightEngine *self);
+IDE_AVAILABLE_IN_3_32
+void                ide_highlight_engine_unpause         (IdeHighlightEngine *self);
+IDE_AVAILABLE_IN_3_32
+void                ide_highlight_engine_advance         (IdeHighlightEngine *self);
+
+G_END_DECLS
diff --git a/src/libide/code/ide-highlight-index.c b/src/libide/code/ide-highlight-index.c
new file mode 100644
index 000000000..464badd0e
--- /dev/null
+++ b/src/libide/code/ide-highlight-index.c
@@ -0,0 +1,244 @@
+/* ide-highlight-index.c
+ *
+ * Copyright 2015-2019 Christian Hergert <christian hergert me>
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-highlight-index"
+
+#include "config.h"
+
+#include <dazzle.h>
+#include <string.h>
+
+#include "ide-highlight-index.h"
+
+G_DEFINE_BOXED_TYPE (IdeHighlightIndex, ide_highlight_index,
+                     ide_highlight_index_ref, ide_highlight_index_unref)
+
+struct _IdeHighlightIndex
+{
+  /* For debugging info */
+  guint          count;
+  gsize          chunk_size;
+
+  GStringChunk  *strings;
+  GHashTable    *index;
+  GVariant      *variant;
+};
+
+IdeHighlightIndex *
+ide_highlight_index_new (void)
+{
+  IdeHighlightIndex *ret;
+
+  ret = g_atomic_rc_box_new0 (IdeHighlightIndex);
+  ret->strings = g_string_chunk_new (ide_get_system_page_size ());
+  ret->index = g_hash_table_new (g_str_hash, g_str_equal);
+
+  return ret;
+}
+
+IdeHighlightIndex *
+ide_highlight_index_new_from_variant (GVariant *variant)
+{
+  IdeHighlightIndex *self;
+
+  self = ide_highlight_index_new ();
+
+  if (variant != NULL)
+    {
+      g_autoptr(GVariant) unboxed = NULL;
+
+      self->variant = g_variant_ref_sink (variant);
+
+      if (g_variant_is_of_type (variant, G_VARIANT_TYPE_VARIANT))
+        variant = unboxed = g_variant_get_variant (variant);
+
+      if (g_variant_is_of_type (variant, G_VARIANT_TYPE_VARDICT))
+        {
+          GVariantIter iter;
+          GVariant *value;
+          const gchar *tag;
+
+          g_variant_iter_init (&iter, variant);
+
+          while (g_variant_iter_loop (&iter, "{&sv}", &tag, &value))
+            {
+              if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING_ARRAY))
+                {
+                  g_autofree const gchar **strv = NULL;
+                  gsize len;
+
+                  strv = g_variant_get_strv (value, &len);
+
+                  for (gsize i = 0; i < len; i++)
+                    {
+                      const gchar *word = strv[i];
+
+                      if (g_hash_table_contains (self->index, word))
+                        continue;
+
+                      /* word is guaranteed to be alive and valid inside our
+                       * variant that we are storing. No need to add to the
+                       * string chunk too.
+                       */
+                      g_hash_table_insert (self->index, (gchar *)word, (gchar *)tag);
+                      self->count++;
+                    }
+                }
+            }
+        }
+    }
+
+  return self;
+}
+
+void
+ide_highlight_index_insert (IdeHighlightIndex *self,
+                            const gchar       *word,
+                            gpointer           tag)
+{
+  gchar *key;
+
+  g_assert (self);
+  g_assert (tag != NULL);
+
+  if (word == NULL || word[0] == '\0')
+    return;
+
+  if (g_hash_table_contains (self->index, word))
+    return;
+
+  self->count++;
+  self->chunk_size += strlen (word) + 1;
+
+  key = g_string_chunk_insert (self->strings, word);
+  g_hash_table_insert (self->index, key, tag);
+}
+
+/**
+ * ide_highlight_index_lookup:
+ * @self: An #IdeHighlightIndex.
+ *
+ * Gets the pointer tag that was registered for @word, or %NULL.  This can be
+ * any arbitrary value. Some highlight engines might use it to point at
+ * internal structures or strings they know about to optimize later work.
+ *
+ * Returns: (transfer none) (nullable): Highlighter specific tag.
+ *
+ * Since: 3.32
+ */
+gpointer
+ide_highlight_index_lookup (IdeHighlightIndex *self,
+                            const gchar       *word)
+{
+  g_assert (self);
+  g_assert (word);
+
+  return g_hash_table_lookup (self->index, word);
+}
+
+IdeHighlightIndex *
+ide_highlight_index_ref (IdeHighlightIndex *self)
+{
+  g_assert (self);
+
+  return g_atomic_rc_box_acquire (self);
+}
+
+static void
+ide_highlight_index_finalize (IdeHighlightIndex *self)
+{
+  IDE_ENTRY;
+
+  g_clear_pointer (&self->strings, g_string_chunk_free);
+  g_clear_pointer (&self->index, g_hash_table_unref);
+
+  IDE_EXIT;
+}
+
+void
+ide_highlight_index_unref (IdeHighlightIndex *self)
+{
+  g_assert (self);
+
+  g_atomic_rc_box_release_full (self, (GDestroyNotify)ide_highlight_index_finalize);
+}
+
+void
+ide_highlight_index_dump (IdeHighlightIndex *self)
+{
+  g_autofree gchar *format = NULL;
+
+  g_assert (self);
+
+  format = g_format_size (self->chunk_size);
+  g_debug ("IdeHighlightIndex (%p) contains %u items and consumes %s.",
+           self, self->count, format);
+}
+
+/**
+ * ide_highlight_index_to_variant:
+ * @self: a #IdeHighlightIndex
+ *
+ * Creates a variant to represent the index. Useful to transport across IPC boundaries.
+ *
+ * Returns: (transfer full): a #GVariant
+ *
+ * Since: 3.32
+ */
+GVariant *
+ide_highlight_index_to_variant (IdeHighlightIndex *self)
+{
+  g_autoptr(GHashTable) arrays = NULL;
+  GHashTableIter iter;
+  const gchar *k, *v;
+  GPtrArray *ar;
+  GVariantDict dict;
+
+  g_return_val_if_fail (self != NULL, NULL);
+
+  arrays = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify)g_ptr_array_unref);
+
+  g_hash_table_iter_init (&iter, self->index);
+  while (g_hash_table_iter_next (&iter, (gpointer *)&k, (gpointer *)&v))
+    {
+      if G_UNLIKELY (!(ar = g_hash_table_lookup (arrays, v)))
+        {
+          ar = g_ptr_array_new ();
+          g_hash_table_insert (arrays, (gchar *)v, ar);
+        }
+
+      g_ptr_array_add (ar, (gchar *)k);
+    }
+
+  g_variant_dict_init (&dict, NULL);
+
+  g_hash_table_iter_init (&iter, arrays);
+  while (g_hash_table_iter_next (&iter, (gpointer *)&k, (gpointer *)&ar))
+    {
+      GVariant *keys;
+
+      g_ptr_array_add (ar, NULL);
+
+      keys = g_variant_new_strv ((const gchar * const *)ar->pdata, ar->len - 1);
+      g_variant_dict_insert_value (&dict, k, g_steal_pointer (&keys));
+    }
+
+  return g_variant_take_ref (g_variant_dict_end (&dict));
+}
diff --git a/src/libide/code/ide-highlight-index.h b/src/libide/code/ide-highlight-index.h
new file mode 100644
index 000000000..b5649cbbb
--- /dev/null
+++ b/src/libide/code/ide-highlight-index.h
@@ -0,0 +1,61 @@
+/* ide-highlight-index.h
+ *
+ * Copyright 2015-2019 Christian Hergert <christian hergert me>
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#if !defined (IDE_CODE_INSIDE) && !defined (IDE_CODE_COMPILATION)
+# error "Only <libide-code.h> can be included directly."
+#endif
+
+#include <libide-core.h>
+
+#include "ide-code-types.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_HIGHLIGHT_INDEX (ide_highlight_index_get_type())
+
+typedef struct _IdeHighlightIndex IdeHighlightIndex;
+
+IDE_AVAILABLE_IN_3_32
+GType              ide_highlight_index_get_type         (void);
+IDE_AVAILABLE_IN_3_32
+IdeHighlightIndex *ide_highlight_index_new              (void);
+IDE_AVAILABLE_IN_3_32
+IdeHighlightIndex *ide_highlight_index_new_from_variant (GVariant          *variant);
+IDE_AVAILABLE_IN_3_32
+IdeHighlightIndex *ide_highlight_index_ref              (IdeHighlightIndex *self);
+IDE_AVAILABLE_IN_3_32
+void               ide_highlight_index_unref            (IdeHighlightIndex *self);
+IDE_AVAILABLE_IN_3_32
+void               ide_highlight_index_insert           (IdeHighlightIndex *self,
+                                                         const gchar       *word,
+                                                         gpointer           tag);
+IDE_AVAILABLE_IN_3_32
+gpointer           ide_highlight_index_lookup           (IdeHighlightIndex *self,
+                                                         const gchar       *word);
+IDE_AVAILABLE_IN_3_32
+void               ide_highlight_index_dump             (IdeHighlightIndex *self);
+IDE_AVAILABLE_IN_3_32
+GVariant          *ide_highlight_index_to_variant       (IdeHighlightIndex *self);
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (IdeHighlightIndex, ide_highlight_index_unref)
+
+G_END_DECLS
diff --git a/src/libide/code/ide-highlighter.c b/src/libide/code/ide-highlighter.c
new file mode 100644
index 000000000..d156dc4da
--- /dev/null
+++ b/src/libide/code/ide-highlighter.c
@@ -0,0 +1,93 @@
+/* ide-highlighter.c
+ *
+ * Copyright 2015-2019 Christian Hergert <christian hergert me>
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-highlighter"
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include "ide-highlighter.h"
+
+G_DEFINE_INTERFACE (IdeHighlighter, ide_highlighter, IDE_TYPE_OBJECT)
+
+static void
+ide_highlighter_real_update (IdeHighlighter       *self,
+                             IdeHighlightCallback  callback,
+                             const GtkTextIter    *range_begin,
+                             const GtkTextIter    *range_end,
+                             GtkTextIter          *location)
+{
+}
+
+static void
+ide_highlighter_real_set_engine (IdeHighlighter     *self,
+                                 IdeHighlightEngine *engine)
+{
+}
+
+static void
+ide_highlighter_default_init (IdeHighlighterInterface *iface)
+{
+  iface->update = ide_highlighter_real_update;
+  iface->set_engine = ide_highlighter_real_set_engine;
+}
+
+/**
+ * ide_highlighter_update:
+ * @self: an #IdeHighlighter.
+ * @callback: (scope call): A callback to apply a given style.
+ * @range_begin: The beginning of the range to update.
+ * @range_end: The end of the range to update.
+ * @location: (out): How far the highlighter got in the update.
+ *
+ * Incrementally processes more of the buffer for highlighting.  If @callback
+ * returns %IDE_HIGHLIGHT_STOP, then this vfunc should stop processing and
+ * return, having set @location to the current position of processing.
+ *
+ * If processing the entire range was successful, then @location should be set
+ * to @range_end.
+ *
+ * Since: 3.32
+ */
+void
+ide_highlighter_update (IdeHighlighter       *self,
+                        IdeHighlightCallback  callback,
+                        const GtkTextIter    *range_begin,
+                        const GtkTextIter    *range_end,
+                        GtkTextIter          *location)
+{
+  g_return_if_fail (IDE_IS_HIGHLIGHTER (self));
+  g_return_if_fail (callback != NULL);
+  g_return_if_fail (range_begin != NULL);
+  g_return_if_fail (range_end != NULL);
+  g_return_if_fail (location != NULL);
+
+  IDE_HIGHLIGHTER_GET_IFACE (self)->update (self, callback, range_begin, range_end, location);
+}
+
+void
+ide_highlighter_load (IdeHighlighter *self)
+{
+  g_return_if_fail (IDE_IS_HIGHLIGHTER (self));
+
+  if (IDE_HIGHLIGHTER_GET_IFACE (self)->load)
+    IDE_HIGHLIGHTER_GET_IFACE (self)->load (self);
+}
diff --git a/src/libide/code/ide-highlighter.h b/src/libide/code/ide-highlighter.h
new file mode 100644
index 000000000..06beabdb2
--- /dev/null
+++ b/src/libide/code/ide-highlighter.h
@@ -0,0 +1,91 @@
+/* ide-highlighter.h
+ *
+ * Copyright 2015-2019 Christian Hergert <christian hergert me>
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#if !defined (IDE_CODE_INSIDE) && !defined (IDE_CODE_COMPILATION)
+# error "Only <libide-code.h> can be included directly."
+#endif
+
+#include <gtk/gtk.h>
+#include <libide-core.h>
+
+#include "ide-code-types.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_HIGHLIGHTER (ide_highlighter_get_type())
+
+IDE_AVAILABLE_IN_3_32
+G_DECLARE_INTERFACE (IdeHighlighter, ide_highlighter, IDE, HIGHLIGHTER, IdeObject)
+
+typedef enum
+{
+  IDE_HIGHLIGHT_STOP,
+  IDE_HIGHLIGHT_CONTINUE,
+} IdeHighlightResult;
+
+typedef IdeHighlightResult (*IdeHighlightCallback) (const GtkTextIter *begin,
+                                                    const GtkTextIter *end,
+                                                    const gchar       *style_name);
+
+struct _IdeHighlighterInterface
+{
+  GTypeInterface parent_interface;
+
+  /**
+   * IdeHighlighter::update:
+   *
+   * #IdeHighlighter should call callback() with the range and style-name of
+   * the style to apply. Callback will ensure that the style exists and style
+   * it appropriately based on the style scheme.
+   *
+   * If @callback returns %IDE_HIGHLIGHT_STOP, the caller has run out of its
+   * time slice and should yield back to the highlight engine.
+   *
+   * @location should be set to the position that the highlighter got to
+   * before yielding back to the engine.
+   *
+   * Since: 3.32
+   */
+  void (*update)     (IdeHighlighter       *self,
+                      IdeHighlightCallback  callback,
+                      const GtkTextIter    *range_begin,
+                      const GtkTextIter    *range_end,
+                      GtkTextIter          *location);
+
+  void (*set_engine) (IdeHighlighter       *self,
+                      IdeHighlightEngine   *engine);
+
+  void (*load)       (IdeHighlighter       *self);
+};
+
+IDE_AVAILABLE_IN_3_32
+void ide_highlighter_load                    (IdeHighlighter       *self);
+IDE_AVAILABLE_IN_3_32
+void ide_highlighter_update                  (IdeHighlighter       *self,
+                                              IdeHighlightCallback  callback,
+                                              const GtkTextIter    *range_begin,
+                                              const GtkTextIter    *range_end,
+                                              GtkTextIter          *location);
+void _ide_highlighter_set_highlighter_engine (IdeHighlighter       *self,
+                                              IdeHighlightEngine   *highlight_engine) G_GNUC_INTERNAL;
+
+G_END_DECLS
diff --git a/src/libide/files/ide-indent-style.h b/src/libide/code/ide-indent-style.h
similarity index 87%
rename from src/libide/files/ide-indent-style.h
rename to src/libide/code/ide-indent-style.h
index 9ae8be890..c0129332d 100644
--- a/src/libide/files/ide-indent-style.h
+++ b/src/libide/code/ide-indent-style.h
@@ -20,6 +20,10 @@
 
 #pragma once
 
+#if !defined (IDE_CODE_INSIDE) && !defined (IDE_CODE_COMPILATION)
+# error "Only <libide-code.h> can be included directly."
+#endif
+
 #include <glib.h>
 
 G_BEGIN_DECLS
diff --git a/src/libide/gsettings/ide-language-defaults.c b/src/libide/code/ide-language-defaults.c
similarity index 99%
rename from src/libide/gsettings/ide-language-defaults.c
rename to src/libide/code/ide-language-defaults.c
index b78805a29..373446abd 100644
--- a/src/libide/gsettings/ide-language-defaults.c
+++ b/src/libide/code/ide-language-defaults.c
@@ -24,12 +24,9 @@
 
 #include <errno.h>
 #include <glib/gi18n.h>
+#include <libide-threading.h>
 
-#include "ide-global.h"
-#include "ide-debug.h"
-
-#include "gsettings/ide-language-defaults.h"
-#include "threading/ide-task.h"
+#include "ide-language-defaults.h"
 
 #define SCHEMA_ID "org.gnome.builder.editor.language"
 #define PATH_BASE "/org/gnome/builder/editor/language/"
@@ -56,6 +53,9 @@ strv_equal (gchar **a,
       if (*a == NULL && *b == NULL)
         return TRUE;
 
+      if (*a == NULL || *b == NULL)
+        return FALSE;
+
       if (g_strcmp0 (*a, *b) == 0)
         continue;
 
diff --git a/src/libide/gsettings/ide-language-defaults.h b/src/libide/code/ide-language-defaults.h
similarity index 100%
rename from src/libide/gsettings/ide-language-defaults.h
rename to src/libide/code/ide-language-defaults.h
diff --git a/src/libide/code/ide-language.c b/src/libide/code/ide-language.c
new file mode 100644
index 000000000..b0aabe392
--- /dev/null
+++ b/src/libide/code/ide-language.c
@@ -0,0 +1,109 @@
+/* ide-language.c
+ *
+ * Copyright 2016-2019 Christian Hergert <chergert redhat com>
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-language"
+
+#include "config.h"
+
+#include <libide-io.h>
+#include <string.h>
+#include <tmpl-glib.h>
+
+#include "ide-language.h"
+
+gchar *
+ide_language_format_header (GtkSourceLanguage *self,
+                            const gchar       *header)
+{
+  IdeLineReader reader;
+  const gchar *first_prefix;
+  const gchar *last_prefix;
+  const gchar *line_prefix;
+  const gchar *line;
+  gboolean first = TRUE;
+  GString *outstr;
+  gsize len;
+  guint prefix_len;
+
+  g_return_val_if_fail (GTK_SOURCE_IS_LANGUAGE (self), NULL);
+  g_return_val_if_fail (header != NULL, NULL);
+
+  first_prefix = gtk_source_language_get_metadata (self, "block-comment-start");
+  last_prefix = gtk_source_language_get_metadata (self, "block-comment-end");
+  line_prefix = gtk_source_language_get_metadata (self, "line-comment-start");
+
+  if ((g_strcmp0 (first_prefix, "/*") == 0) &&
+      (g_strcmp0 (last_prefix, "*/") == 0))
+    line_prefix = " *";
+
+  if (first_prefix == NULL || last_prefix == NULL)
+    {
+      first_prefix = line_prefix;
+      last_prefix = line_prefix;
+    }
+
+  prefix_len = strlen (first_prefix);
+
+  outstr = g_string_new (NULL);
+
+  ide_line_reader_init (&reader, (gchar *)header, -1);
+
+  while (NULL != (line = ide_line_reader_next (&reader, &len)))
+    {
+      if (first)
+        {
+          g_string_append (outstr, first_prefix);
+          first = FALSE;
+        }
+      else if (line_prefix == NULL)
+        {
+          for (guint i = 0; i < prefix_len; i++)
+            g_string_append_c (outstr, ' ');
+        }
+      else
+        {
+          g_string_append (outstr, line_prefix);
+        }
+
+      if (len)
+        {
+          g_string_append_c (outstr, ' ');
+          g_string_append_len (outstr, line, len);
+        }
+
+      /* Lines ending in expansion need an extra \n */
+      if (outstr->len > 2 &&
+          outstr->str[outstr->len - 2] == '}' &&
+          outstr->str[outstr->len - 1] == '}')
+        g_string_append_c (outstr, '\n');
+
+      g_string_append_c (outstr, '\n');
+    }
+
+  if (last_prefix && g_strcmp0 (first_prefix, last_prefix) != 0)
+    {
+      if (line_prefix && *line_prefix == ' ')
+        g_string_append_c (outstr, ' ');
+      g_string_append (outstr, last_prefix);
+      g_string_append_c (outstr, '\n');
+    }
+
+  return g_string_free (outstr, FALSE);
+}
diff --git a/src/libide/code/ide-language.h b/src/libide/code/ide-language.h
new file mode 100644
index 000000000..9af163164
--- /dev/null
+++ b/src/libide/code/ide-language.h
@@ -0,0 +1,36 @@
+/* ide-language.h
+ *
+ * Copyright 2016-2019 Christian Hergert <chergert redhat com>
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#if !defined (IDE_CODE_INSIDE) && !defined (IDE_CODE_COMPILATION)
+# error "Only <libide-code.h> can be included directly."
+#endif
+
+#include <gtksourceview/gtksource.h>
+#include <libide-core.h>
+
+G_BEGIN_DECLS
+
+IDE_AVAILABLE_IN_3_32
+gchar *ide_language_format_header (GtkSourceLanguage *language,
+                                   const gchar       *header);
+
+G_END_DECLS
diff --git a/src/libide/code/ide-location.c b/src/libide/code/ide-location.c
new file mode 100644
index 000000000..27c903af0
--- /dev/null
+++ b/src/libide/code/ide-location.c
@@ -0,0 +1,503 @@
+/* ide-location.c
+ *
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-location"
+
+#include "config.h"
+
+#include "ide-location.h"
+
+typedef struct
+{
+  GFile *file;
+  gint   line;
+  gint   line_offset;
+  gint   offset;
+} IdeLocationPrivate;
+
+enum {
+  PROP_0,
+  PROP_FILE,
+  PROP_LINE,
+  PROP_LINE_OFFSET,
+  PROP_OFFSET,
+  N_PROPS
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (IdeLocation, ide_location, G_TYPE_OBJECT)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+ide_location_set_file (IdeLocation *self,
+                       GFile       *file)
+{
+  IdeLocationPrivate *priv = ide_location_get_instance_private (self);
+
+  g_assert (IDE_IS_LOCATION (self));
+
+  g_set_object (&priv->file, file);
+}
+
+static void
+ide_location_set_line (IdeLocation *self,
+                       gint         line)
+{
+  IdeLocationPrivate *priv = ide_location_get_instance_private (self);
+
+  g_assert (IDE_IS_LOCATION (self));
+
+  priv->line = CLAMP (line, -1, G_MAXINT);
+}
+
+static void
+ide_location_set_line_offset (IdeLocation *self,
+                              gint         line_offset)
+{
+  IdeLocationPrivate *priv = ide_location_get_instance_private (self);
+
+  g_assert (IDE_IS_LOCATION (self));
+
+  priv->line_offset = CLAMP (line_offset, -1, G_MAXINT);
+}
+
+static void
+ide_location_set_offset (IdeLocation *self,
+                         gint         offset)
+{
+  IdeLocationPrivate *priv = ide_location_get_instance_private (self);
+
+  g_assert (IDE_IS_LOCATION (self));
+
+  priv->offset = CLAMP (offset, -1, G_MAXINT);
+}
+
+static void
+ide_location_dispose (GObject *object)
+{
+  IdeLocation *self = (IdeLocation *)object;
+  IdeLocationPrivate *priv = ide_location_get_instance_private (self);
+
+  g_clear_object (&priv->file);
+
+  G_OBJECT_CLASS (ide_location_parent_class)->dispose (object);
+}
+
+static void
+ide_location_get_property (GObject    *object,
+                           guint       prop_id,
+                           GValue     *value,
+                           GParamSpec *pspec)
+{
+  IdeLocation *self = IDE_LOCATION (object);
+
+  switch (prop_id)
+    {
+    case PROP_FILE:
+      g_value_set_object (value, ide_location_get_file (self));
+      break;
+
+    case PROP_LINE:
+      g_value_set_int (value, ide_location_get_line (self));
+      break;
+
+    case PROP_LINE_OFFSET:
+      g_value_set_int (value, ide_location_get_line_offset (self));
+      break;
+
+    case PROP_OFFSET:
+      g_value_set_int (value, ide_location_get_offset (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_location_set_property (GObject      *object,
+                           guint         prop_id,
+                           const GValue *value,
+                           GParamSpec   *pspec)
+{
+  IdeLocation *self = IDE_LOCATION (object);
+
+  switch (prop_id)
+    {
+    case PROP_FILE:
+      ide_location_set_file (self, g_value_get_object (value));
+      break;
+
+    case PROP_LINE:
+      ide_location_set_line (self, g_value_get_int (value));
+      break;
+
+    case PROP_LINE_OFFSET:
+      ide_location_set_line_offset (self, g_value_get_int (value));
+      break;
+
+    case PROP_OFFSET:
+      ide_location_set_offset (self, g_value_get_int (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_location_class_init (IdeLocationClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = ide_location_dispose;
+  object_class->get_property = ide_location_get_property;
+  object_class->set_property = ide_location_set_property;
+
+  properties [PROP_FILE] =
+    g_param_spec_object ("file",
+                         "File",
+                         "The file representing the location",
+                         G_TYPE_FILE,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_LINE] =
+    g_param_spec_int ("line",
+                      "Line",
+                      "The line number within the file, starting from 0 or -1 for unknown",
+                      -1, G_MAXINT, -1,
+                      (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_LINE_OFFSET] =
+    g_param_spec_int ("line-offset",
+                      "Line Offset",
+                      "The offset within the line, starting from 0 or -1 for unknown",
+                      -1, G_MAXINT, -1,
+                      (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_OFFSET] =
+    g_param_spec_int ("offset",
+                      "Offset",
+                      "The offset within the file in characters, or -1 if unknown",
+                      -1, G_MAXINT, -1,
+                      (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_location_init (IdeLocation *self)
+{
+  IdeLocationPrivate *priv = ide_location_get_instance_private (self);
+
+  priv->line = -1;
+  priv->line_offset = -1;
+  priv->offset = -1;
+}
+
+/**
+ * ide_location_get_file:
+ * @self: a #IdeLocation
+ *
+ * Gets the file within the location.
+ *
+ * Returns: (transfer none) (nullable): a #GFile or %NULL
+ *
+ * Since: 3.32
+ */
+GFile *
+ide_location_get_file (IdeLocation *self)
+{
+  IdeLocationPrivate *priv = ide_location_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_LOCATION (self), NULL);
+
+  return priv->file;
+}
+
+/**
+ * ide_location_get_line:
+ * @self: a #IdeLocation
+ *
+ * Gets the line within the #IdeLocation:file, or -1 if it is unknown.
+ *
+ * Returns: the line number, or -1.
+ *
+ * Since: 3.32
+ */
+gint
+ide_location_get_line (IdeLocation *self)
+{
+  IdeLocationPrivate *priv = ide_location_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_LOCATION (self), -1);
+
+  return priv->line;
+}
+
+/**
+ * ide_location_get_line_offset:
+ * @self: a #IdeLocation
+ *
+ * Gets the offset within the #IdeLocation:line, or -1 if it is unknown.
+ *
+ * Returns: the line offset, or -1.
+ *
+ * Since: 3.32
+ */
+gint
+ide_location_get_line_offset (IdeLocation *self)
+{
+  IdeLocationPrivate *priv = ide_location_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_LOCATION (self), -1);
+
+  return priv->line_offset;
+}
+
+/**
+ * ide_location_get_offset:
+ * @self: a #IdeLocation
+ *
+ * Gets the offset within the file in characters, or -1 if it is unknown.
+ *
+ * Returns: the line offset, or -1.
+ *
+ * Since: 3.32
+ */
+gint
+ide_location_get_offset (IdeLocation *self)
+{
+  IdeLocationPrivate *priv = ide_location_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_LOCATION (self), -1);
+
+  return priv->offset;
+}
+
+/**
+ * ide_location_dup:
+ * @self: a #IdeLocation
+ *
+ * Makes a deep copy of @self.
+ *
+ * Returns: (transfer full): a new #IdeLocation
+ *
+ * Since: 3.32
+ */
+IdeLocation *
+ide_location_dup (IdeLocation *self)
+{
+  IdeLocationPrivate *priv = ide_location_get_instance_private (self);
+
+  g_return_val_if_fail (!self || IDE_IS_LOCATION (self), NULL);
+
+  if (self == NULL)
+    return NULL;
+
+  return g_object_new (IDE_TYPE_LOCATION,
+                       "file", priv->file,
+                       "line", priv->line,
+                       "line-offset", priv->line_offset,
+                       "offset", priv->offset,
+                       NULL);
+}
+
+/**
+ * ide_location_to_variant:
+ * @self: a #IdeLocation
+ *
+ * Serializes the location into a variant that can be used to transport
+ * across IPC boundaries.
+ *
+ * This function will never return a variant with a floating reference.
+ *
+ * Returns: (transfer full): a #GVariant
+ *
+ * Since: 3.32
+ */
+GVariant *
+ide_location_to_variant (IdeLocation *self)
+{
+  IdeLocationPrivate *priv = ide_location_get_instance_private (self);
+  g_autofree gchar *uri = NULL;
+  GVariantDict dict;
+
+  g_return_val_if_fail (self != NULL, NULL);
+
+  g_variant_dict_init (&dict, NULL);
+
+  uri = g_file_get_uri (priv->file);
+
+  g_variant_dict_insert (&dict, "uri", "s", uri);
+  g_variant_dict_insert (&dict, "line", "i", priv->line);
+  g_variant_dict_insert (&dict, "line-offset", "i", priv->line_offset);
+
+  return g_variant_take_ref (g_variant_dict_end (&dict));
+}
+
+IdeLocation *
+ide_location_new (GFile *file,
+                  gint   line,
+                  gint   line_offset)
+{
+  g_return_val_if_fail (G_IS_FILE (file), NULL);
+
+  line = CLAMP (line, -1, G_MAXINT);
+  line_offset = CLAMP (line_offset, -1, G_MAXINT);
+
+  return g_object_new (IDE_TYPE_LOCATION,
+                       "file", file,
+                       "line", line,
+                       "line-offset", line_offset,
+                       NULL);
+}
+
+/**
+ * ide_location_new_with_offset:
+ * @file: a #GFile
+ * @line: a line number starting from 0, or -1 if unknown
+ * @line_offset: a line offset starting from 0, or -1 if unknown
+ * @offset: a charcter offset in file starting from 0, or -1 if unknown
+ *
+ * Returns: (transfer full): an #IdeLocation
+ *
+ * Since: 3.32
+ */
+IdeLocation *
+ide_location_new_with_offset (GFile *file,
+                              gint   line,
+                              gint   line_offset,
+                              gint   offset)
+{
+  g_return_val_if_fail (G_IS_FILE (file), NULL);
+
+  line = CLAMP (line, -1, G_MAXINT);
+  line_offset = CLAMP (line_offset, -1, G_MAXINT);
+  offset = CLAMP (offset, -1, G_MAXINT);
+
+  return g_object_new (IDE_TYPE_LOCATION,
+                       "file", file,
+                       "line", line,
+                       "line-offset", line_offset,
+                       "offset", offset,
+                       NULL);
+}
+
+/**
+ * ide_location_new_from_variant:
+ * @variant: (nullable): a #GVariant or %NULL
+ *
+ * Creates a new #IdeLocation using the serialized form from a
+ * previously serialized #GVariant.
+ *
+ * As a convenience, if @variant is %NULL, %NULL is returned.
+ *
+ * See also: ide_location_to_variant()
+ *
+ * Returns: (transfer full) (nullable): a #GVariant if succesful;
+ *   otherwise %NULL.
+ *
+ * Since: 3.32
+ */
+IdeLocation *
+ide_location_new_from_variant (GVariant *variant)
+{
+  g_autoptr(GVariant) unboxed = NULL;
+  g_autoptr(GFile) file = NULL;
+  IdeLocation *self = NULL;
+  GVariantDict dict;
+  const gchar *uri;
+  guint32 line;
+  guint32 line_offset;
+
+  if (variant == NULL)
+    return NULL;
+
+  if (g_variant_is_of_type (variant, G_VARIANT_TYPE_VARIANT))
+    variant = unboxed = g_variant_get_variant (variant);
+
+  g_variant_dict_init (&dict, variant);
+
+  if (!g_variant_dict_lookup (&dict, "uri", "&s", &uri))
+    goto failure;
+
+  if (!g_variant_dict_lookup (&dict, "line", "i", &line))
+    line = 0;
+
+  if (!g_variant_dict_lookup (&dict, "line-offset", "i", &line_offset))
+    line_offset = 0;
+
+  file = g_file_new_for_uri (uri);
+
+  self = ide_location_new (file, line, line_offset);
+
+failure:
+  g_variant_dict_clear (&dict);
+
+  return self;
+}
+
+static gint
+file_compare (GFile *a,
+              GFile *b)
+{
+  g_autofree gchar *uri_a = g_file_get_uri (a);
+  g_autofree gchar *uri_b = g_file_get_uri (b);
+
+  return g_strcmp0 (uri_a, uri_b);
+}
+
+gboolean
+ide_location_compare (IdeLocation *a,
+                      IdeLocation *b)
+{
+  IdeLocationPrivate *priv_a = ide_location_get_instance_private (a);
+  IdeLocationPrivate *priv_b = ide_location_get_instance_private (b);
+  gint ret;
+
+  g_assert (IDE_IS_LOCATION (a));
+  g_assert (IDE_IS_LOCATION (b));
+
+  if (priv_a->file && priv_b->file)
+    {
+      if (0 != (ret = file_compare (priv_a->file, priv_b->file)))
+        return ret;
+    }
+  else if (priv_a->file)
+    return -1;
+  else if (priv_b->file)
+    return 1;
+
+  if (0 != (ret = priv_a->line - priv_b->line))
+    return ret;
+
+  return priv_a->line_offset - priv_b->line_offset;
+}
+
+guint
+ide_location_hash (IdeLocation *self)
+{
+  IdeLocationPrivate *priv = ide_location_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_LOCATION (self), 0);
+
+  return g_file_hash (priv->file) ^ g_int_hash (&priv->line) ^ g_int_hash (&priv->line_offset);
+}
diff --git a/src/libide/code/ide-location.h b/src/libide/code/ide-location.h
new file mode 100644
index 000000000..1820a8027
--- /dev/null
+++ b/src/libide/code/ide-location.h
@@ -0,0 +1,75 @@
+/* ide-location.h
+ *
+ * Copyright 2014-2019 Christian Hergert <chergert redhat com>
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#if !defined (IDE_CODE_INSIDE) && !defined (IDE_CODE_COMPILATION)
+# error "Only <libide-code.h> can be included directly."
+#endif
+
+#include <libide-core.h>
+
+#include "ide-code-types.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_LOCATION (ide_location_get_type())
+
+IDE_AVAILABLE_IN_3_32
+G_DECLARE_DERIVABLE_TYPE (IdeLocation, ide_location, IDE, LOCATION, GObject)
+
+struct _IdeLocationClass
+{
+  GObjectClass parent_class;
+
+  /*< private >*/
+  gpointer _reserved[16];
+};
+
+IDE_AVAILABLE_IN_3_32
+IdeLocation *ide_location_new_from_variant (GVariant    *variant);
+IDE_AVAILABLE_IN_3_32
+IdeLocation *ide_location_new              (GFile       *file,
+                                            gint         line,
+                                            gint         line_offset);
+IDE_AVAILABLE_IN_3_32
+IdeLocation *ide_location_new_with_offset  (GFile       *file,
+                                            gint         line,
+                                            gint         line_offset,
+                                            gint         offset);
+IDE_AVAILABLE_IN_3_32
+IdeLocation *ide_location_dup              (IdeLocation *self);
+IDE_AVAILABLE_IN_3_32
+gint         ide_location_get_line         (IdeLocation *self);
+IDE_AVAILABLE_IN_3_32
+gint         ide_location_get_line_offset  (IdeLocation *self);
+IDE_AVAILABLE_IN_3_32
+gint         ide_location_get_offset       (IdeLocation *self);
+IDE_AVAILABLE_IN_3_32
+GFile       *ide_location_get_file         (IdeLocation *self);
+IDE_AVAILABLE_IN_3_32
+GVariant    *ide_location_to_variant       (IdeLocation *self);
+IDE_AVAILABLE_IN_3_32
+gboolean     ide_location_compare          (IdeLocation *a,
+                                            IdeLocation *b);
+IDE_AVAILABLE_IN_3_32
+guint        ide_location_hash             (IdeLocation *self);
+
+G_END_DECLS
diff --git a/src/libide/code/ide-range.c b/src/libide/code/ide-range.c
new file mode 100644
index 000000000..e805f2e12
--- /dev/null
+++ b/src/libide/code/ide-range.c
@@ -0,0 +1,290 @@
+/* ide-range.c
+ *
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-range"
+
+#include "config.h"
+
+#include "ide-location.h"
+#include "ide-range.h"
+
+typedef struct
+{
+  IdeLocation *begin;
+  IdeLocation *end;
+} IdeRangePrivate;
+
+enum {
+  PROP_0,
+  PROP_BEGIN,
+  PROP_END,
+  N_PROPS
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (IdeRange, ide_range, G_TYPE_OBJECT)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+ide_range_set_begin (IdeRange    *self,
+                     IdeLocation *location)
+{
+  IdeRangePrivate *priv = ide_range_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_RANGE (self));
+  g_return_if_fail (IDE_IS_LOCATION (location));
+
+  g_set_object (&priv->begin, location);
+}
+
+static void
+ide_range_set_end (IdeRange    *self,
+                   IdeLocation *location)
+{
+  IdeRangePrivate *priv = ide_range_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_RANGE (self));
+  g_return_if_fail (IDE_IS_LOCATION (location));
+
+  g_set_object (&priv->end, location);
+}
+
+static void
+ide_range_finalize (GObject *object)
+{
+  IdeRange *self = (IdeRange *)object;
+  IdeRangePrivate *priv = ide_range_get_instance_private (self);
+
+  g_clear_object (&priv->begin);
+  g_clear_object (&priv->end);
+
+  G_OBJECT_CLASS (ide_range_parent_class)->finalize (object);
+}
+
+static void
+ide_range_get_property (GObject    *object,
+                        guint       prop_id,
+                        GValue     *value,
+                        GParamSpec *pspec)
+{
+  IdeRange *self = IDE_RANGE (object);
+
+  switch (prop_id)
+    {
+    case PROP_BEGIN:
+      g_value_set_object (value, ide_range_get_begin (self));
+      break;
+
+    case PROP_END:
+      g_value_set_object (value, ide_range_get_end (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_range_set_property (GObject      *object,
+                        guint         prop_id,
+                        const GValue *value,
+                        GParamSpec   *pspec)
+{
+  IdeRange *self = IDE_RANGE (object);
+
+  switch (prop_id)
+    {
+    case PROP_BEGIN:
+      ide_range_set_begin (self, g_value_get_object (value));
+      break;
+
+    case PROP_END:
+      ide_range_set_end (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_range_class_init (IdeRangeClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_range_finalize;
+  object_class->get_property = ide_range_get_property;
+  object_class->set_property = ide_range_set_property;
+
+  properties [PROP_BEGIN] =
+    g_param_spec_object ("begin",
+                         "Begin",
+                         "The start of the range",
+                         IDE_TYPE_LOCATION,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_END] =
+    g_param_spec_object ("end",
+                         "End",
+                         "The end of the range",
+                         IDE_TYPE_LOCATION,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_range_init (IdeRange *self)
+{
+}
+
+IdeRange *
+ide_range_new (IdeLocation *begin,
+               IdeLocation *end)
+{
+  g_return_val_if_fail (IDE_IS_LOCATION (begin), NULL);
+  g_return_val_if_fail (IDE_IS_LOCATION (end), NULL);
+
+  return g_object_new (IDE_TYPE_RANGE,
+                       "begin", begin,
+                       "end", end,
+                       NULL);
+}
+
+/**
+ * ide_range_get_begin:
+ * @self: a #IdeRange
+ *
+ * Returns: (transfer none): the beginning of the range
+ *
+ * Since: 3.32
+ */
+IdeLocation *
+ide_range_get_begin (IdeRange *self)
+{
+  IdeRangePrivate *priv = ide_range_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_RANGE (self), NULL);
+
+  return priv->begin;
+}
+
+/**
+ * ide_range_get_end:
+ * @self: a #IdeRange
+ *
+ * Returns: (transfer none): the end of the range
+ *
+ * Since: 3.32
+ */
+IdeLocation *
+ide_range_get_end (IdeRange *self)
+{
+  IdeRangePrivate *priv = ide_range_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_RANGE (self), NULL);
+
+  return priv->end;
+}
+
+/**
+ * ide_range_to_variant:
+ * @self: a #IdeRange
+ *
+ * Creates a variant to represent the range.
+ *
+ * This function will never return a floating variant.
+ *
+ * Returns: (transfer full): a #GVariant
+ *
+ * Since: 3.32
+ */
+GVariant *
+ide_range_to_variant (IdeRange *self)
+{
+  IdeRangePrivate *priv = ide_range_get_instance_private (self);
+  GVariantDict dict;
+
+  g_return_val_if_fail (IDE_IS_RANGE (self), NULL);
+
+  g_variant_dict_init (&dict, NULL);
+
+  if (priv->begin)
+    {
+      g_autoptr(GVariant) begin = NULL;
+
+      if ((begin = ide_location_to_variant (priv->begin)))
+        g_variant_dict_insert_value (&dict, "begin", begin);
+    }
+
+  if (priv->end)
+    {
+      g_autoptr(GVariant) end = NULL;
+
+      if ((end = ide_location_to_variant (priv->end)))
+        g_variant_dict_insert_value (&dict, "end", end);
+    }
+
+  return g_variant_take_ref (g_variant_dict_end (&dict));
+}
+
+/**
+ * ide_range_new_from_variant:
+ * @variant: a #GVariant
+ *
+ * Returns: (transfer full) (nullable): a new range or %NULL
+ *
+ * Since: 3.32
+ */
+IdeRange *
+ide_range_new_from_variant (GVariant *variant)
+{
+  g_autoptr(GVariant) unboxed = NULL;
+  g_autoptr(GVariant) vbegin = NULL;
+  g_autoptr(GVariant) vend = NULL;
+  g_autoptr(IdeLocation) begin = NULL;
+  g_autoptr(IdeLocation) end = NULL;
+  IdeRange *self = NULL;
+  GVariantDict dict;
+
+  if (variant == NULL)
+    return NULL;
+
+  if (g_variant_is_of_type (variant, G_VARIANT_TYPE_VARIANT))
+    variant = unboxed = g_variant_get_variant (variant);
+
+  g_variant_dict_init (&dict, variant);
+
+  if (!(vbegin = g_variant_dict_lookup_value (&dict, "begin", NULL)) ||
+      !(begin = ide_location_new_from_variant (vbegin)))
+    goto failure;
+
+  if (!(vend = g_variant_dict_lookup_value (&dict, "end", NULL)) ||
+      !(end = ide_location_new_from_variant (vend)))
+    goto failure;
+
+  self = ide_range_new (begin, end);
+
+  g_variant_dict_clear (&dict);
+
+failure:
+
+  return self;
+}
diff --git a/src/libide/code/ide-range.h b/src/libide/code/ide-range.h
new file mode 100644
index 000000000..f1e855c1d
--- /dev/null
+++ b/src/libide/code/ide-range.h
@@ -0,0 +1,58 @@
+/* ide-range.h
+ *
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#if !defined (IDE_CODE_INSIDE) && !defined (IDE_CODE_COMPILATION)
+# error "Only <libide-code.h> can be included directly."
+#endif
+
+#include <libide-core.h>
+
+#include "ide-code-types.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_RANGE (ide_range_get_type())
+
+IDE_AVAILABLE_IN_3_32
+G_DECLARE_DERIVABLE_TYPE (IdeRange, ide_range, IDE, RANGE, GObject)
+
+struct _IdeRangeClass
+{
+  GObjectClass parent_class;
+
+  /*< private >*/
+  gpointer _reserved[16];
+};
+
+IDE_AVAILABLE_IN_3_32
+IdeRange    *ide_range_new_from_variant (GVariant    *variant);
+IDE_AVAILABLE_IN_3_32
+IdeRange    *ide_range_new              (IdeLocation *begin,
+                                         IdeLocation *end);
+IDE_AVAILABLE_IN_3_32
+IdeLocation *ide_range_get_begin        (IdeRange    *self);
+IDE_AVAILABLE_IN_3_32
+IdeLocation *ide_range_get_end          (IdeRange    *self);
+IDE_AVAILABLE_IN_3_32
+GVariant    *ide_range_to_variant       (IdeRange    *self);
+
+G_END_DECLS
diff --git a/src/libide/rename/ide-rename-provider.c b/src/libide/code/ide-rename-provider.c
similarity index 81%
rename from src/libide/rename/ide-rename-provider.c
rename to src/libide/code/ide-rename-provider.c
index a769a5129..9f9cff717 100644
--- a/src/libide/rename/ide-rename-provider.c
+++ b/src/libide/code/ide-rename-provider.c
@@ -22,18 +22,15 @@
 
 #include "config.h"
 
-#include "ide-context.h"
-#include "ide-debug.h"
+#include <libide-threading.h>
 
-#include "buffers/ide-buffer.h"
-#include "rename/ide-rename-provider.h"
-#include "threading/ide-task.h"
+#include "ide-rename-provider.h"
 
 G_DEFINE_INTERFACE (IdeRenameProvider, ide_rename_provider, IDE_TYPE_OBJECT)
 
 static void
 ide_rename_provider_real_rename_async (IdeRenameProvider   *self,
-                                       IdeSourceLocation   *location,
+                                       IdeLocation   *location,
                                        const gchar         *new_name,
                                        GCancellable        *cancellable,
                                        GAsyncReadyCallback  callback,
@@ -72,19 +69,12 @@ ide_rename_provider_default_init (IdeRenameProviderInterface *iface)
 {
   iface->rename_async = ide_rename_provider_real_rename_async;
   iface->rename_finish = ide_rename_provider_real_rename_finish;
-
-  g_object_interface_install_property (iface,
-                                       g_param_spec_object ("buffer",
-                                                            "Buffer",
-                                                            "Buffer",
-                                                            IDE_TYPE_BUFFER,
-                                                            (G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS)));
 }
 
 /**
  * ide_rename_provider_rename_async:
  * @self: An #IdeRenameProvider
- * @location: An #IdeSourceLocation
+ * @location: An #IdeLocation
  * @new_name: The replacement name for the symbol
  * @cancellable: (nullable): a #GCancellable or %NULL
  * @callback: a callback to complete the request
@@ -99,7 +89,7 @@ ide_rename_provider_default_init (IdeRenameProviderInterface *iface)
  */
 void
 ide_rename_provider_rename_async (IdeRenameProvider   *self,
-                                  IdeSourceLocation   *location,
+                                  IdeLocation         *location,
                                   const gchar         *new_name,
                                   GCancellable        *cancellable,
                                   GAsyncReadyCallback  callback,
@@ -121,16 +111,17 @@ ide_rename_provider_rename_async (IdeRenameProvider   *self,
  * ide_rename_provider_rename_finish:
  * @self: An #IdeRenameProvider
  * @result: a #GAsyncResult
- * @edits: (out) (transfer full) (element-type Ide.ProjectEdit) (optional): A location
- *   for a #GPtrArray of #IdeProjectEdit instances.
+ * @edits: (out) (transfer full) (element-type IdeTextEdit) (optional): A location
+ *   for a #GPtrArray of #IdeTextEdit instances.
  * @error: a location for a #GError, or %NULL.
  *
  * Completes a request to ide_rename_provider_rename_async().
  *
- * You can use the resulting #GPtrArray of #IdeProjectEdit instances to edit the project
- * to complete the symbol rename.
+ * You can use the resulting #GPtrArray of #IdeTextEdit instances to edit the
+ * project to complete the symbol rename.
  *
- * Returns: %TRUE if successful and @edits is set. Otherwise %FALSE and @error is set.
+ * Returns: %TRUE if successful and @edits is set. Otherwise %FALSE and @error
+ *   is set.
  *
  * Since: 3.32
  */
@@ -160,3 +151,12 @@ ide_rename_provider_load (IdeRenameProvider *self)
   if (IDE_RENAME_PROVIDER_GET_IFACE (self)->load)
     IDE_RENAME_PROVIDER_GET_IFACE (self)->load (self);
 }
+
+void
+ide_rename_provider_unload (IdeRenameProvider *self)
+{
+  g_return_if_fail (IDE_IS_RENAME_PROVIDER (self));
+
+  if (IDE_RENAME_PROVIDER_GET_IFACE (self)->unload)
+    IDE_RENAME_PROVIDER_GET_IFACE (self)->unload (self);
+}
diff --git a/src/libide/rename/ide-rename-provider.h b/src/libide/code/ide-rename-provider.h
similarity index 84%
rename from src/libide/rename/ide-rename-provider.h
rename to src/libide/code/ide-rename-provider.h
index 684e97067..4d84a0546 100644
--- a/src/libide/rename/ide-rename-provider.h
+++ b/src/libide/code/ide-rename-provider.h
@@ -20,11 +20,13 @@
 
 #pragma once
 
-#include "ide-version-macros.h"
+#if !defined (IDE_CODE_INSIDE) && !defined (IDE_CODE_COMPILATION)
+# error "Only <libide-code.h> can be included directly."
+#endif
 
-#include "ide-object.h"
+#include <libide-core.h>
 
-#include "projects/ide-project-edit.h"
+#include "ide-code-types.h"
 
 G_BEGIN_DECLS
 
@@ -37,8 +39,10 @@ struct _IdeRenameProviderInterface
 {
   GTypeInterface parent_iface;
 
+  void     (*load)          (IdeRenameProvider    *self);
+  void     (*unload)        (IdeRenameProvider    *self);
   void     (*rename_async)  (IdeRenameProvider    *self,
-                             IdeSourceLocation    *location,
+                             IdeLocation          *location,
                              const gchar          *new_name,
                              GCancellable         *cancellable,
                              GAsyncReadyCallback   callback,
@@ -47,14 +51,15 @@ struct _IdeRenameProviderInterface
                              GAsyncResult         *result,
                              GPtrArray           **edits,
                              GError              **error);
-  void     (*load)          (IdeRenameProvider    *self);
 };
 
 IDE_AVAILABLE_IN_3_32
 void      ide_rename_provider_load          (IdeRenameProvider     *self);
 IDE_AVAILABLE_IN_3_32
+void      ide_rename_provider_unload        (IdeRenameProvider     *self);
+IDE_AVAILABLE_IN_3_32
 void      ide_rename_provider_rename_async  (IdeRenameProvider     *self,
-                                             IdeSourceLocation     *location,
+                                             IdeLocation           *location,
                                              const gchar           *new_name,
                                              GCancellable          *cancellable,
                                              GAsyncReadyCallback    callback,
diff --git a/src/libide/sourceview/ide-source-iter.c b/src/libide/code/ide-source-iter.c
similarity index 99%
rename from src/libide/sourceview/ide-source-iter.c
rename to src/libide/code/ide-source-iter.c
index f2ca9475a..fda13e314 100644
--- a/src/libide/sourceview/ide-source-iter.c
+++ b/src/libide/code/ide-source-iter.c
@@ -34,11 +34,7 @@
 
 #include "config.h"
 
-/*
- * TODO: Once this is made public api, switch to using that instead of a copy.
- */
-
-#include "sourceview/ide-source-iter.h"
+#include "ide-source-iter.h"
 
 /* Go to the end of the next or current "full word". A full word is a group of
  * non-blank chars.
diff --git a/src/libide/code/ide-source-iter.h b/src/libide/code/ide-source-iter.h
new file mode 100644
index 000000000..d2ded4386
--- /dev/null
+++ b/src/libide/code/ide-source-iter.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- */
+/* ide-source-iter.h
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2014 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * GtkSourceView 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 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView 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 Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+#include <libide-core.h>
+
+G_BEGIN_DECLS
+
+/* Semi-public functions. */
+
+IDE_AVAILABLE_IN_3_32
+gboolean _ide_source_iter_forward_visible_word_end          (GtkTextIter       *iter);
+IDE_AVAILABLE_IN_3_32
+gboolean _ide_source_iter_forward_visible_word_ends         (GtkTextIter       *iter,
+                                                             gint               count);
+IDE_AVAILABLE_IN_3_32
+gboolean _ide_source_iter_backward_visible_word_start       (GtkTextIter       *iter);
+IDE_AVAILABLE_IN_3_32
+gboolean _ide_source_iter_backward_visible_word_starts      (GtkTextIter       *iter,
+                                                             gint               count);
+IDE_AVAILABLE_IN_3_32
+void     _ide_source_iter_extend_selection_word             (const GtkTextIter *location,
+                                                             GtkTextIter       *start,
+                                                             GtkTextIter       *end);
+IDE_AVAILABLE_IN_3_32
+void     _ide_source_iter_forward_full_word_end             (GtkTextIter       *iter);
+IDE_AVAILABLE_IN_3_32
+void     _ide_source_iter_backward_full_word_start          (GtkTextIter       *iter);
+IDE_AVAILABLE_IN_3_32
+gboolean _ide_source_iter_starts_full_word                  (const GtkTextIter *iter);
+IDE_AVAILABLE_IN_3_32
+gboolean _ide_source_iter_ends_full_word                    (const GtkTextIter *iter);
+IDE_AVAILABLE_IN_3_32
+void     _ide_source_iter_forward_extra_natural_word_end    (GtkTextIter       *iter);
+IDE_AVAILABLE_IN_3_32
+void     _ide_source_iter_backward_extra_natural_word_start (GtkTextIter       *iter);
+IDE_AVAILABLE_IN_3_32
+gboolean _ide_source_iter_starts_extra_natural_word         (const GtkTextIter *iter);
+IDE_AVAILABLE_IN_3_32
+gboolean _ide_source_iter_ends_extra_natural_word           (const GtkTextIter *iter);
+IDE_AVAILABLE_IN_3_32
+gboolean _ide_source_iter_starts_word                       (const GtkTextIter *iter);
+IDE_AVAILABLE_IN_3_32
+gboolean _ide_source_iter_ends_word                         (const GtkTextIter *iter);
+IDE_AVAILABLE_IN_3_32
+gboolean _ide_source_iter_inside_word                       (const GtkTextIter *iter);
+
+G_END_DECLS
diff --git a/src/libide/sourceview/ide-source-style-scheme.c b/src/libide/code/ide-source-style-scheme.c
similarity index 98%
rename from src/libide/sourceview/ide-source-style-scheme.c
rename to src/libide/code/ide-source-style-scheme.c
index 67fe59b85..b68fd10dc 100644
--- a/src/libide/sourceview/ide-source-style-scheme.c
+++ b/src/libide/code/ide-source-style-scheme.c
@@ -24,7 +24,7 @@
 
 #include <string.h>
 
-#include "sourceview/ide-source-style-scheme.h"
+#include "ide-source-style-scheme.h"
 
 gboolean
 ide_source_style_scheme_apply_style (GtkSourceStyleScheme *style_scheme,
diff --git a/src/libide/sourceview/ide-source-style-scheme.h b/src/libide/code/ide-source-style-scheme.h
similarity index 87%
rename from src/libide/sourceview/ide-source-style-scheme.h
rename to src/libide/code/ide-source-style-scheme.h
index 099804f91..cce1de880 100644
--- a/src/libide/sourceview/ide-source-style-scheme.h
+++ b/src/libide/code/ide-source-style-scheme.h
@@ -20,9 +20,12 @@
 
 #pragma once
 
-#include <gtksourceview/gtksource.h>
+#if !defined (IDE_CODE_INSIDE) && !defined (IDE_CODE_COMPILATION)
+# error "Only <libide-code.h> can be included directly."
+#endif
 
-#include "ide-version-macros.h"
+#include <libide-core.h>
+#include <gtksourceview/gtksource.h>
 
 G_BEGIN_DECLS
 
diff --git a/src/libide/files/ide-spaces-style.h b/src/libide/code/ide-spaces-style.h
similarity index 90%
rename from src/libide/files/ide-spaces-style.h
rename to src/libide/code/ide-spaces-style.h
index b1501a283..51cb50c9e 100644
--- a/src/libide/files/ide-spaces-style.h
+++ b/src/libide/code/ide-spaces-style.h
@@ -20,6 +20,10 @@
 
 #pragma once
 
+#if !defined (IDE_CODE_INSIDE) && !defined (IDE_CODE_COMPILATION)
+# error "Only <libide-code.h> can be included directly."
+#endif
+
 #include <glib.h>
 
 G_BEGIN_DECLS
diff --git a/src/libide/symbols/ide-symbol-node.c b/src/libide/code/ide-symbol-node.c
similarity index 92%
rename from src/libide/symbols/ide-symbol-node.c
rename to src/libide/code/ide-symbol-node.c
index 6ef3b88bf..05ad3f150 100644
--- a/src/libide/symbols/ide-symbol-node.c
+++ b/src/libide/code/ide-symbol-node.c
@@ -23,11 +23,11 @@
 #include "config.h"
 
 #include <glib/gi18n.h>
+#include <libide-threading.h>
 
-#include "ide-enums.h"
-#include "symbols/ide-symbol.h"
-#include "symbols/ide-symbol-node.h"
-#include "threading/ide-task.h"
+#include "ide-code-enums.h"
+#include "ide-symbol.h"
+#include "ide-symbol-node.h"
 
 typedef struct
 {
@@ -37,7 +37,7 @@ typedef struct
   guint           use_markup : 1;
 } IdeSymbolNodePrivate;
 
-G_DEFINE_TYPE_WITH_PRIVATE (IdeSymbolNode, ide_symbol_node, IDE_TYPE_OBJECT)
+G_DEFINE_TYPE_WITH_PRIVATE (IdeSymbolNode, ide_symbol_node, G_TYPE_OBJECT)
 
 enum {
   PROP_0,
@@ -45,10 +45,10 @@ enum {
   PROP_KIND,
   PROP_NAME,
   PROP_USE_MARKUP,
-  LAST_PROP
+  N_PROPS
 };
 
-static GParamSpec *properties [LAST_PROP];
+static GParamSpec *properties [N_PROPS];
 
 static void
 ide_symbol_node_real_get_location_async (IdeSymbolNode       *self,
@@ -66,7 +66,7 @@ ide_symbol_node_real_get_location_async (IdeSymbolNode       *self,
                              "Unsupported operation on symbol node");
 }
 
-static IdeSourceLocation *
+static IdeLocation *
 ide_symbol_node_real_get_location_finish (IdeSymbolNode  *self,
                                           GAsyncResult   *result,
                                           GError        **error)
@@ -173,7 +173,7 @@ ide_symbol_node_class_init (IdeSymbolNodeClass *klass)
                        "Kind",
                        "Kind",
                        IDE_TYPE_SYMBOL_KIND,
-                       IDE_SYMBOL_NONE,
+                       IDE_SYMBOL_KIND_NONE,
                        (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
   properties [PROP_FLAGS] =
@@ -191,7 +191,7 @@ ide_symbol_node_class_init (IdeSymbolNodeClass *klass)
                           FALSE,
                           (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
 
-  g_object_class_install_properties (object_class, LAST_PROP, properties);
+  g_object_class_install_properties (object_class, N_PROPS, properties);
 }
 
 static void
@@ -224,7 +224,7 @@ ide_symbol_node_get_kind (IdeSymbolNode *self)
 {
   IdeSymbolNodePrivate *priv = ide_symbol_node_get_instance_private (self);
 
-  g_return_val_if_fail (IDE_IS_SYMBOL_NODE (self), IDE_SYMBOL_NONE);
+  g_return_val_if_fail (IDE_IS_SYMBOL_NODE (self), IDE_SYMBOL_KIND_NONE);
 
   return priv->kind;
 }
@@ -256,11 +256,11 @@ ide_symbol_node_get_location_async (IdeSymbolNode       *self,
  *
  * Completes the request to gets the location for the symbol node.
  *
- * Returns: (transfer full) (nullable): An #IdeSourceLocation or %NULL.
+ * Returns: (transfer full) (nullable): An #IdeLocation or %NULL.
  *
  * Since: 3.32
  */
-IdeSourceLocation *
+IdeLocation *
 ide_symbol_node_get_location_finish (IdeSymbolNode  *self,
                                      GAsyncResult   *result,
                                      GError        **error)
diff --git a/src/libide/code/ide-symbol-node.h b/src/libide/code/ide-symbol-node.h
new file mode 100644
index 000000000..e5004c2e1
--- /dev/null
+++ b/src/libide/code/ide-symbol-node.h
@@ -0,0 +1,73 @@
+/* ide-symbol-node.h
+ *
+ * Copyright 2015-2019 Christian Hergert <christian hergert me>
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#if !defined (IDE_CODE_INSIDE) && !defined (IDE_CODE_COMPILATION)
+# error "Only <libide-code.h> can be included directly."
+#endif
+
+#include <libide-core.h>
+
+#include "ide-code-types.h"
+#include "ide-symbol.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_SYMBOL_NODE (ide_symbol_node_get_type())
+
+IDE_AVAILABLE_IN_3_32
+G_DECLARE_DERIVABLE_TYPE (IdeSymbolNode, ide_symbol_node, IDE, SYMBOL_NODE, GObject)
+
+struct _IdeSymbolNodeClass
+{
+  GObjectClass parent;
+
+  void         (*get_location_async)  (IdeSymbolNode        *self,
+                                       GCancellable         *cancellable,
+                                       GAsyncReadyCallback   callback,
+                                       gpointer              user_data);
+  IdeLocation *(*get_location_finish) (IdeSymbolNode        *self,
+                                       GAsyncResult         *result,
+                                       GError             **error);
+
+  /*< private >*/
+  gpointer _reserved[8];
+};
+
+IDE_AVAILABLE_IN_3_32
+IdeSymbolKind   ide_symbol_node_get_kind            (IdeSymbolNode        *self);
+IDE_AVAILABLE_IN_3_32
+IdeSymbolFlags  ide_symbol_node_get_flags           (IdeSymbolNode        *self);
+IDE_AVAILABLE_IN_3_32
+const gchar    *ide_symbol_node_get_name            (IdeSymbolNode        *self);
+IDE_AVAILABLE_IN_3_32
+gboolean        ide_symbol_node_get_use_markup      (IdeSymbolNode        *self);
+IDE_AVAILABLE_IN_3_32
+void            ide_symbol_node_get_location_async  (IdeSymbolNode        *self,
+                                                     GCancellable         *cancellable,
+                                                     GAsyncReadyCallback   callback,
+                                                     gpointer              user_data);
+IDE_AVAILABLE_IN_3_32
+IdeLocation    *ide_symbol_node_get_location_finish (IdeSymbolNode        *self,
+                                                     GAsyncResult         *result,
+                                                     GError              **error);
+
+G_END_DECLS
diff --git a/src/libide/symbols/ide-symbol-resolver.c b/src/libide/code/ide-symbol-resolver.c
similarity index 86%
rename from src/libide/symbols/ide-symbol-resolver.c
rename to src/libide/code/ide-symbol-resolver.c
index 7afcf45cb..d5c830d7a 100644
--- a/src/libide/symbols/ide-symbol-resolver.c
+++ b/src/libide/code/ide-symbol-resolver.c
@@ -1,6 +1,6 @@
 /* ide-symbol-resolver.c
  *
- * Copyright 2015-2019 Christian Hergert <christian hergert me>
+ * Copyright 2015-2019 Christian Hergert <chergert redhat com>
  *
  * 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
@@ -22,19 +22,17 @@
 
 #include "config.h"
 
-#include "ide-context.h"
+#include <libide-core.h>
+#include <libide-threading.h>
 
-#include "buffers/ide-buffer.h"
-#include "files/ide-file.h"
-#include "symbols/ide-symbol-resolver.h"
-#include "threading/ide-task.h"
+#include "ide-symbol-resolver.h"
 
 G_DEFINE_INTERFACE (IdeSymbolResolver, ide_symbol_resolver, IDE_TYPE_OBJECT)
 
 static void
 ide_symbol_resolver_real_get_symbol_tree_async (IdeSymbolResolver   *self,
                                                 GFile               *file,
-                                                IdeBuffer           *buffer,
+                                                GBytes              *contents,
                                                 GCancellable        *cancellable,
                                                 GAsyncReadyCallback  callback,
                                                 gpointer             user_data)
@@ -43,7 +41,6 @@ ide_symbol_resolver_real_get_symbol_tree_async (IdeSymbolResolver   *self,
 
   g_assert (IDE_IS_SYMBOL_RESOLVER (self));
   g_assert (G_IS_FILE (file));
-  g_assert (buffer == NULL || IDE_IS_BUFFER (buffer));
   g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
 
   task = ide_task_new (self, cancellable, callback, user_data);
@@ -67,7 +64,8 @@ ide_symbol_resolver_real_get_symbol_tree_finish (IdeSymbolResolver  *self,
 
 static void
 ide_symbol_resolver_real_find_references_async (IdeSymbolResolver   *self,
-                                                IdeSourceLocation   *location,
+                                                IdeLocation         *location,
+                                                const gchar         *language_id,
                                                 GCancellable        *cancellable,
                                                 GAsyncReadyCallback  callback,
                                                 gpointer             user_data)
@@ -98,7 +96,7 @@ ide_symbol_resolver_real_find_references_finish (IdeSymbolResolver  *self,
 
 static void
 ide_symbol_resolver_real_find_nearest_scope_async (IdeSymbolResolver   *self,
-                                                   IdeSourceLocation   *location,
+                                                   IdeLocation         *location,
                                                    GCancellable        *cancellable,
                                                    GAsyncReadyCallback  callback,
                                                    gpointer             user_data)
@@ -141,7 +139,7 @@ ide_symbol_resolver_default_init (IdeSymbolResolverInterface *iface)
 /**
  * ide_symbol_resolver_lookup_symbol_async:
  * @self: An #IdeSymbolResolver.
- * @location: An #IdeSourceLocation.
+ * @location: An #IdeLocation.
  * @cancellable: (allow-none): a #GCancellable or %NULL.
  * @callback: A callback to execute upon completion.
  * @user_data: user data for @callback.
@@ -153,11 +151,11 @@ ide_symbol_resolver_default_init (IdeSymbolResolverInterface *iface)
  * Since: 3.32
  */
 void
-ide_symbol_resolver_lookup_symbol_async  (IdeSymbolResolver   *self,
-                                          IdeSourceLocation   *location,
-                                          GCancellable        *cancellable,
-                                          GAsyncReadyCallback  callback,
-                                          gpointer             user_data)
+ide_symbol_resolver_lookup_symbol_async (IdeSymbolResolver   *self,
+                                         IdeLocation         *location,
+                                         GCancellable        *cancellable,
+                                         GAsyncReadyCallback  callback,
+                                         gpointer             user_data)
 {
   g_return_if_fail (IDE_IS_SYMBOL_RESOLVER (self));
   g_return_if_fail (location != NULL);
@@ -194,7 +192,7 @@ ide_symbol_resolver_lookup_symbol_finish (IdeSymbolResolver  *self,
  * ide_symbol_resolver_get_symbol_tree_async:
  * @self: An #IdeSymbolResolver
  * @file: a #GFile
- * @buffer: an #IdeBuffer or %NULL
+ * @contents: (nullable): a #GBytes or %NULL
  * @cancellable: (allow-none): a #GCancellable or %NULL.
  * @callback: (allow-none): a callback to execute upon completion
  * @user_data: user data for @callback
@@ -206,7 +204,7 @@ ide_symbol_resolver_lookup_symbol_finish (IdeSymbolResolver  *self,
 void
 ide_symbol_resolver_get_symbol_tree_async (IdeSymbolResolver   *self,
                                            GFile               *file,
-                                           IdeBuffer           *buffer,
+                                           GBytes              *contents,
                                            GCancellable        *cancellable,
                                            GAsyncReadyCallback  callback,
                                            gpointer             user_data)
@@ -214,15 +212,17 @@ ide_symbol_resolver_get_symbol_tree_async (IdeSymbolResolver   *self,
   g_return_if_fail (IDE_IS_SYMBOL_RESOLVER (self));
   g_return_if_fail (G_IS_FILE (file));
 
-  IDE_SYMBOL_RESOLVER_GET_IFACE (self)->get_symbol_tree_async (self, file, buffer, cancellable, callback, 
user_data);
+  IDE_SYMBOL_RESOLVER_GET_IFACE (self)->get_symbol_tree_async (self, file, contents, cancellable, callback, 
user_data);
 }
 
 /**
  * ide_symbol_resolver_get_symbol_tree_finish:
  *
- * Completes an asynchronous request to get the symbol tree for the requested file.
+ * Completes an asynchronous request to get the symbol tree for the
+ * requested file.
  *
- * Returns: (nullable) (transfer full): An #IdeSymbolTree; otherwise %NULL and @error is set.
+ * Returns: (nullable) (transfer full): An #IdeSymbolTree; otherwise
+ *   %NULL and @error is set.
  *
  * Since: 3.32
  */
@@ -255,9 +255,20 @@ ide_symbol_resolver_unload (IdeSymbolResolver *self)
     IDE_SYMBOL_RESOLVER_GET_IFACE (self)->unload (self);
 }
 
+/**
+ * ide_symbol_resolver_find_references_async:
+ * @self: a #IdeSymbolResolver
+ * @location: an #IdeLocation
+ * @language_id: (nullable): a language identifier or %NULL
+ * @cancellable: (nullable): a #GCancellable or %NULL
+ * @callback: a callback to execute
+ * @user_data: user data for @callback
+ *
+ */
 void
 ide_symbol_resolver_find_references_async (IdeSymbolResolver   *self,
-                                           IdeSourceLocation   *location,
+                                           IdeLocation         *location,
+                                           const gchar         *language_id,
                                            GCancellable        *cancellable,
                                            GAsyncReadyCallback  callback,
                                            gpointer             user_data)
@@ -266,7 +277,7 @@ ide_symbol_resolver_find_references_async (IdeSymbolResolver   *self,
   g_return_if_fail (location != NULL);
   g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
 
-  IDE_SYMBOL_RESOLVER_GET_IFACE (self)->find_references_async (self, location, cancellable, callback, 
user_data);
+  IDE_SYMBOL_RESOLVER_GET_IFACE (self)->find_references_async (self, location, language_id, cancellable, 
callback, user_data);
 }
 
 /**
@@ -277,8 +288,8 @@ ide_symbol_resolver_find_references_async (IdeSymbolResolver   *self,
  *
  * Completes an asynchronous request to ide_symbol_resolver_find_references_async().
  *
- * Returns: (transfer full) (element-type Ide.SourceRange): a #GPtrArray
- *   of #IdeSourceRange if successful; otherwise %NULL and @error is set.
+ * Returns: (transfer full) (element-type IdeRange): a #GPtrArray
+ *   of #IdeRange if successful; otherwise %NULL and @error is set.
  *
  * Since: 3.32
  */
@@ -296,7 +307,7 @@ ide_symbol_resolver_find_references_finish (IdeSymbolResolver  *self,
 /**
  * ide_symbol_resolver_find_nearest_scope_async:
  * @self: a #IdeSymbolResolver
- * @location: an #IdeSourceLocation
+ * @location: an #IdeLocation
  * @cancellable: (nullable): a #GCancellable or %NULL
  * @callback: (scope async) (closure user_data): an async callback
  * @user_data: user data for @callback
@@ -310,11 +321,11 @@ ide_symbol_resolver_find_references_finish (IdeSymbolResolver  *self,
  * Since: 3.32
  */
 void
-ide_symbol_resolver_find_nearest_scope_async (IdeSymbolResolver    *self,
-                                              IdeSourceLocation    *location,
-                                              GCancellable         *cancellable,
-                                              GAsyncReadyCallback   callback,
-                                              gpointer              user_data)
+ide_symbol_resolver_find_nearest_scope_async (IdeSymbolResolver   *self,
+                                              IdeLocation         *location,
+                                              GCancellable        *cancellable,
+                                              GAsyncReadyCallback  callback,
+                                              gpointer             user_data)
 {
   g_return_if_fail (IDE_IS_SYMBOL_RESOLVER (self));
   g_return_if_fail (location != NULL);
diff --git a/src/libide/symbols/ide-symbol-resolver.h b/src/libide/code/ide-symbol-resolver.h
similarity index 89%
rename from src/libide/symbols/ide-symbol-resolver.h
rename to src/libide/code/ide-symbol-resolver.h
index a2ec3fcba..9b27a2062 100644
--- a/src/libide/symbols/ide-symbol-resolver.h
+++ b/src/libide/code/ide-symbol-resolver.h
@@ -20,10 +20,13 @@
 
 #pragma once
 
-#include "ide-version-macros.h"
+#if !defined (IDE_CODE_INSIDE) && !defined (IDE_CODE_COMPILATION)
+# error "Only <libide-code.h> can be included directly."
+#endif
 
-#include "ide-object.h"
-#include "symbols/ide-symbol-tree.h"
+#include <libide-core.h>
+
+#include "ide-code-types.h"
 
 G_BEGIN_DECLS
 
@@ -36,8 +39,10 @@ struct _IdeSymbolResolverInterface
 {
   GTypeInterface parent_interface;
 
+  void           (*load)                     (IdeSymbolResolver    *self);
+  void           (*unload)                   (IdeSymbolResolver    *self);
   void           (*lookup_symbol_async)      (IdeSymbolResolver    *self,
-                                              IdeSourceLocation    *location,
+                                              IdeLocation          *location,
                                               GCancellable         *cancellable,
                                               GAsyncReadyCallback   callback,
                                               gpointer              user_data);
@@ -46,16 +51,16 @@ struct _IdeSymbolResolverInterface
                                               GError              **error);
   void           (*get_symbol_tree_async)    (IdeSymbolResolver    *self,
                                               GFile                *file,
-                                              IdeBuffer            *buffer,
+                                              GBytes               *contents,
                                               GCancellable         *cancellable,
                                               GAsyncReadyCallback   callback,
                                               gpointer              user_data);
   IdeSymbolTree *(*get_symbol_tree_finish)   (IdeSymbolResolver    *self,
                                               GAsyncResult         *result,
                                               GError              **error);
-  void           (*load)                     (IdeSymbolResolver    *self);
   void           (*find_references_async)    (IdeSymbolResolver    *self,
-                                              IdeSourceLocation    *location,
+                                              IdeLocation          *location,
+                                              const gchar          *language_id,
                                               GCancellable         *cancellable,
                                               GAsyncReadyCallback   callback,
                                               gpointer              user_data);
@@ -63,14 +68,13 @@ struct _IdeSymbolResolverInterface
                                               GAsyncResult         *result,
                                               GError              **error);
   void           (*find_nearest_scope_async) (IdeSymbolResolver    *self,
-                                              IdeSourceLocation    *location,
+                                              IdeLocation          *location,
                                               GCancellable         *cancellable,
                                               GAsyncReadyCallback   callback,
                                               gpointer              user_data);
   IdeSymbol     *(*find_nearest_scope_finish) (IdeSymbolResolver    *self,
                                               GAsyncResult         *result,
                                               GError              **error);
-  void           (*unload)                   (IdeSymbolResolver    *self);
 };
 
 IDE_AVAILABLE_IN_3_32
@@ -79,7 +83,7 @@ IDE_AVAILABLE_IN_3_32
 void           ide_symbol_resolver_unload                    (IdeSymbolResolver    *self);
 IDE_AVAILABLE_IN_3_32
 void           ide_symbol_resolver_lookup_symbol_async       (IdeSymbolResolver    *self,
-                                                              IdeSourceLocation    *location,
+                                                              IdeLocation          *location,
                                                               GCancellable         *cancellable,
                                                               GAsyncReadyCallback   callback,
                                                               gpointer              user_data);
@@ -90,7 +94,7 @@ IdeSymbol     *ide_symbol_resolver_lookup_symbol_finish      (IdeSymbolResolver
 IDE_AVAILABLE_IN_3_32
 void           ide_symbol_resolver_get_symbol_tree_async     (IdeSymbolResolver    *self,
                                                               GFile                *file,
-                                                              IdeBuffer            *buffer,
+                                                              GBytes               *contents,
                                                               GCancellable         *cancellable,
                                                               GAsyncReadyCallback   callback,
                                                               gpointer              user_data);
@@ -100,7 +104,8 @@ IdeSymbolTree *ide_symbol_resolver_get_symbol_tree_finish    (IdeSymbolResolver
                                                               GError              **error);
 IDE_AVAILABLE_IN_3_32
 void           ide_symbol_resolver_find_references_async     (IdeSymbolResolver    *self,
-                                                              IdeSourceLocation    *location,
+                                                              IdeLocation          *location,
+                                                              const gchar          *language_id,
                                                               GCancellable         *cancellable,
                                                               GAsyncReadyCallback   callback,
                                                               gpointer              user_data);
@@ -110,7 +115,7 @@ GPtrArray     *ide_symbol_resolver_find_references_finish    (IdeSymbolResolver
                                                               GError              **error);
 IDE_AVAILABLE_IN_3_32
 void           ide_symbol_resolver_find_nearest_scope_async  (IdeSymbolResolver    *self,
-                                                              IdeSourceLocation    *location,
+                                                              IdeLocation          *location,
                                                               GCancellable         *cancellable,
                                                               GAsyncReadyCallback   callback,
                                                               gpointer              user_data);
diff --git a/src/libide/symbols/ide-symbol-tree.c b/src/libide/code/ide-symbol-tree.c
similarity index 97%
rename from src/libide/symbols/ide-symbol-tree.c
rename to src/libide/code/ide-symbol-tree.c
index 706ccf0bd..c96dc6987 100644
--- a/src/libide/symbols/ide-symbol-tree.c
+++ b/src/libide/code/ide-symbol-tree.c
@@ -22,7 +22,8 @@
 
 #include "config.h"
 
-#include "symbols/ide-symbol-tree.h"
+#include "ide-symbol-node.h"
+#include "ide-symbol-tree.h"
 
 G_DEFINE_INTERFACE (IdeSymbolTree, ide_symbol_tree, G_TYPE_OBJECT)
 
diff --git a/src/libide/symbols/ide-symbol-tree.h b/src/libide/code/ide-symbol-tree.h
similarity index 90%
rename from src/libide/symbols/ide-symbol-tree.h
rename to src/libide/code/ide-symbol-tree.h
index 914c738aa..9eea8ab9a 100644
--- a/src/libide/symbols/ide-symbol-tree.h
+++ b/src/libide/code/ide-symbol-tree.h
@@ -20,11 +20,13 @@
 
 #pragma once
 
-#include <glib-object.h>
+#if !defined (IDE_CODE_INSIDE) && !defined (IDE_CODE_COMPILATION)
+# error "Only <libide-code.h> can be included directly."
+#endif
 
-#include "ide-version-macros.h"
+#include <libide-core.h>
 
-#include "symbols/ide-symbol-node.h"
+#include "ide-code-types.h"
 
 G_BEGIN_DECLS
 
diff --git a/src/libide/code/ide-symbol.c b/src/libide/code/ide-symbol.c
new file mode 100644
index 000000000..b1dc0b407
--- /dev/null
+++ b/src/libide/code/ide-symbol.c
@@ -0,0 +1,533 @@
+/* ide-symbol.c
+ *
+ * Copyright 2015-2019 Christian Hergert <chergert redhat com>
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-symbol"
+
+#include "config.h"
+
+#include "ide-code-enums.h"
+#include "ide-location.h"
+#include "ide-symbol.h"
+
+typedef struct
+{
+  IdeSymbolKind   kind;
+  IdeSymbolFlags  flags;
+  gchar          *name;
+  IdeLocation    *location;
+  IdeLocation    *header_location;
+} IdeSymbolPrivate;
+
+enum {
+  PROP_0,
+  PROP_KIND,
+  PROP_FLAGS,
+  PROP_NAME,
+  PROP_LOCATION,
+  PROP_HEADER_LOCATION,
+  N_PROPS
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (IdeSymbol, ide_symbol, G_TYPE_OBJECT)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+ide_symbol_finalize (GObject *object)
+{
+  IdeSymbol *self = (IdeSymbol *)object;
+  IdeSymbolPrivate *priv = ide_symbol_get_instance_private (self);
+
+  g_clear_pointer (&priv->name, g_free);
+  g_clear_object (&priv->location);
+  g_clear_object (&priv->header_location);
+
+  G_OBJECT_CLASS (ide_symbol_parent_class)->finalize (object);
+}
+
+static void
+ide_symbol_get_property (GObject    *object,
+                         guint       prop_id,
+                         GValue     *value,
+                         GParamSpec *pspec)
+{
+  IdeSymbol *self = IDE_SYMBOL (object);
+
+  switch (prop_id)
+    {
+    case PROP_KIND:
+      g_value_set_enum (value, ide_symbol_get_kind (self));
+      break;
+
+    case PROP_FLAGS:
+      g_value_set_flags (value, ide_symbol_get_flags (self));
+      break;
+
+    case PROP_NAME:
+      g_value_set_string (value, ide_symbol_get_name (self));
+      break;
+
+    case PROP_LOCATION:
+      g_value_set_object (value, ide_symbol_get_location (self));
+      break;
+
+    case PROP_HEADER_LOCATION:
+      g_value_set_object (value, ide_symbol_get_header_location (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_symbol_set_property (GObject      *object,
+                         guint         prop_id,
+                         const GValue *value,
+                         GParamSpec   *pspec)
+{
+  IdeSymbol *self = IDE_SYMBOL (object);
+  IdeSymbolPrivate *priv = ide_symbol_get_instance_private (self);
+
+  switch (prop_id)
+    {
+    case PROP_KIND:
+      priv->kind = g_value_get_enum (value);
+      break;
+
+    case PROP_FLAGS:
+      priv->flags = g_value_get_flags (value);
+      break;
+
+    case PROP_NAME:
+      priv->name = g_value_dup_string (value);
+      break;
+
+    case PROP_LOCATION:
+      priv->location = g_value_dup_object (value);
+      break;
+
+    case PROP_HEADER_LOCATION:
+      priv->header_location = g_value_dup_object (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_symbol_class_init (IdeSymbolClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_symbol_finalize;
+  object_class->get_property = ide_symbol_get_property;
+  object_class->set_property = ide_symbol_set_property;
+
+  properties [PROP_KIND] =
+    g_param_spec_enum ("kind",
+                       "Kind",
+                       "The kind of symbol",
+                       IDE_TYPE_SYMBOL_KIND,
+                       IDE_SYMBOL_KIND_NONE,
+                       (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_FLAGS] =
+    g_param_spec_flags ("flags",
+                        "Flags",
+                        "The symbol flags",
+                        IDE_TYPE_SYMBOL_FLAGS,
+                        IDE_SYMBOL_FLAGS_NONE,
+                        (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_NAME] =
+    g_param_spec_string ("name",
+                         "Name",
+                         "The name of the symbol",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_LOCATION] =
+    g_param_spec_object ("location",
+                         "Location",
+                         "The location for the symbol",
+                         IDE_TYPE_LOCATION,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_HEADER_LOCATION] =
+    g_param_spec_object ("header-location",
+                         "Header Location",
+                         "The header location for the symbol",
+                         IDE_TYPE_LOCATION,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_symbol_init (IdeSymbol *self)
+{
+}
+
+IdeSymbolKind
+ide_symbol_get_kind (IdeSymbol *self)
+{
+  IdeSymbolPrivate *priv = ide_symbol_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_SYMBOL (self), 0);
+
+  return priv->kind;
+}
+
+IdeSymbolFlags
+ide_symbol_get_flags (IdeSymbol *self)
+{
+  IdeSymbolPrivate *priv = ide_symbol_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_SYMBOL (self), 0);
+
+  return priv->flags;
+}
+
+const gchar *
+ide_symbol_get_name (IdeSymbol *self)
+{
+  IdeSymbolPrivate *priv = ide_symbol_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_SYMBOL (self), NULL);
+
+  return priv->name;
+}
+
+/**
+ * ide_symbol_get_location:
+ * @self: a #IdeSymbol
+ *
+ * Gets the location, if any.
+ *
+ * Returns: (transfer none) (nullable): an #IdeLocation or %NULL
+ *
+ * Since: 3.32
+ */
+IdeLocation *
+ide_symbol_get_location (IdeSymbol *self)
+{
+  IdeSymbolPrivate *priv = ide_symbol_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_SYMBOL (self), NULL);
+
+  return priv->location;
+}
+
+/**
+ * ide_symbol_get_header_location:
+ * @self: a #IdeSymbol
+ *
+ * Gets the header location, if any.
+ *
+ * Returns: (transfer none) (nullable): an #IdeLocation or %NULL
+ *
+ * Since: 3.32
+ */
+IdeLocation *
+ide_symbol_get_header_location (IdeSymbol *self)
+{
+  IdeSymbolPrivate *priv = ide_symbol_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_SYMBOL (self), NULL);
+
+  return priv->header_location;
+}
+
+const gchar *
+ide_symbol_kind_get_icon_name (IdeSymbolKind kind)
+{
+  const gchar *icon_name = NULL;
+
+  switch (kind)
+    {
+    case IDE_SYMBOL_KIND_ALIAS:
+      icon_name = "lang-typedef-symbolic";
+      break;
+
+    case IDE_SYMBOL_KIND_CLASS:
+      icon_name = "lang-class-symbolic";
+      break;
+
+    case IDE_SYMBOL_KIND_ENUM:
+      icon_name = "lang-enum-symbolic";
+      break;
+
+    case IDE_SYMBOL_KIND_ENUM_VALUE:
+      icon_name = "lang-enum-value-symbolic";
+      break;
+
+    case IDE_SYMBOL_KIND_FUNCTION:
+      icon_name = "lang-function-symbolic";
+      break;
+
+    case IDE_SYMBOL_KIND_PACKAGE:
+      icon_name = "lang-include-symbolic";
+      break;
+
+    case IDE_SYMBOL_KIND_MACRO:
+      icon_name = "lang-define-symbolic";
+      break;
+
+    case IDE_SYMBOL_KIND_METHOD:
+      icon_name = "lang-method-symbolic";
+      break;
+
+    case IDE_SYMBOL_KIND_NAMESPACE:
+      icon_name = "lang-namespace-symbolic";
+      break;
+
+    case IDE_SYMBOL_KIND_STRUCT:
+      icon_name = "lang-struct-symbolic";
+      break;
+
+    case IDE_SYMBOL_KIND_FIELD:
+      icon_name = "lang-struct-field-symbolic";
+      break;
+
+    case IDE_SYMBOL_KIND_SCALAR:
+    case IDE_SYMBOL_KIND_VARIABLE:
+      icon_name = "lang-variable-symbolic";
+      break;
+
+    case IDE_SYMBOL_KIND_UNION:
+      icon_name = "lang-union-symbolic";
+      break;
+
+    case IDE_SYMBOL_KIND_ARRAY:
+    case IDE_SYMBOL_KIND_BOOLEAN:
+    case IDE_SYMBOL_KIND_CONSTANT:
+    case IDE_SYMBOL_KIND_CONSTRUCTOR:
+    case IDE_SYMBOL_KIND_FILE:
+    case IDE_SYMBOL_KIND_HEADER:
+    case IDE_SYMBOL_KIND_INTERFACE:
+    case IDE_SYMBOL_KIND_MODULE:
+    case IDE_SYMBOL_KIND_NUMBER:
+    case IDE_SYMBOL_KIND_NONE:
+    case IDE_SYMBOL_KIND_PROPERTY:
+    case IDE_SYMBOL_KIND_STRING:
+    case IDE_SYMBOL_KIND_TEMPLATE:
+    case IDE_SYMBOL_KIND_KEYWORD:
+      icon_name = NULL;
+      break;
+
+    case IDE_SYMBOL_KIND_UI_ATTRIBUTES:
+      icon_name = "ui-attributes-symbolic";
+      break;
+
+    case IDE_SYMBOL_KIND_UI_CHILD:
+      icon_name = "ui-child-symbolic";
+      break;
+
+    case IDE_SYMBOL_KIND_UI_ITEM:
+      icon_name = "ui-item-symbolic";
+      break;
+
+    case IDE_SYMBOL_KIND_UI_MENU:
+      icon_name = "ui-menu-symbolic";
+      break;
+
+    case IDE_SYMBOL_KIND_UI_OBJECT:
+      icon_name = "ui-object-symbolic";
+      break;
+
+    case IDE_SYMBOL_KIND_UI_PACKING:
+      icon_name = "ui-packing-symbolic";
+      break;
+
+    case IDE_SYMBOL_KIND_UI_PROPERTY:
+      icon_name = "ui-property-symbolic";
+      break;
+
+    case IDE_SYMBOL_KIND_UI_SECTION:
+      icon_name = "ui-section-symbolic";
+      break;
+
+    case IDE_SYMBOL_KIND_UI_SIGNAL:
+      icon_name = "ui-signal-symbolic";
+      break;
+
+    case IDE_SYMBOL_KIND_UI_STYLE:
+      icon_name = "ui-style-symbolic";
+      break;
+
+    case IDE_SYMBOL_KIND_UI_SUBMENU:
+      icon_name = "ui-submenu-symbolic";
+      break;
+
+    case IDE_SYMBOL_KIND_UI_TEMPLATE:
+      icon_name = "ui-template-symbolic";
+      break;
+
+    case IDE_SYMBOL_KIND_XML_ATTRIBUTE:
+      icon_name = "xml-attribute-symbolic";
+      break;
+
+    case IDE_SYMBOL_KIND_XML_CDATA:
+      icon_name = "xml-cdata-symbolic";
+      break;
+
+    case IDE_SYMBOL_KIND_XML_COMMENT:
+      icon_name = "xml-comment-symbolic";
+      break;
+
+    case IDE_SYMBOL_KIND_XML_DECLARATION:
+      icon_name = "xml-declaration-symbolic";
+      break;
+
+    case IDE_SYMBOL_KIND_XML_ELEMENT:
+      icon_name = "xml-element-symbolic";
+      break;
+
+    case IDE_SYMBOL_KIND_UI_MENU_ATTRIBUTE:
+    case IDE_SYMBOL_KIND_UI_STYLE_CLASS:
+      icon_name = NULL;
+      break;
+
+    default:
+      icon_name = NULL;
+      break;
+    }
+
+  return icon_name;
+}
+
+/**
+ * ide_symbol_to_variant:
+ * @self: a #IdeSymbol
+ *
+ * This converts the symbol to a #GVariant that is suitable for passing
+ * across an IPC boundary.
+ *
+ * This function will never return a floating reference.
+ *
+ * Returns: (transfer full): a #GVariant
+ *
+ * Since: 3.32
+ */
+GVariant *
+ide_symbol_to_variant (IdeSymbol *self)
+{
+  IdeSymbolPrivate *priv = ide_symbol_get_instance_private (self);
+  GVariantBuilder builder;
+
+  g_return_val_if_fail (self != NULL, NULL);
+
+  g_variant_builder_init (&builder, G_VARIANT_TYPE_VARDICT);
+
+  g_variant_builder_add_parsed (&builder, "{%s,<%i>}", "kind", priv->kind);
+  g_variant_builder_add_parsed (&builder, "{%s,<%i>}", "flags", priv->flags);
+  g_variant_builder_add_parsed (&builder, "{%s,<%s>}", "name", priv->name);
+
+  if (priv->location)
+    {
+      g_autoptr(GVariant) v = ide_location_to_variant (priv->location);
+      g_variant_builder_add_parsed (&builder, "{%s,%v}", "location", v);
+    }
+
+  if (priv->header_location)
+    {
+      g_autoptr(GVariant) v = ide_location_to_variant (priv->header_location);
+      g_variant_builder_add_parsed (&builder, "{%s,%v}", "header-location", v);
+    }
+
+  return g_variant_take_ref (g_variant_builder_end (&builder));
+}
+
+IdeSymbol *
+ide_symbol_new_from_variant (GVariant *variant)
+{
+  g_autoptr(GVariant) unboxed = NULL;
+  g_autoptr(GVariant) vdecl = NULL;
+  g_autoptr(GVariant) vdef = NULL;
+  g_autoptr(GVariant) vcanon = NULL;
+  g_autoptr(IdeLocation) decl = NULL;
+  g_autoptr(IdeLocation) def = NULL;
+  g_autoptr(IdeLocation) canon = NULL;
+  const gchar *name;
+  IdeSymbolKind kind;
+  IdeSymbolFlags flags;
+  IdeSymbol *self;
+  GVariantDict dict;
+
+  if (variant == NULL)
+    return NULL;
+
+  if (g_variant_is_of_type (variant, G_VARIANT_TYPE_VARIANT))
+    variant = unboxed = g_variant_get_variant (variant);
+
+  if (!g_variant_is_of_type (variant, G_VARIANT_TYPE_VARDICT))
+    return NULL;
+
+  g_variant_dict_init (&dict, variant);
+
+  if (!g_variant_dict_lookup (&dict, "kind", "i", &kind))
+    kind = 0;
+
+  if (!g_variant_dict_lookup (&dict, "flags", "i", &flags))
+    flags = 0;
+
+  if (!g_variant_dict_lookup (&dict, "name", "&s", &name))
+    name = NULL;
+
+  vdef = g_variant_dict_lookup_value (&dict, "location", NULL);
+  vdecl = g_variant_dict_lookup_value (&dict, "header-location", NULL);
+
+  decl = ide_location_new_from_variant (vdecl);
+  def = ide_location_new_from_variant (vdef);
+
+  self = ide_symbol_new (name, kind, flags, decl, def);
+
+  g_variant_dict_clear (&dict);
+
+  return self;
+}
+
+/**
+ * ide_symbol_new:
+ *
+ * Returns: (transfer full): an #IdeSymbol
+ *
+ * Since: 3.32
+ */
+IdeSymbol *
+ide_symbol_new (const gchar    *name,
+                IdeSymbolKind   kind,
+                IdeSymbolFlags  flags,
+                IdeLocation    *location,
+                IdeLocation    *header_location)
+{
+  g_return_val_if_fail (!location || IDE_IS_LOCATION (location), NULL);
+  g_return_val_if_fail (!header_location || IDE_IS_LOCATION (header_location), NULL);
+
+  return g_object_new (IDE_TYPE_SYMBOL,
+                       "name", name,
+                       "kind", kind,
+                       "flags", flags,
+                       "location", location,
+                       "header-location", header_location,
+                       NULL);
+}
diff --git a/src/libide/code/ide-symbol.h b/src/libide/code/ide-symbol.h
new file mode 100644
index 000000000..e8f02360d
--- /dev/null
+++ b/src/libide/code/ide-symbol.h
@@ -0,0 +1,129 @@
+/* ide-symbol.h
+ *
+ * Copyright 2015-2019 Christian Hergert <christian hergert me>
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#if !defined (IDE_CODE_INSIDE) && !defined (IDE_CODE_COMPILATION)
+# error "Only <libide-code.h> can be included directly."
+#endif
+
+#include <libide-core.h>
+
+#include "ide-code-types.h"
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+  IDE_SYMBOL_KIND_NONE,
+  IDE_SYMBOL_KIND_ALIAS,
+  IDE_SYMBOL_KIND_ARRAY,
+  IDE_SYMBOL_KIND_BOOLEAN,
+  IDE_SYMBOL_KIND_CLASS,
+  IDE_SYMBOL_KIND_CONSTANT,
+  IDE_SYMBOL_KIND_CONSTRUCTOR,
+  IDE_SYMBOL_KIND_ENUM,
+  IDE_SYMBOL_KIND_ENUM_VALUE,
+  IDE_SYMBOL_KIND_FIELD,
+  IDE_SYMBOL_KIND_FILE,
+  IDE_SYMBOL_KIND_FUNCTION,
+  IDE_SYMBOL_KIND_HEADER,
+  IDE_SYMBOL_KIND_INTERFACE,
+  IDE_SYMBOL_KIND_MACRO,
+  IDE_SYMBOL_KIND_METHOD,
+  IDE_SYMBOL_KIND_MODULE,
+  IDE_SYMBOL_KIND_NAMESPACE,
+  IDE_SYMBOL_KIND_NUMBER,
+  IDE_SYMBOL_KIND_PACKAGE,
+  IDE_SYMBOL_KIND_PROPERTY,
+  IDE_SYMBOL_KIND_SCALAR,
+  IDE_SYMBOL_KIND_STRING,
+  IDE_SYMBOL_KIND_STRUCT,
+  IDE_SYMBOL_KIND_TEMPLATE,
+  IDE_SYMBOL_KIND_UNION,
+  IDE_SYMBOL_KIND_VARIABLE,
+  IDE_SYMBOL_KIND_KEYWORD,
+  IDE_SYMBOL_KIND_UI_ATTRIBUTES,
+  IDE_SYMBOL_KIND_UI_CHILD,
+  IDE_SYMBOL_KIND_UI_ITEM,
+  IDE_SYMBOL_KIND_UI_MENU,
+  IDE_SYMBOL_KIND_UI_MENU_ATTRIBUTE,
+  IDE_SYMBOL_KIND_UI_OBJECT,
+  IDE_SYMBOL_KIND_UI_PACKING,
+  IDE_SYMBOL_KIND_UI_PROPERTY,
+  IDE_SYMBOL_KIND_UI_SECTION,
+  IDE_SYMBOL_KIND_UI_SIGNAL,
+  IDE_SYMBOL_KIND_UI_STYLE,
+  IDE_SYMBOL_KIND_UI_STYLE_CLASS,
+  IDE_SYMBOL_KIND_UI_SUBMENU,
+  IDE_SYMBOL_KIND_UI_TEMPLATE,
+  IDE_SYMBOL_KIND_XML_ATTRIBUTE,
+  IDE_SYMBOL_KIND_XML_DECLARATION,
+  IDE_SYMBOL_KIND_XML_ELEMENT,
+  IDE_SYMBOL_KIND_XML_COMMENT,
+  IDE_SYMBOL_KIND_XML_CDATA,
+} IdeSymbolKind;
+
+typedef enum
+{
+  IDE_SYMBOL_FLAGS_NONE          = 0,
+  IDE_SYMBOL_FLAGS_IS_STATIC     = 1 << 0,
+  IDE_SYMBOL_FLAGS_IS_MEMBER     = 1 << 1,
+  IDE_SYMBOL_FLAGS_IS_DEPRECATED = 1 << 2,
+  IDE_SYMBOL_FLAGS_IS_DEFINITION = 1 << 3
+} IdeSymbolFlags;
+
+#define IDE_TYPE_SYMBOL (ide_symbol_get_type())
+
+IDE_AVAILABLE_IN_3_32
+G_DECLARE_DERIVABLE_TYPE (IdeSymbol, ide_symbol, IDE, SYMBOL, GObject)
+
+struct _IdeSymbolClass
+{
+  GObjectClass parent_class;
+
+  /*< private >*/
+  gpointer _reserved[16];
+};
+
+IDE_AVAILABLE_IN_3_32
+IdeSymbol      *ide_symbol_new                 (const gchar     *name,
+                                                IdeSymbolKind    kind,
+                                                IdeSymbolFlags   flags,
+                                                IdeLocation     *location,
+                                                IdeLocation     *header_location);
+IDE_AVAILABLE_IN_3_32
+IdeSymbolKind   ide_symbol_get_kind            (IdeSymbol       *self);
+IDE_AVAILABLE_IN_3_32
+IdeSymbolFlags  ide_symbol_get_flags           (IdeSymbol       *self);
+IDE_AVAILABLE_IN_3_32
+const gchar    *ide_symbol_get_name            (IdeSymbol       *self);
+IDE_AVAILABLE_IN_3_32
+IdeLocation    *ide_symbol_get_location        (IdeSymbol       *self);
+IDE_AVAILABLE_IN_3_32
+IdeLocation    *ide_symbol_get_header_location (IdeSymbol       *self);
+IDE_AVAILABLE_IN_3_32
+IdeSymbol      *ide_symbol_new_from_variant    (GVariant        *variant);
+IDE_AVAILABLE_IN_3_32
+GVariant       *ide_symbol_to_variant          (IdeSymbol       *self);
+IDE_AVAILABLE_IN_3_32
+const gchar    *ide_symbol_kind_get_icon_name  (IdeSymbolKind    kind);
+
+G_END_DECLS
diff --git a/src/libide/code/ide-text-edit-private.h b/src/libide/code/ide-text-edit-private.h
new file mode 100644
index 000000000..0a9e1d106
--- /dev/null
+++ b/src/libide/code/ide-text-edit-private.h
@@ -0,0 +1,32 @@
+/* ide-text-edit-private.h
+ *
+ * Copyright 2016-2019 Christian Hergert <chergert redhat com>
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include "ide-code-types.h"
+
+G_BEGIN_DECLS
+
+void _ide_text_edit_apply   (IdeTextEdit *self,
+                             IdeBuffer   *buffer);
+void _ide_text_edit_prepare (IdeTextEdit *self,
+                             IdeBuffer   *buffer);
+
+G_END_DECLS
diff --git a/src/libide/code/ide-text-edit.c b/src/libide/code/ide-text-edit.c
new file mode 100644
index 000000000..5259a2220
--- /dev/null
+++ b/src/libide/code/ide-text-edit.c
@@ -0,0 +1,347 @@
+/* ide-text-edit.c
+ *
+ * Copyright 2015-2019 Christian Hergert <chergert redhat com>
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-text-edit"
+
+#include "config.h"
+
+#include "ide-buffer.h"
+#include "ide-text-edit.h"
+#include "ide-text-edit-private.h"
+#include "ide-location.h"
+#include "ide-range.h"
+
+typedef struct
+{
+  IdeRange       *range;
+  gchar          *text;
+
+  /* No references, cleared in apply */
+  GtkTextMark    *begin_mark;
+  GtkTextMark    *end_mark;
+} IdeTextEditPrivate;
+
+enum {
+  PROP_0,
+  PROP_RANGE,
+  PROP_TEXT,
+  N_PROPS
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (IdeTextEdit, ide_text_edit, IDE_TYPE_OBJECT)
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+ide_text_edit_finalize (GObject *object)
+{
+  IdeTextEdit *self = (IdeTextEdit *)object;
+  IdeTextEditPrivate *priv = ide_text_edit_get_instance_private (self);
+
+  g_clear_object (&priv->range);
+  g_clear_pointer (&priv->text, g_free);
+
+  G_OBJECT_CLASS (ide_text_edit_parent_class)->finalize (object);
+}
+
+static void
+ide_text_edit_get_property (GObject    *object,
+                            guint       prop_id,
+                            GValue     *value,
+                            GParamSpec *pspec)
+{
+  IdeTextEdit *self = IDE_TEXT_EDIT (object);
+
+  switch (prop_id)
+    {
+    case PROP_RANGE:
+      g_value_set_object (value, ide_text_edit_get_range (self));
+      break;
+
+    case PROP_TEXT:
+      g_value_set_string (value, ide_text_edit_get_text (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_text_edit_set_property (GObject      *object,
+                            guint         prop_id,
+                            const GValue *value,
+                            GParamSpec   *pspec)
+{
+  IdeTextEdit *self = IDE_TEXT_EDIT (object);
+
+  switch (prop_id)
+    {
+    case PROP_RANGE:
+      ide_text_edit_set_range (self, g_value_get_object (value));
+      break;
+
+    case PROP_TEXT:
+      ide_text_edit_set_text (self, g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_text_edit_class_init (IdeTextEditClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_text_edit_finalize;
+  object_class->get_property = ide_text_edit_get_property;
+  object_class->set_property = ide_text_edit_set_property;
+
+  properties [PROP_RANGE] =
+    g_param_spec_object ("range",
+                         "Range",
+                         "The range for the text edit",
+                         IDE_TYPE_RANGE,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_TEXT] =
+    g_param_spec_string ("text",
+                         "Text",
+                         "The text to replace",
+                         NULL,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_text_edit_init (IdeTextEdit *self)
+{
+}
+
+/**
+ * ide_text_edit_get_text:
+ * @self: a #IdeTextEdit
+ *
+ * Gets the text for the edit.
+ *
+ * Returns: (nullable): the text to replace, or %NULL
+ *
+ * Since: 3.32
+ */
+const gchar *
+ide_text_edit_get_text (IdeTextEdit *self)
+{
+  IdeTextEditPrivate *priv = ide_text_edit_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_TEXT_EDIT (self), NULL);
+
+  return priv->text;
+}
+
+/**
+ * ide_text_edit_get_range:
+ * @self: a #IdeTextEdit
+ *
+ * Gets the range for the edit.
+ *
+ * Returns: (transfer none) (nullable): the range for the replacement, or %NULL
+ *
+ * Since: 3.32
+ */
+IdeRange *
+ide_text_edit_get_range (IdeTextEdit *self)
+{
+  IdeTextEditPrivate *priv = ide_text_edit_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_TEXT_EDIT (self), NULL);
+
+  return priv->range;
+}
+
+void
+_ide_text_edit_apply (IdeTextEdit *self,
+                      IdeBuffer   *buffer)
+{
+  IdeTextEditPrivate *priv = ide_text_edit_get_instance_private (self);
+  GtkTextIter begin;
+  GtkTextIter end;
+
+  g_assert (IDE_IS_TEXT_EDIT (self));
+  g_assert (IDE_IS_BUFFER (buffer));
+
+  gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (buffer), &begin, priv->begin_mark);
+  gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (buffer), &end, priv->end_mark);
+  gtk_text_buffer_delete (GTK_TEXT_BUFFER (buffer), &begin, &end);
+  gtk_text_buffer_insert (GTK_TEXT_BUFFER (buffer), &begin, priv->text, -1);
+  gtk_text_buffer_delete_mark (GTK_TEXT_BUFFER (buffer), priv->begin_mark);
+  gtk_text_buffer_delete_mark (GTK_TEXT_BUFFER (buffer), priv->end_mark);
+}
+
+void
+_ide_text_edit_prepare (IdeTextEdit *self,
+                        IdeBuffer   *buffer)
+{
+  IdeTextEditPrivate *priv = ide_text_edit_get_instance_private (self);
+  IdeLocation *begin;
+  IdeLocation *end;
+  GtkTextIter begin_iter;
+  GtkTextIter end_iter;
+
+  g_assert (IDE_IS_TEXT_EDIT (self));
+  g_assert (IDE_IS_BUFFER (buffer));
+
+  begin = ide_range_get_begin (priv->range);
+  end = ide_range_get_end (priv->range);
+
+  ide_buffer_get_iter_at_location (buffer, &begin_iter, begin);
+  ide_buffer_get_iter_at_location (buffer, &end_iter, end);
+
+  priv->begin_mark = gtk_text_buffer_create_mark (GTK_TEXT_BUFFER (buffer),
+                                                  NULL,
+                                                  &begin_iter,
+                                                  TRUE);
+
+  priv->end_mark = gtk_text_buffer_create_mark (GTK_TEXT_BUFFER (buffer),
+                                                NULL,
+                                                &end_iter,
+                                                FALSE);
+}
+
+IdeTextEdit *
+ide_text_edit_new (IdeRange    *range,
+                   const gchar *text)
+{
+  g_return_val_if_fail (IDE_IS_RANGE (range), NULL);
+
+  return g_object_new (IDE_TYPE_TEXT_EDIT,
+                       "range", range,
+                       "text", text,
+                       NULL);
+}
+
+/**
+ * ide_text_edit_to_variant:
+ * @self: a #IdeTextEdit
+ *
+ * Creates a #GVariant to represent a text_edit.
+ *
+ * This function will never return a floating variant.
+ *
+ * Returns: (transfer full): a #GVariant
+ *
+ * Since: 3.32
+ */
+GVariant *
+ide_text_edit_to_variant (IdeTextEdit *self)
+{
+  IdeTextEditPrivate *priv = ide_text_edit_get_instance_private (self);
+  GVariantDict dict;
+  g_autoptr(GVariant) vrange = NULL;
+
+  g_return_val_if_fail (self != NULL, NULL);
+
+  g_variant_dict_init (&dict, NULL);
+
+  g_variant_dict_insert (&dict, "text", "s", priv->text ?: "");
+
+  if ((vrange = ide_range_to_variant (priv->range)))
+    g_variant_dict_insert_value (&dict, "range", vrange);
+
+  return g_variant_take_ref (g_variant_dict_end (&dict));
+}
+
+/**
+ * ide_text_edit_new_from_variant:
+ * @variant: (nullable): a #GVariant
+ *
+ * Creates a new #IdeTextEdit from the variant.
+ *
+ * If @variant is %NULL, %NULL is returned.
+ *
+ * Returns: (transfer full) (nullable): an #IdeTextEdit or %NULL
+ *
+ * Since: 3.32
+ */
+IdeTextEdit *
+ide_text_edit_new_from_variant (GVariant *variant)
+{
+  g_autoptr(GVariant) unboxed = NULL;
+  g_autoptr(GVariant) vrange = NULL;
+  g_autoptr(IdeRange) range = NULL;
+  GVariantDict dict;
+  const gchar *text;
+  IdeTextEdit *self = NULL;
+
+  if (variant == NULL)
+    return NULL;
+
+  if (g_variant_is_of_type (variant, G_VARIANT_TYPE_VARIANT))
+    variant = unboxed = g_variant_get_variant (variant);
+
+  g_variant_dict_init (&dict, variant);
+
+  if (!g_variant_dict_lookup (&dict, "text", "&s", &text))
+    text = "";
+
+  if ((vrange = g_variant_dict_lookup_value (&dict, "range", NULL)))
+    {
+      if (!(range = ide_range_new_from_variant (vrange)))
+        goto failed;
+    }
+
+  self = ide_text_edit_new (range, text);
+
+failed:
+
+  g_variant_dict_clear (&dict);
+
+  return self;
+}
+
+void
+ide_text_edit_set_text (IdeTextEdit *self,
+                        const gchar *text)
+{
+  IdeTextEditPrivate *priv = ide_text_edit_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_TEXT_EDIT (self));
+
+  if (!ide_str_equal0 (priv->text, text))
+    {
+      g_free (priv->text);
+      priv->text = g_strdup (text);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TEXT]);
+    }
+}
+
+void
+ide_text_edit_set_range (IdeTextEdit *self,
+                         IdeRange    *range)
+{
+  IdeTextEditPrivate *priv = ide_text_edit_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_TEXT_EDIT (self));
+
+  if (g_set_object (&priv->range, range))
+    g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_RANGE]);
+}
diff --git a/src/libide/code/ide-text-edit.h b/src/libide/code/ide-text-edit.h
new file mode 100644
index 000000000..eb7a3287d
--- /dev/null
+++ b/src/libide/code/ide-text-edit.h
@@ -0,0 +1,64 @@
+/* ide-text-edit.h
+ *
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#if !defined (IDE_CODE_INSIDE) && !defined (IDE_CODE_COMPILATION)
+# error "Only <libide-code.h> can be included directly."
+#endif
+
+#include <libide-core.h>
+
+#include "ide-code-types.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_TEXT_EDIT (ide_text_edit_get_type())
+
+IDE_AVAILABLE_IN_3_32
+G_DECLARE_DERIVABLE_TYPE (IdeTextEdit, ide_text_edit, IDE, TEXT_EDIT, IdeObject)
+
+struct _IdeTextEditClass
+{
+  IdeObjectClass parent_class;
+
+  /*< private >*/
+  gpointer _reserved[16];
+};
+
+IDE_AVAILABLE_IN_3_32
+IdeTextEdit *ide_text_edit_new              (IdeRange    *range,
+                                             const gchar *text);
+IDE_AVAILABLE_IN_3_32
+IdeTextEdit *ide_text_edit_new_from_variant (GVariant    *variant);
+IDE_AVAILABLE_IN_3_32
+const gchar *ide_text_edit_get_text         (IdeTextEdit *self);
+IDE_AVAILABLE_IN_3_32
+void         ide_text_edit_set_text         (IdeTextEdit *self,
+                                             const gchar *text);
+IDE_AVAILABLE_IN_3_32
+IdeRange    *ide_text_edit_get_range        (IdeTextEdit *self);
+IDE_AVAILABLE_IN_3_32
+void         ide_text_edit_set_range        (IdeTextEdit *self,
+                                             IdeRange    *range);
+IDE_AVAILABLE_IN_3_32
+GVariant    *ide_text_edit_to_variant       (IdeTextEdit *self);
+
+G_END_DECLS
diff --git a/src/libide/sourceview/ide-text-iter.c b/src/libide/code/ide-text-iter.c
similarity index 84%
rename from src/libide/sourceview/ide-text-iter.c
rename to src/libide/code/ide-text-iter.c
index 552dec351..541b7df43 100644
--- a/src/libide/sourceview/ide-text-iter.c
+++ b/src/libide/code/ide-text-iter.c
@@ -22,14 +22,11 @@
 
 #include "config.h"
 
-#include <dazzle.h>
 #include <gtk/gtk.h>
 #include <gtksourceview/gtksource.h>
 #include <string.h>
 
-#include "ide-debug.h"
-
-#include "sourceview/ide-text-iter.h"
+#include "ide-text-iter.h"
 
 typedef enum
 {
@@ -48,7 +45,7 @@ enum
 };
 
 static int
-_ide_text_word_classify (gunichar ch)
+ide_text_word_classify (gunichar ch)
 {
   switch (ch)
     {
@@ -75,7 +72,7 @@ _ide_text_word_classify (gunichar ch)
 }
 
 static int
-_ide_text_word_classify_newline_stop (gunichar ch)
+ide_text_word_classify_newline_stop (gunichar ch)
 {
   switch (ch)
     {
@@ -104,7 +101,7 @@ _ide_text_word_classify_newline_stop (gunichar ch)
 }
 
 static int
-_ide_text_WORD_classify (gunichar ch)
+ide_text_WORD_classify (gunichar ch)
 {
   if (g_unichar_isspace (ch))
     return CLASS_SPACE;
@@ -112,7 +109,7 @@ _ide_text_WORD_classify (gunichar ch)
 }
 
 static int
-_ide_text_WORD_classify_newline_stop (gunichar ch)
+ide_text_WORD_classify_newline_stop (gunichar ch)
 {
   if (ch == '\n')
     return CLASS_NEWLINE;
@@ -123,13 +120,13 @@ _ide_text_WORD_classify_newline_stop (gunichar ch)
 }
 
 static gboolean
-_ide_text_iter_line_is_empty (GtkTextIter *iter)
+ide_text_iter_line_is_empty (GtkTextIter *iter)
 {
   return gtk_text_iter_starts_line (iter) && gtk_text_iter_ends_line (iter);
 }
 
 /**
- * _ide_text_iter_backward_paragraph_start:
+ * ide_text_iter_backward_paragraph_start:
  * @iter: a #GtkTextIter
  *
  * Searches backwards until we find the beginning of a paragraph.
@@ -139,18 +136,18 @@ _ide_text_iter_line_is_empty (GtkTextIter *iter)
  * Since: 3.32
  */
 gboolean
-_ide_text_iter_backward_paragraph_start (GtkTextIter *iter)
+ide_text_iter_backward_paragraph_start (GtkTextIter *iter)
 {
   g_return_val_if_fail (iter, FALSE);
 
   /* Work our way past the current empty lines */
-  if (_ide_text_iter_line_is_empty (iter))
-    while (_ide_text_iter_line_is_empty (iter))
+  if (ide_text_iter_line_is_empty (iter))
+    while (ide_text_iter_line_is_empty (iter))
       if (!gtk_text_iter_backward_line (iter))
         return FALSE;
 
   /* Now find first line that is empty */
-  while (!_ide_text_iter_line_is_empty (iter))
+  while (!ide_text_iter_line_is_empty (iter))
     if (!gtk_text_iter_backward_line (iter))
       return FALSE;
 
@@ -158,7 +155,7 @@ _ide_text_iter_backward_paragraph_start (GtkTextIter *iter)
 }
 
 /**
- * _ide_text_iter_forward_paragraph_end:
+ * ide_text_iter_forward_paragraph_end:
  * @iter: a #GtkTextIter
  *
  * Searches forward until the end of a paragraph has been hit.
@@ -168,18 +165,18 @@ _ide_text_iter_backward_paragraph_start (GtkTextIter *iter)
  * Since: 3.32
  */
 gboolean
-_ide_text_iter_forward_paragraph_end (GtkTextIter *iter)
+ide_text_iter_forward_paragraph_end (GtkTextIter *iter)
 {
   g_return_val_if_fail (iter, FALSE);
 
   /* Work our way past the current empty lines */
-  if (_ide_text_iter_line_is_empty (iter))
-    while (_ide_text_iter_line_is_empty (iter))
+  if (ide_text_iter_line_is_empty (iter))
+    while (ide_text_iter_line_is_empty (iter))
       if (!gtk_text_iter_forward_line (iter))
         return FALSE;
 
   /* Now find first line that is empty */
-  while (!_ide_text_iter_line_is_empty (iter))
+  while (!ide_text_iter_line_is_empty (iter))
     if (!gtk_text_iter_forward_line (iter))
       return FALSE;
 
@@ -203,7 +200,7 @@ sentence_end_chars (gunichar ch,
 }
 
 static SentenceStatus
-_ide_text_iter_backward_sentence_end (GtkTextIter *iter)
+ide_text_iter_backward_sentence_end (GtkTextIter *iter)
 {
   GtkTextIter end_bounds;
   GtkTextIter start_bounds;
@@ -213,7 +210,7 @@ _ide_text_iter_backward_sentence_end (GtkTextIter *iter)
 
   end_bounds = *iter;
   start_bounds = *iter;
-  found_para = _ide_text_iter_backward_paragraph_start (&start_bounds);
+  found_para = ide_text_iter_backward_paragraph_start (&start_bounds);
 
   if (!found_para)
     gtk_text_buffer_get_start_iter (gtk_text_iter_get_buffer (iter), &start_bounds);
@@ -259,7 +256,7 @@ _ide_text_iter_backward_sentence_end (GtkTextIter *iter)
 }
 
 gboolean
-_ide_text_iter_forward_sentence_end (GtkTextIter *iter)
+ide_text_iter_forward_sentence_end (GtkTextIter *iter)
 {
   GtkTextIter end_bounds;
   gboolean found_para;
@@ -267,7 +264,7 @@ _ide_text_iter_forward_sentence_end (GtkTextIter *iter)
   g_return_val_if_fail (iter, FALSE);
 
   end_bounds = *iter;
-  found_para = _ide_text_iter_forward_paragraph_end (&end_bounds);
+  found_para = ide_text_iter_forward_paragraph_end (&end_bounds);
 
   if (!found_para)
     gtk_text_buffer_get_end_iter (gtk_text_iter_get_buffer (iter), &end_bounds);
@@ -318,7 +315,7 @@ _ide_text_iter_forward_sentence_end (GtkTextIter *iter)
 }
 
 gboolean
-_ide_text_iter_backward_sentence_start (GtkTextIter *iter)
+ide_text_iter_backward_sentence_start (GtkTextIter *iter)
 {
   GtkTextIter tmp;
   SentenceStatus status;
@@ -326,7 +323,7 @@ _ide_text_iter_backward_sentence_start (GtkTextIter *iter)
   g_return_val_if_fail (iter, FALSE);
 
   tmp = *iter;
-  status = _ide_text_iter_backward_sentence_end (&tmp);
+  status = ide_text_iter_backward_sentence_end (&tmp);
 
   switch (status)
     {
@@ -357,7 +354,7 @@ _ide_text_iter_backward_sentence_start (GtkTextIter *iter)
 }
 
 static gboolean
-_ide_text_iter_forward_classified_start (GtkTextIter  *iter,
+ide_text_iter_forward_classified_start (GtkTextIter  *iter,
                                          gint        (*classify) (gunichar))
 {
   gint begin_class;
@@ -404,27 +401,27 @@ _ide_text_iter_forward_classified_start (GtkTextIter  *iter,
 }
 
 gboolean
-_ide_text_iter_forward_word_start (GtkTextIter *iter,
+ide_text_iter_forward_word_start (GtkTextIter *iter,
                                    gboolean     newline_stop)
 {
   if (newline_stop)
-    return _ide_text_iter_forward_classified_start (iter, _ide_text_word_classify_newline_stop);
+    return ide_text_iter_forward_classified_start (iter, ide_text_word_classify_newline_stop);
   else
-    return _ide_text_iter_forward_classified_start (iter, _ide_text_word_classify);
+    return ide_text_iter_forward_classified_start (iter, ide_text_word_classify);
 }
 
 gboolean
-_ide_text_iter_forward_WORD_start (GtkTextIter *iter,
+ide_text_iter_forward_WORD_start (GtkTextIter *iter,
                                    gboolean     newline_stop)
 {
   if (newline_stop)
-    return _ide_text_iter_forward_classified_start (iter, _ide_text_WORD_classify_newline_stop);
+    return ide_text_iter_forward_classified_start (iter, ide_text_WORD_classify_newline_stop);
   else
-    return _ide_text_iter_forward_classified_start (iter, _ide_text_WORD_classify);
+    return ide_text_iter_forward_classified_start (iter, ide_text_WORD_classify);
 }
 
 static gboolean
-_ide_text_iter_forward_classified_end (GtkTextIter  *iter,
+ide_text_iter_forward_classified_end (GtkTextIter  *iter,
                                        gint        (*classify) (gunichar))
 {
   gunichar ch;
@@ -439,7 +436,7 @@ _ide_text_iter_forward_classified_end (GtkTextIter  *iter,
   /* If we are on space, walk to the start of the next word. */
   ch = gtk_text_iter_get_char (iter);
   if (classify (ch) == CLASS_SPACE)
-    if (!_ide_text_iter_forward_classified_start (iter, classify))
+    if (!ide_text_iter_forward_classified_start (iter, classify))
       return FALSE;
 
   ch = gtk_text_iter_get_char (iter);
@@ -470,27 +467,27 @@ _ide_text_iter_forward_classified_end (GtkTextIter  *iter,
 }
 
 gboolean
-_ide_text_iter_forward_word_end (GtkTextIter *iter,
+ide_text_iter_forward_word_end (GtkTextIter *iter,
                                  gboolean     newline_stop)
 {
   if (newline_stop)
-    return _ide_text_iter_forward_classified_end (iter, _ide_text_word_classify_newline_stop);
+    return ide_text_iter_forward_classified_end (iter, ide_text_word_classify_newline_stop);
   else
-    return _ide_text_iter_forward_classified_end (iter, _ide_text_word_classify);
+    return ide_text_iter_forward_classified_end (iter, ide_text_word_classify);
 }
 
 gboolean
-_ide_text_iter_forward_WORD_end (GtkTextIter *iter,
+ide_text_iter_forward_WORD_end (GtkTextIter *iter,
                                  gboolean     newline_stop)
 {
   if (newline_stop)
-    return _ide_text_iter_forward_classified_end (iter, _ide_text_WORD_classify_newline_stop);
+    return ide_text_iter_forward_classified_end (iter, ide_text_WORD_classify_newline_stop);
   else
-    return _ide_text_iter_forward_classified_end (iter, _ide_text_WORD_classify);
+    return ide_text_iter_forward_classified_end (iter, ide_text_WORD_classify);
 }
 
 static gboolean
-_ide_text_iter_backward_classified_end (GtkTextIter  *iter,
+ide_text_iter_backward_classified_end (GtkTextIter  *iter,
                                         gint        (*classify) (gunichar))
 {
   gunichar ch;
@@ -534,27 +531,27 @@ _ide_text_iter_backward_classified_end (GtkTextIter  *iter,
 }
 
 gboolean
-_ide_text_iter_backward_word_end (GtkTextIter *iter,
+ide_text_iter_backward_word_end (GtkTextIter *iter,
                                   gboolean     newline_stop)
 {
   if (newline_stop)
-    return _ide_text_iter_backward_classified_end (iter, _ide_text_word_classify_newline_stop);
+    return ide_text_iter_backward_classified_end (iter, ide_text_word_classify_newline_stop);
   else
-    return _ide_text_iter_backward_classified_end (iter, _ide_text_word_classify);
+    return ide_text_iter_backward_classified_end (iter, ide_text_word_classify);
 }
 
 gboolean
-_ide_text_iter_backward_WORD_end (GtkTextIter *iter,
+ide_text_iter_backward_WORD_end (GtkTextIter *iter,
                                   gboolean     newline_stop)
 {
   if (newline_stop)
-    return _ide_text_iter_backward_classified_end (iter, _ide_text_WORD_classify_newline_stop);
+    return ide_text_iter_backward_classified_end (iter, ide_text_WORD_classify_newline_stop);
   else
-    return _ide_text_iter_backward_classified_end (iter, _ide_text_WORD_classify);
+    return ide_text_iter_backward_classified_end (iter, ide_text_WORD_classify);
 }
 
 static gboolean
-_ide_text_iter_backward_classified_start (GtkTextIter  *iter,
+ide_text_iter_backward_classified_start (GtkTextIter  *iter,
                                           gint        (*classify) (gunichar))
 {
   gunichar ch;
@@ -569,7 +566,7 @@ _ide_text_iter_backward_classified_start (GtkTextIter  *iter,
   /* If we are on space, walk to the end of the previous word. */
   ch = gtk_text_iter_get_char (iter);
   if (classify (ch) == CLASS_SPACE)
-    if (!_ide_text_iter_backward_classified_end (iter, classify))
+    if (!ide_text_iter_backward_classified_end (iter, classify))
       return FALSE;
 
   ch = gtk_text_iter_get_char (iter);
@@ -599,23 +596,23 @@ _ide_text_iter_backward_classified_start (GtkTextIter  *iter,
 }
 
 gboolean
-_ide_text_iter_backward_word_start (GtkTextIter *iter,
+ide_text_iter_backward_word_start (GtkTextIter *iter,
                                     gboolean     newline_stop)
 {
   if (newline_stop)
-    return _ide_text_iter_backward_classified_start (iter, _ide_text_word_classify_newline_stop);
+    return ide_text_iter_backward_classified_start (iter, ide_text_word_classify_newline_stop);
   else
-    return _ide_text_iter_backward_classified_start (iter, _ide_text_word_classify);
+    return ide_text_iter_backward_classified_start (iter, ide_text_word_classify);
 }
 
 gboolean
-_ide_text_iter_backward_WORD_start (GtkTextIter *iter,
+ide_text_iter_backward_WORD_start (GtkTextIter *iter,
                                     gboolean     newline_stop)
 {
   if (newline_stop)
-    return _ide_text_iter_backward_classified_start (iter, _ide_text_WORD_classify_newline_stop);
+    return ide_text_iter_backward_classified_start (iter, ide_text_WORD_classify_newline_stop);
   else
-    return _ide_text_iter_backward_classified_start (iter, _ide_text_WORD_classify);
+    return ide_text_iter_backward_classified_start (iter, ide_text_WORD_classify);
 }
 
 static gboolean
@@ -630,11 +627,19 @@ matches_pred (GtkTextIter              *iter,
   return (*pred) (iter, ch, user_data);
 }
 
-/* Similar to gtk_text_iter_forward_find_char but
- * lets us acces to the iter in the predicate
+/**
+ * ide_text_iter_forward_find_char:
+ * @pred: (scope call): a callback to locate the char.
+ *
+ * Similar to gtk_text_iter_forward_find_char but
+ * lets us acces to the iter in the predicate.
+ *
+ * Returns: %TRUE if found
+ *
+ * Since: 3.32
  */
 gboolean
-_ide_text_iter_forward_find_char (GtkTextIter              *iter,
+ide_text_iter_forward_find_char (GtkTextIter              *iter,
                                   IdeTextIterCharPredicate  pred,
                                   gpointer                  user_data,
                                   const GtkTextIter        *limit)
@@ -659,8 +664,14 @@ _ide_text_iter_forward_find_char (GtkTextIter              *iter,
 /* Similar to gtk_text_iter_backward_find_char but
  * lets us acces to the iter in the predicate
  */
+/**
+ * ide_text_iter_backward_find_char:
+ * @pred: (scope call):
+ *
+ * Since: 3.32
+ */
 gboolean
-_ide_text_iter_backward_find_char (GtkTextIter              *iter,
+ide_text_iter_backward_find_char (GtkTextIter              *iter,
                                    IdeTextIterCharPredicate  pred,
                                    gpointer                  user_data,
                                    const GtkTextIter        *limit)
@@ -697,7 +708,7 @@ _ide_text_iter_backward_find_char (GtkTextIter              *iter,
  * Since: 3.32
  */
 gboolean
-_ide_text_iter_in_string (GtkTextIter *iter,
+ide_text_iter_in_string (GtkTextIter *iter,
                           const gchar *str,
                           GtkTextIter *str_start,
                           GtkTextIter *str_end,
@@ -721,7 +732,7 @@ _ide_text_iter_in_string (GtkTextIter *iter,
   GtkTextIter end_iter;
   gboolean ret = FALSE;
 
-  g_return_val_if_fail (!dzl_str_empty0 (str), FALSE);
+  g_return_val_if_fail (!ide_str_empty0 (str), FALSE);
 
   len = g_utf8_strlen (str, -1);
   cursor_offset = gtk_text_iter_get_offset (iter);
@@ -782,7 +793,7 @@ _ide_text_iter_in_string (GtkTextIter *iter,
 }
 
 /**
- * _ide_text_iter_find_chars_backward:
+ * ide_text_iter_find_chars_backward:
  * @iter: a #GtkTextIter indicating the start position to check for.
  * @limit: (nullable): a #GtkTextIter indicating the limit of the search.
  * @end: (out) (nullable): a #GtkTextIter returning the str end iter (if found).
@@ -799,7 +810,7 @@ _ide_text_iter_in_string (GtkTextIter *iter,
  * Since: 3.32
  */
 gboolean
-_ide_text_iter_find_chars_backward (GtkTextIter *iter,
+ide_text_iter_find_chars_backward (GtkTextIter *iter,
                                     GtkTextIter *limit,
                                     GtkTextIter *end,
                                     const gchar *str,
@@ -809,7 +820,7 @@ _ide_text_iter_find_chars_backward (GtkTextIter *iter,
   const gchar *str_limit;
   GtkTextIter base_cursor;
 
-  g_return_val_if_fail (!dzl_str_empty0 (str), FALSE);
+  g_return_val_if_fail (!ide_str_empty0 (str), FALSE);
 
   if (!gtk_text_iter_backward_char (iter))
     return FALSE;
@@ -855,7 +866,7 @@ _ide_text_iter_find_chars_backward (GtkTextIter *iter,
 }
 
 /**
- * _ide_text_iter_find_chars_forward:
+ * ide_text_iter_find_chars_forward:
  * @iter: a #GtkTextIter indicating the start position to check for.
  * @limit: (nullable): a #GtkTextIter indicating the limit of the search.
  * @end: (out) (nullable): a #GtkTextIter returning the str end iter (if found).
@@ -871,7 +882,7 @@ _ide_text_iter_find_chars_backward (GtkTextIter *iter,
  * Since: 3.32
  */
 gboolean
-_ide_text_iter_find_chars_forward (GtkTextIter *iter,
+ide_text_iter_find_chars_forward (GtkTextIter *iter,
                                    GtkTextIter *limit,
                                    GtkTextIter *end,
                                    const gchar *str,
@@ -884,7 +895,7 @@ _ide_text_iter_find_chars_forward (GtkTextIter *iter,
   gint str_char_len;
   gint real_limit_offset;
 
-  g_return_val_if_fail (!dzl_str_empty0 (str), FALSE);
+  g_return_val_if_fail (!ide_str_empty0 (str), FALSE);
 
   if (limit == NULL)
     {
@@ -948,7 +959,7 @@ is_symbol_char (gunichar ch)
 }
 
 gchar *
-_ide_text_iter_current_symbol (const GtkTextIter *iter,
+ide_text_iter_current_symbol (const GtkTextIter *iter,
                                GtkTextIter       *out_begin)
 {
   GtkTextBuffer *buffer;
diff --git a/src/libide/sourceview/ide-text-iter.h b/src/libide/code/ide-text-iter.h
similarity index 69%
rename from src/libide/sourceview/ide-text-iter.h
rename to src/libide/code/ide-text-iter.h
index 7a3da6a6f..151cb6644 100644
--- a/src/libide/sourceview/ide-text-iter.h
+++ b/src/libide/code/ide-text-iter.h
@@ -1,6 +1,6 @@
 /* ide-text-iter.h
  *
- * Copyright 2015-2019 Christian Hergert <christian hergert me>
+ * Copyright 2015-2019 Christian Hergert <chergert redhat com>
  *
  * 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
@@ -20,9 +20,12 @@
 
 #pragma once
 
-#include <gtk/gtk.h>
+#if !defined (IDE_CODE_INSIDE) && !defined (IDE_CODE_COMPILATION)
+# error "Only <libide-code.h> can be included directly."
+#endif
 
-#include "ide-version-macros.h"
+#include <gtk/gtk.h>
+#include <libide-core.h>
 
 G_BEGIN_DECLS
 
@@ -33,67 +36,67 @@ typedef gboolean (* IdeTextIterCharPredicate)    (GtkTextIter              *iter
                                                   gpointer                  user_data);
 
 IDE_AVAILABLE_IN_3_32
-gboolean _ide_text_iter_forward_find_char        (GtkTextIter              *iter,
+gboolean ide_text_iter_forward_find_char        (GtkTextIter              *iter,
                                                   IdeTextIterCharPredicate  pred,
                                                   gpointer                  user_data,
                                                   const GtkTextIter        *limit);
 IDE_AVAILABLE_IN_3_32
-gboolean _ide_text_iter_backward_find_char       (GtkTextIter              *iter,
+gboolean ide_text_iter_backward_find_char       (GtkTextIter              *iter,
                                                   IdeTextIterCharPredicate  pred,
                                                   gpointer                  user_data,
                                                   const GtkTextIter        *limit);
 IDE_AVAILABLE_IN_3_32
-gboolean _ide_text_iter_forward_word_start       (GtkTextIter              *iter,
+gboolean ide_text_iter_forward_word_start       (GtkTextIter              *iter,
                                                   gboolean                  newline_stop);
 IDE_AVAILABLE_IN_3_32
-gboolean _ide_text_iter_forward_WORD_start       (GtkTextIter              *iter,
+gboolean ide_text_iter_forward_WORD_start       (GtkTextIter              *iter,
                                                   gboolean                  newline_stop);
 IDE_AVAILABLE_IN_3_32
-gboolean _ide_text_iter_forward_word_end         (GtkTextIter              *iter,
+gboolean ide_text_iter_forward_word_end         (GtkTextIter              *iter,
                                                   gboolean                  newline_stop);
 IDE_AVAILABLE_IN_3_32
-gboolean _ide_text_iter_forward_WORD_end         (GtkTextIter              *iter,
+gboolean ide_text_iter_forward_WORD_end         (GtkTextIter              *iter,
                                                   gboolean                  newline_stop);
 IDE_AVAILABLE_IN_3_32
-gboolean _ide_text_iter_backward_paragraph_start (GtkTextIter              *iter);
+gboolean ide_text_iter_backward_paragraph_start (GtkTextIter              *iter);
 IDE_AVAILABLE_IN_3_32
-gboolean _ide_text_iter_forward_paragraph_end    (GtkTextIter              *iter);
+gboolean ide_text_iter_forward_paragraph_end    (GtkTextIter              *iter);
 IDE_AVAILABLE_IN_3_32
-gboolean _ide_text_iter_backward_sentence_start  (GtkTextIter              *iter);
+gboolean ide_text_iter_backward_sentence_start  (GtkTextIter              *iter);
 IDE_AVAILABLE_IN_3_32
-gboolean _ide_text_iter_forward_sentence_end     (GtkTextIter              *iter);
+gboolean ide_text_iter_forward_sentence_end     (GtkTextIter              *iter);
 IDE_AVAILABLE_IN_3_32
-gboolean _ide_text_iter_backward_WORD_start      (GtkTextIter              *iter,
+gboolean ide_text_iter_backward_WORD_start      (GtkTextIter              *iter,
                                                   gboolean                  newline_stop);
 IDE_AVAILABLE_IN_3_32
-gboolean _ide_text_iter_backward_word_start      (GtkTextIter              *iter,
+gboolean ide_text_iter_backward_word_start      (GtkTextIter              *iter,
                                                   gboolean                  newline_stop);
 IDE_AVAILABLE_IN_3_32
-gboolean _ide_text_iter_backward_WORD_end        (GtkTextIter              *iter,
+gboolean ide_text_iter_backward_WORD_end        (GtkTextIter              *iter,
                                                   gboolean                  newline_stop);
 IDE_AVAILABLE_IN_3_32
-gboolean _ide_text_iter_backward_word_end        (GtkTextIter              *iter,
+gboolean ide_text_iter_backward_word_end        (GtkTextIter              *iter,
                                                   gboolean                  newline_stop);
 IDE_AVAILABLE_IN_3_32
-gboolean _ide_text_iter_in_string                (GtkTextIter              *iter,
+gboolean ide_text_iter_in_string                (GtkTextIter              *iter,
                                                   const gchar              *str,
                                                   GtkTextIter              *str_start,
                                                   GtkTextIter              *str_end,
                                                   gboolean                  include_str_bounds);
 IDE_AVAILABLE_IN_3_32
-gboolean _ide_text_iter_find_chars_backward      (GtkTextIter              *iter,
+gboolean ide_text_iter_find_chars_backward      (GtkTextIter              *iter,
                                                   GtkTextIter              *limit,
                                                   GtkTextIter              *end,
                                                   const gchar              *str,
                                                   gboolean                  only_at_start);
 IDE_AVAILABLE_IN_3_32
-gboolean _ide_text_iter_find_chars_forward       (GtkTextIter              *iter,
+gboolean ide_text_iter_find_chars_forward       (GtkTextIter              *iter,
                                                   GtkTextIter              *limit,
                                                   GtkTextIter              *end,
                                                   const gchar              *str,
                                                   gboolean                  only_at_start);
 IDE_AVAILABLE_IN_3_32
-gchar   *_ide_text_iter_current_symbol           (const GtkTextIter        *iter,
+gchar   *ide_text_iter_current_symbol           (const GtkTextIter        *iter,
                                                   GtkTextIter              *out_begin);
 
 G_END_DECLS
diff --git a/src/libide/code/libide-code.gresource.xml b/src/libide/code/libide-code.gresource.xml
new file mode 100644
index 000000000..22ebf3183
--- /dev/null
+++ b/src/libide/code/libide-code.gresource.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+  <gresource prefix="/org/gnome/builder/file-settings">
+    <file>defaults.ini</file>
+  </gresource>
+</gresources>
diff --git a/src/libide/code/libide-code.h b/src/libide/code/libide-code.h
new file mode 100644
index 000000000..e35570a3c
--- /dev/null
+++ b/src/libide/code/libide-code.h
@@ -0,0 +1,70 @@
+/* libide-code.h
+ *
+ * Copyright 2018-2019 Christian Hergert <chergert redhat com>
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <libide-core.h>
+#include <libide-io.h>
+#include <libide-threading.h>
+
+G_BEGIN_DECLS
+
+#define IDE_CODE_INSIDE
+
+#include "ide-code-enums.h"
+#include "ide-code-types.h"
+
+#include "ide-buffer.h"
+#include "ide-buffer-addin.h"
+#include "ide-buffer-change-monitor.h"
+#include "ide-buffer-manager.h"
+#include "ide-code-index-entries.h"
+#include "ide-code-index-entry.h"
+#include "ide-code-indexer.h"
+#include "ide-diagnostic.h"
+#include "ide-diagnostic-provider.h"
+#include "ide-diagnostics.h"
+#include "ide-diagnostics-manager.h"
+#include "ide-file-settings.h"
+#include "ide-formatter-options.h"
+#include "ide-formatter.h"
+#include "ide-highlight-engine.h"
+#include "ide-highlight-index.h"
+#include "ide-highlighter.h"
+#include "ide-indent-style.h"
+#include "ide-language.h"
+#include "ide-location.h"
+#include "ide-range.h"
+#include "ide-rename-provider.h"
+#include "ide-source-iter.h"
+#include "ide-source-style-scheme.h"
+#include "ide-spaces-style.h"
+#include "ide-symbol-node.h"
+#include "ide-symbol-resolver.h"
+#include "ide-symbol-tree.h"
+#include "ide-symbol.h"
+#include "ide-text-edit.h"
+#include "ide-text-iter.h"
+#include "ide-unsaved-file.h"
+#include "ide-unsaved-files.h"
+
+#undef IDE_CODE_INSIDE
+
+G_END_DECLS
diff --git a/src/libide/code/meson.build b/src/libide/code/meson.build
new file mode 100644
index 000000000..14e7979b2
--- /dev/null
+++ b/src/libide/code/meson.build
@@ -0,0 +1,189 @@
+libide_code_header_dir = join_paths(libide_header_dir, 'code')
+libide_code_header_subdir = join_paths(libide_header_subdir, 'code')
+
+libide_code_generated_sources = []
+libide_code_generated_headers = []
+libide_include_directories += include_directories('.')
+
+#
+# Public API Headers
+#
+
+libide_code_private_headers = [
+  'ide-buffer-private.h',
+  'ide-doc-seq-private.h',
+  'ide-gsettings-file-settings.h',
+  'ide-language-defaults.h',
+  'ide-text-edit-private.h',
+  'ide-unsaved-file-private.h',
+]
+
+libide_code_public_headers = [
+  'ide-buffer-addin.h',
+  'ide-buffer-change-monitor.h',
+  'ide-buffer.h',
+  'ide-buffer-manager.h',
+  'ide-code-index-entries.h',
+  'ide-code-index-entry.h',
+  'ide-code-indexer.h',
+  'ide-code-types.h',
+  'ide-diagnostic.h',
+  'ide-diagnostic-provider.h',
+  'ide-diagnostics.h',
+  'ide-diagnostics-manager.h',
+  'ide-file-settings.h',
+  'ide-file-settings.defs',
+  'ide-formatter.h',
+  'ide-formatter-options.h',
+  'ide-highlight-engine.h',
+  'ide-highlighter.h',
+  'ide-highlight-index.h',
+  'ide-indent-style.h',
+  'ide-language.h',
+  'ide-location.h',
+  'ide-range.h',
+  'ide-rename-provider.h',
+  'ide-source-iter.h',
+  'ide-source-style-scheme.h',
+  'ide-spaces-style.h',
+  'ide-symbol.h',
+  'ide-symbol-node.h',
+  'ide-symbol-resolver.h',
+  'ide-symbol-tree.h',
+  'ide-text-edit.h',
+  'ide-text-iter.h',
+  'ide-unsaved-file.h',
+  'ide-unsaved-files.h',
+  'libide-code.h',
+]
+
+libide_code_enum_headers = [
+  'ide-buffer.h',
+  'ide-buffer-manager.h',
+  'ide-diagnostic.h',
+  'ide-indent-style.h',
+  'ide-spaces-style.h',
+  'ide-symbol.h',
+]
+
+install_headers(libide_code_public_headers, subdir: libide_code_header_subdir)
+
+#
+# Sources
+#
+
+libide_code_private_sources = [
+  'ide-doc-seq.c',
+  'ide-gsettings-file-settings.c',
+  'ide-language-defaults.c',
+]
+
+libide_code_public_sources = [
+  'ide-buffer-addin.c',
+  'ide-buffer.c',
+  'ide-buffer-change-monitor.c',
+  'ide-buffer-manager.c',
+  'ide-code-global.c',
+  'ide-code-index-entries.c',
+  'ide-code-index-entry.c',
+  'ide-code-indexer.c',
+  'ide-diagnostic.c',
+  'ide-diagnostic-provider.c',
+  'ide-diagnostics.c',
+  'ide-diagnostics-manager.c',
+  'ide-file-settings.c',
+  'ide-formatter.c',
+  'ide-formatter-options.c',
+  'ide-highlight-engine.c',
+  'ide-highlighter.c',
+  'ide-highlight-index.c',
+  'ide-language.c',
+  'ide-location.c',
+  'ide-range.c',
+  'ide-rename-provider.c',
+  'ide-source-iter.c',
+  'ide-source-style-scheme.c',
+  'ide-symbol.c',
+  'ide-symbol-node.c',
+  'ide-symbol-resolver.c',
+  'ide-symbol-tree.c',
+  'ide-text-edit.c',
+  'ide-text-iter.c',
+  'ide-unsaved-file.c',
+  'ide-unsaved-files.c',
+]
+
+#
+# Enum generation
+#
+
+libide_code_enums = gnome.mkenums_simple('ide-code-enums',
+     body_prefix: '#include "config.h"',
+   header_prefix: '#include <libide-core.h>',
+       decorator: '_IDE_EXTERN',
+         sources: libide_code_enum_headers,
+  install_header: true,
+     install_dir: libide_code_header_dir,
+)
+libide_code_generated_sources += [libide_code_enums[0]]
+libide_code_generated_headers += [libide_code_enums[1]]
+
+#
+# Generated Resource Files
+#
+
+libide_code_resources = gnome.compile_resources(
+  'ide-code-resources',
+  'libide-code.gresource.xml',
+  c_name: 'ide_code',
+)
+libide_code_generated_headers += [libide_code_resources[1]]
+libide_code_generated_sources += libide_code_resources[0]
+
+
+#
+# Dependencies
+#
+
+libide_code_deps = [
+  libgio_dep,
+  libgtk_dep,
+  libgtksource_dep,
+  libdazzle_dep,
+  libtemplate_glib_dep,
+
+  libide_core_dep,
+  libide_plugins_dep,
+  libide_io_dep,
+  libide_threading_dep,
+]
+
+#
+# Library Definitions
+#
+
+
+libide_code = static_library('ide-code-' + libide_api_version,
+                             libide_code_public_sources,
+                             libide_code_private_sources,
+                             libide_code_generated_sources,
+                             libide_code_generated_headers,
+   dependencies: libide_code_deps,
+         c_args: libide_args + release_args + ['-DIDE_CODE_COMPILATION'],
+)
+
+libide_code_dep = declare_dependency(
+              sources: libide_code_private_headers + libide_code_generated_headers,
+         dependencies: libide_code_deps,
+           link_whole: libide_code,
+  include_directories: include_directories('.'),
+)
+
+gnome_builder_public_sources += files(libide_code_public_sources)
+gnome_builder_public_headers += files(libide_code_public_headers)
+gnome_builder_private_sources += files(libide_code_private_sources)
+gnome_builder_private_headers += files(libide_code_private_headers)
+gnome_builder_generated_headers += libide_code_generated_headers
+gnome_builder_generated_sources += libide_code_generated_sources
+gnome_builder_include_subdirs += libide_code_header_subdir
+gnome_builder_gir_extra_args += ['--c-include=libide-code.h', '-DIDE_CODE_COMPILATION']
diff --git a/src/libide/sourceview/ide-text-util.c b/src/libide/sourceview/ide-text-util.c
index 86776cd4b..3ec3c01ec 100644
--- a/src/libide/sourceview/ide-text-util.c
+++ b/src/libide/sourceview/ide-text-util.c
@@ -18,9 +18,11 @@
  *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
  */
 
-#include "sourceview/ide-text-util.h"
+#include "ide-text-util.h"
 
 void
 ide_text_util_delete_line (GtkTextView *text_view,



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