Allow use of Pygments to colorize source code
authorTom Tromey <tromey@adacore.com>
Fri, 3 Jan 2020 20:59:27 +0000 (13:59 -0700)
committerTom Tromey <tromey@adacore.com>
Tue, 21 Jan 2020 19:39:17 +0000 (12:39 -0700)
While GNU Source Highlight is good, it's also difficult to build and
distribute.  For one thing, it needs Boost.  For another, it has an
unusual configuration and installation setup.

Pygments, a Python library, doesn't suffer from these issues, and so I
thought it would be a reasonable fallback.

This patch implements this idea.  GNU Source Highlight is preferred,
but if it is unavailable (or fails), the extension languages are
tried.  This patch also implements support for Pygments.

Something similar could be done for Guile, using:

    https://dthompson.us/projects/guile-syntax-highlight.html

However, I don't know enough about Guile internals to make this
happen, so I have not done it here.

gdb/ChangeLog
2020-01-21  Tom Tromey  <tromey@adacore.com>

* source-cache.c (source_cache::ensure): Call ext_lang_colorize.
* python/python.c (python_extension_ops): Update.
(gdbpy_colorize): New function.
* python/lib/gdb/__init__.py (colorize): New function.
* extension.h (ext_lang_colorize): Declare.
* extension.c (ext_lang_colorize): New function.
* extension-priv.h (struct extension_language_ops) <colorize>: New
member.
* cli/cli-style.c (_initialize_cli_style): Update help text.

Change-Id: I5e21623ee05f1f66baaa6deaeca78b578c031bf4

gdb/ChangeLog
gdb/cli/cli-style.c
gdb/extension-priv.h
gdb/extension.c
gdb/extension.h
gdb/python/lib/gdb/__init__.py
gdb/python/python.c
gdb/source-cache.c

index 0161fbf6849c6c52bdd91b3c279ec2327508535d..798fe5b7c4942661ae17ac492e7acb9dd5e0c234 100644 (file)
@@ -1,3 +1,15 @@
+2020-01-21  Tom Tromey  <tromey@adacore.com>
+
+       * source-cache.c (source_cache::ensure): Call ext_lang_colorize.
+       * python/python.c (python_extension_ops): Update.
+       (gdbpy_colorize): New function.
+       * python/lib/gdb/__init__.py (colorize): New function.
+       * extension.h (ext_lang_colorize): Declare.
+       * extension.c (ext_lang_colorize): New function.
+       * extension-priv.h (struct extension_language_ops) <colorize>: New
+       member.
+       * cli/cli-style.c (_initialize_cli_style): Update help text.
+
 2020-01-21  Luis Machado  <luis.machado@linaro.org>
 
        * aarch64-tdep.c (struct aarch64_displaced_step_closure)
index 917a7f6c8fe5fe743be5088034f1931100e6588a..d2d9928acd5c1f01744f404e067fd282806f4c6b 100644 (file)
@@ -341,8 +341,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);
index 2af2dede6939690162693c2279541703371823f0..c35671013d9ca146b3f24660baf06bf91f626111 100644 (file)
@@ -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.  */
index 947e440c12c85e7637e5eccfa458726537d517db..e2efe0b0d836fe12aeb7aa115110d9a33930ba02 100644 (file)
@@ -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,
index 5da0602e421c2da6089b9ff9fae72c9074ba0d27..ca3fc14bd0fb3a3a89b4fea2e6385a374f93d497 100644 (file)
@@ -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 */
index 09d43b2a8aba1e9d0d49408036ab426a65d9e162..a1aac0079238949cd0470212b210747cb1115158 100644 (file)
@@ -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
index d6f7f99c457300cba8e6dbe590ac15b4b2e2b110..f75c7b170630e58063f7956ad08bae584a0f039b 100644 (file)
@@ -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
@@ -1104,6 +1108,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 ()));
+}
+
 \f
 
 /* Printing.  */
index d546ae59524854964ad224f0862523ce5decf4b5..71277ecc9b3c658d494ac8d0693e2c2a34c0a007 100644 (file)
@@ -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));