diff --git a/gdb/cli/cli-style.c b/gdb/cli/cli-style.c
index 7e20c1b4826..936f40e6358 100644
--- a/gdb/cli/cli-style.c
+++ b/gdb/cli/cli-style.c
@@ -340,8 +340,9 @@ If enabled, source code is styled.\n"
 "Note that source styling only works if styling in general is enabled,\n\
 see \"show style enabled\"."
 #else
-"Source highlighting is disabled in this installation of gdb, because\n\
-it was not linked against GNU Source Highlight."
+"Source highlighting may be disabled in this installation of gdb, because\n\
+it was not linked against GNU Source Highlight.  However, it might still be\n\
+available if the appropriate extension is available at runtime."
 #endif
 			   ), set_style_enabled, show_style_sources,
 			   &style_set_list, &style_show_list);
diff --git a/gdb/extension-priv.h b/gdb/extension-priv.h
index 2af2dede693..c35671013d9 100644
--- a/gdb/extension-priv.h
+++ b/gdb/extension-priv.h
@@ -254,6 +254,13 @@ struct extension_language_ops
      struct type *obj_type,
      const char *method_name,
      std::vector<xmethod_worker_up> *dm_vec);
+
+  /* Colorize a source file.  NAME is the source file's name, and
+     CONTENTS is the contents of the file.  This should either return
+     colorized (using ANSI terminal escapes) version of the contents,
+     or an empty option.  */
+  gdb::optional<std::string> (*colorize) (const std::string &name,
+					  const std::string &contents);
 };
 
 /* State necessary to restore a signal handler to its previous value.  */
diff --git a/gdb/extension.c b/gdb/extension.c
index f9418de4311..093779f2c47 100644
--- a/gdb/extension.c
+++ b/gdb/extension.c
@@ -903,6 +903,27 @@ xmethod_worker::get_result_type (value *object, gdb::array_view<value *> args)
   return result_type;
 }
 
+/* See extension.h.  */
+
+gdb::optional<std::string>
+ext_lang_colorize (const std::string &filename, const std::string &contents)
+{
+  int i;
+  const struct extension_language_defn *extlang;
+  gdb::optional<std::string> result;
+
+  ALL_ENABLED_EXTENSION_LANGUAGES (i, extlang)
+    {
+      if (extlang->ops->colorize == nullptr)
+	continue;
+      result = extlang->ops->colorize (filename, contents);
+      if (result.has_value ())
+	return result;
+    }
+
+  return result;
+}
+
 /* Called via an observer before gdb prints its prompt.
    Iterate over the extension languages giving them a chance to
    change the prompt.  The first one to change the prompt wins,
diff --git a/gdb/extension.h b/gdb/extension.h
index 5da0602e421..ca3fc14bd0f 100644
--- a/gdb/extension.h
+++ b/gdb/extension.h
@@ -22,6 +22,7 @@
 
 #include "mi/mi-cmds.h" /* For PRINT_NO_VALUES, etc.  */
 #include "gdbsupport/array-view.h"
+#include "gdbsupport/gdb_optional.h"
 
 struct breakpoint;
 struct command_line;
@@ -309,4 +310,12 @@ extern void get_matching_xmethod_workers
   (struct type *type, const char *method_name,
    std::vector<xmethod_worker_up> *workers);
 
+/* Try to colorize some source code.  FILENAME is the name of the file
+   holding the code.  CONTENTS is the source code itself.  This will
+   either a colorized (using ANSI terminal escapes) version of the
+   source code, or an empty value if colorizing could not be done.  */
+
+extern gdb::optional<std::string> ext_lang_colorize
+  (const std::string &filename, const std::string &contents);
+
 #endif /* EXTENSION_H */
diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py
index 09d43b2a8ab..a1aac007923 100644
--- a/gdb/python/lib/gdb/__init__.py
+++ b/gdb/python/lib/gdb/__init__.py
@@ -210,3 +210,17 @@ def find_pc_line(pc):
     """find_pc_line (pc) -> Symtab_and_line.
 Return the gdb.Symtab_and_line object corresponding to the pc value."""
     return current_progspace().find_pc_line(pc)
+
+try:
+    from pygments import formatters, lexers, highlight
+    def colorize(filename, contents):
+        # Don't want any errors.
+        try:
+            lexer = lexers.get_lexer_for_filename(filename)
+            formatter = formatters.TerminalFormatter()
+            return highlight(contents, lexer, formatter)
+        except:
+            return None
+except:
+    def colorize(filename, contents):
+        return None
diff --git a/gdb/python/python.c b/gdb/python/python.c
index bf214fae6e2..3231acaab08 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -149,6 +149,8 @@ static void gdbpy_set_quit_flag (const struct extension_language_defn *);
 static int gdbpy_check_quit_flag (const struct extension_language_defn *);
 static enum ext_lang_rc gdbpy_before_prompt_hook
   (const struct extension_language_defn *, const char *current_gdb_prompt);
+static gdb::optional<std::string> gdbpy_colorize
+  (const std::string &filename, const std::string &contents);
 
 /* The interface between gdb proper and loading of python scripts.  */
 
@@ -188,6 +190,8 @@ const struct extension_language_ops python_extension_ops =
   gdbpy_before_prompt_hook,
 
   gdbpy_get_matching_xmethod_workers,
+
+  gdbpy_colorize,
 };
 
 /* Architecture and language to be used in callbacks from
@@ -1095,6 +1099,74 @@ gdbpy_before_prompt_hook (const struct extension_language_defn *extlang,
   return EXT_LANG_RC_NOP;
 }
 
+/* This is the extension_language_ops.colorize "method".  */
+
+static gdb::optional<std::string>
+gdbpy_colorize (const std::string &filename, const std::string &contents)
+{
+  if (!gdb_python_initialized)
+    return {};
+
+  gdbpy_enter enter_py (get_current_arch (), current_language);
+
+  if (gdb_python_module == nullptr
+      || !PyObject_HasAttrString (gdb_python_module, "colorize"))
+    return {};
+
+  gdbpy_ref<> hook (PyObject_GetAttrString (gdb_python_module, "colorize"));
+  if (hook == nullptr)
+    {
+      gdbpy_print_stack ();
+      return {};
+    }
+
+  if (!PyCallable_Check (hook.get ()))
+    return {};
+
+  gdbpy_ref<> fname_arg (PyString_FromString (filename.c_str ()));
+  if (fname_arg == nullptr)
+    {
+      gdbpy_print_stack ();
+      return {};
+    }
+  gdbpy_ref<> contents_arg (PyString_FromString (contents.c_str ()));
+  if (contents_arg == nullptr)
+    {
+      gdbpy_print_stack ();
+      return {};
+    }
+
+  gdbpy_ref<> result (PyObject_CallFunctionObjArgs (hook.get (),
+						    fname_arg.get (),
+						    contents_arg.get (),
+						    nullptr));
+  if (result == nullptr)
+    {
+      gdbpy_print_stack ();
+      return {};
+    }
+
+  if (!gdbpy_is_string (result.get ()))
+    return {};
+
+  gdbpy_ref<> unic = python_string_to_unicode (result.get ());
+  if (unic == nullptr)
+    {
+      gdbpy_print_stack ();
+      return {};
+    }
+  gdbpy_ref<> host_str (PyUnicode_AsEncodedString (unic.get (),
+						   host_charset (),
+						   nullptr));
+  if (host_str == nullptr)
+    {
+      gdbpy_print_stack ();
+      return {};
+    }
+
+  return std::string (PyBytes_AsString (host_str.get ()));
+}
+
 
 
 /* Printing.  */
diff --git a/gdb/source-cache.c b/gdb/source-cache.c
index 965dc380b14..b21741fedda 100644
--- a/gdb/source-cache.c
+++ b/gdb/source-cache.c
@@ -178,9 +178,10 @@ source_cache::ensure (struct symtab *s)
 
   std::string contents = get_plain_source_lines (s, fullname);
 
-#ifdef HAVE_SOURCE_HIGHLIGHT
   if (source_styling && gdb_stdout->can_emit_style_escape ())
     {
+#ifdef HAVE_SOURCE_HIGHLIGHT
+      bool already_styled = false;
       const char *lang_name = get_language_name (SYMTAB_LANGUAGE (s));
       if (lang_name != nullptr)
 	{
@@ -202,6 +203,7 @@ source_cache::ensure (struct symtab *s)
 	      std::ostringstream output;
 	      highlighter->highlight (input, output, lang_name, fullname);
 	      contents = output.str ();
+	      already_styled = true;
 	    }
 	  catch (...)
 	    {
@@ -213,8 +215,16 @@ source_cache::ensure (struct symtab *s)
 		 un-highlighted text. */
 	    }
 	}
-    }
+
+      if (!already_styled)
 #endif /* HAVE_SOURCE_HIGHLIGHT */
+	{
+	  gdb::optional<std::string> ext_contents;
+	  ext_contents = ext_lang_colorize (fullname, contents);
+	  if (ext_contents.has_value ())
+	    contents = std::move (*ext_contents);
+	}
+    }
 
   source_text result = { std::move (fullname), std::move (contents) };
   m_source_map.push_back (std::move (result));
