teemperor updated this revision to Diff 157054.
teemperor added a comment.

[Updated patch to address Pavel's comments, thanks!]

@zturner So I looked into the Windows support:

Windows requires us to directly flush/signal/write/flush to the console output 
stream. However lldb's output is buffered by design into a StreamString first 
to prevent overlapping with the process output. So we can't just add Windows 
support to the coloring backend as we don't have direct access to the output 
stream.

If we want to fix this in lldb, then we could write an ANSI color code 
interpreter and let that run over our final output while we print it. That 
wouldn't be too complex and would fix all existing coloring output in lldb. It 
would also fix that lldb configs that enable color support on Windows are 
broken (because people just add color codes there).

However, it seems Windows starting with https://reviews.llvm.org/W10 anyway 
supports ANSI color codes (or at least you can enable them in the settings). So 
that interpreter is only really necessary until everyone moved to a version 
that supports color codes: 
https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#span-idtextformattingspanspan-idtextformattingspanspan-idtextformattingspantext-formatting

So I would suggest we maybe just hack in the color interpreter and drop it when 
https://reviews.llvm.org/W7 reaches EoL (?). I can make another patch for that 
if it sounds good.


https://reviews.llvm.org/D49334

Files:
  include/lldb/Core/Debugger.h
  include/lldb/Core/Highlighter.h
  include/lldb/Target/Language.h
  packages/Python/lldbsuite/test/source-manager/TestSourceManager.py
  source/Core/CMakeLists.txt
  source/Core/Debugger.cpp
  source/Core/Highlighter.cpp
  source/Core/SourceManager.cpp
  source/Plugins/Language/CMakeLists.txt
  source/Plugins/Language/CPlusPlus/CMakeLists.txt
  source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
  source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h
  source/Plugins/Language/ClangCommon/CMakeLists.txt
  source/Plugins/Language/ClangCommon/ClangHighlighter.cpp
  source/Plugins/Language/ClangCommon/ClangHighlighter.h
  source/Plugins/Language/Go/GoLanguage.cpp
  source/Plugins/Language/Go/GoLanguage.h
  source/Plugins/Language/Java/JavaLanguage.cpp
  source/Plugins/Language/Java/JavaLanguage.h
  source/Plugins/Language/OCaml/OCamlLanguage.cpp
  source/Plugins/Language/OCaml/OCamlLanguage.h
  source/Plugins/Language/ObjC/CMakeLists.txt
  source/Plugins/Language/ObjC/ObjCLanguage.cpp
  source/Plugins/Language/ObjC/ObjCLanguage.h
  source/Plugins/Language/ObjCPlusPlus/CMakeLists.txt
  source/Plugins/Language/ObjCPlusPlus/ObjCPlusPlusLanguage.cpp
  source/Plugins/Language/ObjCPlusPlus/ObjCPlusPlusLanguage.h
  source/Target/Language.cpp
  unittests/Language/CMakeLists.txt
  unittests/Language/Highlighting/CMakeLists.txt
  unittests/Language/Highlighting/HighlighterTest.cpp

Index: unittests/Language/Highlighting/HighlighterTest.cpp
===================================================================
--- /dev/null
+++ unittests/Language/Highlighting/HighlighterTest.cpp
@@ -0,0 +1,228 @@
+//===-- HighlighterTest.cpp -------------------------------------*- C++ -*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "gtest/gtest.h"
+
+#include "lldb/Core/Highlighter.h"
+
+#include "llvm/Support/Threading.h"
+
+#include "Plugins/Language/CPlusPlus/CPlusPlusLanguage.h"
+#include "Plugins/Language/Go/GoLanguage.h"
+#include "Plugins/Language/Java/JavaLanguage.h"
+#include "Plugins/Language/OCaml/OCamlLanguage.h"
+#include "Plugins/Language/ObjC/ObjCLanguage.h"
+#include "Plugins/Language/ObjCPlusPlus/ObjCPlusPlusLanguage.h"
+
+using namespace lldb_private;
+
+static void InitLanguages() {
+  static llvm::once_flag g_once_flag;
+  llvm::call_once(g_once_flag, []() {
+    // The HighlighterManager uses the language plugins under the hood, so we
+    // have to initialize them here for our test process.
+    CPlusPlusLanguage::Initialize();
+    GoLanguage::Initialize();
+    JavaLanguage::Initialize();
+    ObjCLanguage::Initialize();
+    ObjCPlusPlusLanguage::Initialize();
+    OCamlLanguage::Initialize();
+  });
+}
+
+static std::string getName(lldb::LanguageType type) {
+  HighlighterManager mgr;
+  return mgr.getHighlighterFor(type, "").GetName().str();
+}
+
+static std::string getName(llvm::StringRef path) {
+  HighlighterManager mgr;
+  return mgr.getHighlighterFor(lldb::eLanguageTypeUnknown, path)
+      .GetName()
+      .str();
+}
+
+TEST(HighlighterTest, HighlighterSelectionType) {
+  InitLanguages();
+  EXPECT_EQ(getName(lldb::eLanguageTypeC_plus_plus), "clang");
+  EXPECT_EQ(getName(lldb::eLanguageTypeC_plus_plus_03), "clang");
+  EXPECT_EQ(getName(lldb::eLanguageTypeC_plus_plus_11), "clang");
+  EXPECT_EQ(getName(lldb::eLanguageTypeC_plus_plus_14), "clang");
+  EXPECT_EQ(getName(lldb::eLanguageTypeObjC), "clang");
+  EXPECT_EQ(getName(lldb::eLanguageTypeObjC_plus_plus), "clang");
+
+  EXPECT_EQ(getName(lldb::eLanguageTypeUnknown), "none");
+  EXPECT_EQ(getName(lldb::eLanguageTypeJulia), "none");
+  EXPECT_EQ(getName(lldb::eLanguageTypeJava), "none");
+  EXPECT_EQ(getName(lldb::eLanguageTypeHaskell), "none");
+}
+
+TEST(HighlighterTest, HighlighterSelectionPath) {
+  InitLanguages();
+  EXPECT_EQ(getName("myfile.cc"), "clang");
+  EXPECT_EQ(getName("moo.cpp"), "clang");
+  EXPECT_EQ(getName("mar.cxx"), "clang");
+  EXPECT_EQ(getName("foo.C"), "clang");
+  EXPECT_EQ(getName("bar.CC"), "clang");
+  EXPECT_EQ(getName("a/dir.CC"), "clang");
+  EXPECT_EQ(getName("/a/dir.hpp"), "clang");
+  EXPECT_EQ(getName("header.h"), "clang");
+
+  EXPECT_EQ(getName(""), "none");
+  EXPECT_EQ(getName("/dev/null"), "none");
+  EXPECT_EQ(getName("Factory.java"), "none");
+  EXPECT_EQ(getName("poll.py"), "none");
+  EXPECT_EQ(getName("reducer.hs"), "none");
+}
+
+TEST(HighlighterTest, FallbackHighlighter) {
+  InitLanguages();
+  HighlighterManager mgr;
+  const Highlighter &h =
+      mgr.getHighlighterFor(lldb::eLanguageTypePascal83, "foo.pas");
+
+  HighlightStyle style;
+  style.identifier.Set("[", "]");
+  style.semicolons.Set("<", ">");
+
+  const char *code = "program Hello;";
+  std::string output = h.Highlight(style, code);
+
+  EXPECT_STREQ(output.c_str(), code);
+}
+
+TEST(HighlighterTest, DefaultHighlighter) {
+  InitLanguages();
+  HighlighterManager mgr;
+  const Highlighter &h = mgr.getHighlighterFor(lldb::eLanguageTypeC, "main.c");
+
+  HighlightStyle style;
+
+  const char *code = "int my_main() { return 22; } \n";
+  std::string output = h.Highlight(style, code);
+
+  EXPECT_STREQ(output.c_str(), code);
+}
+
+//------------------------------------------------------------------------------
+// Tests highlighting with the Clang highlighter.
+//------------------------------------------------------------------------------
+
+static std::string highlightC(llvm::StringRef code, HighlightStyle style) {
+  InitLanguages();
+  HighlighterManager mgr;
+  const Highlighter &h = mgr.getHighlighterFor(lldb::eLanguageTypeC, "main.c");
+  return h.Highlight(style, code);
+}
+
+TEST(HighlighterTest, ClangEmptyInput) {
+  HighlightStyle s;
+
+  std::string output = highlightC("", s);
+  EXPECT_STREQ("", output.c_str());
+}
+
+TEST(HighlighterTest, ClangScalarLiterals) {
+  HighlightStyle s;
+  s.scalar_literal.Set("<scalar>", "</scalar>");
+
+  std::string output = highlightC(" int i = 22;", s);
+  EXPECT_STREQ(" int i = <scalar>22</scalar>;", output.c_str());
+}
+
+TEST(HighlighterTest, ClangStringLiterals) {
+  HighlightStyle s;
+  s.string_literal.Set("<str>", "</str>");
+
+  std::string output = highlightC("const char *f = 22 + \"foo\";", s);
+  EXPECT_STREQ("const char *f = 22 + <str>\"foo\"</str>;", output.c_str());
+}
+
+TEST(HighlighterTest, ClangUnterminatedString) {
+  HighlightStyle s;
+  s.string_literal.Set("<str>", "</str>");
+
+  std::string output = highlightC(" f = \"", s);
+  EXPECT_STREQ(" f = \"", output.c_str());
+}
+
+TEST(HighlighterTest, Keywords) {
+  HighlightStyle s;
+  s.keyword.Set("<k>", "</k>");
+
+  std::string output = highlightC(" return 1; ", s);
+  EXPECT_STREQ(" <k>return</k> 1; ", output.c_str());
+}
+
+TEST(HighlighterTest, Colons) {
+  HighlightStyle s;
+  s.colon.Set("<c>", "</c>");
+
+  std::string output = highlightC("foo::bar:", s);
+  EXPECT_STREQ("foo<c>:</c><c>:</c>bar<c>:</c>", output.c_str());
+}
+
+TEST(HighlighterTest, ClangBraces) {
+  HighlightStyle s;
+  s.braces.Set("<b>", "</b>");
+
+  std::string output = highlightC("a{}", s);
+  EXPECT_STREQ("a<b>{</b><b>}</b>", output.c_str());
+}
+
+TEST(HighlighterTest, ClangSquareBrackets) {
+  HighlightStyle s;
+  s.square_brackets.Set("<sb>", "</sb>");
+
+  std::string output = highlightC("a[]", s);
+  EXPECT_STREQ("a<sb>[</sb><sb>]</sb>", output.c_str());
+}
+
+TEST(HighlighterTest, ClangCommas) {
+  HighlightStyle s;
+  s.comma.Set("<comma>", "</comma>");
+
+  std::string output = highlightC(" bool f = foo(), 1;", s);
+  EXPECT_STREQ(" bool f = foo()<comma>,</comma> 1;", output.c_str());
+}
+
+TEST(HighlighterTest, ClangPPDirectives) {
+  HighlightStyle s;
+  s.pp_directive.Set("<pp>", "</pp>");
+
+  std::string output = highlightC(" #include \"foo\" // comment\n", s);
+  EXPECT_STREQ(" <pp>#</pp><pp>include</pp><pp> </pp><pp>\"foo\"</pp><pp> </pp>"
+               "// comment\n",
+               output.c_str());
+}
+
+TEST(HighlighterTest, ClangComments) {
+  HighlightStyle s;
+  s.comment.Set("<cc>", "</cc>");
+
+  std::string output = highlightC(" /*com */ // com /*n*/", s);
+  EXPECT_STREQ(" <cc>/*com */</cc> <cc>// com /*n*/</cc>", output.c_str());
+}
+
+TEST(HighlighterTest, ClangOperators) {
+  HighlightStyle s;
+  s.operators.Set("[", "]");
+
+  std::string output = highlightC(" 1+2/a*f&x|~l", s);
+  EXPECT_STREQ(" 1[+]2[/]a[*]f[&]x[|][~]l", output.c_str());
+}
+
+TEST(HighlighterTest, ClangIdentifiers) {
+  HighlightStyle s;
+  s.identifier.Set("<id>", "</id>");
+
+  std::string output = highlightC(" foo c = bar(); return 1;", s);
+  EXPECT_STREQ(" <id>foo</id> <id>c</id> = <id>bar</id>(); return 1;",
+               output.c_str());
+}
Index: unittests/Language/Highlighting/CMakeLists.txt
===================================================================
--- /dev/null
+++ unittests/Language/Highlighting/CMakeLists.txt
@@ -0,0 +1,11 @@
+add_lldb_unittest(HighlighterTests
+  HighlighterTest.cpp
+
+  LINK_LIBS
+    lldbPluginCPlusPlusLanguage
+    lldbPluginObjCLanguage
+    lldbPluginObjCPlusPlusLanguage
+    lldbPluginJavaLanguage
+    lldbPluginOCamlLanguage
+    lldbPluginGoLanguage
+  )
Index: unittests/Language/CMakeLists.txt
===================================================================
--- unittests/Language/CMakeLists.txt
+++ unittests/Language/CMakeLists.txt
@@ -1 +1,2 @@
 add_subdirectory(CPlusPlus)
+add_subdirectory(Highlighting)
Index: source/Target/Language.cpp
===================================================================
--- source/Target/Language.cpp
+++ source/Target/Language.cpp
@@ -77,7 +77,39 @@
   return nullptr;
 }
 
+Language *Language::FindPlugin(llvm::StringRef file_path) {
+  Language *result = nullptr;
+  ForEach([&result, file_path](Language *language) {
+    if (language->IsSourceFile(file_path)) {
+      result = language;
+      return false;
+    }
+    return true;
+  });
+  return result;
+}
+
+Language *Language::FindPlugin(LanguageType language,
+                               llvm::StringRef file_path) {
+  Language *result = FindPlugin(language);
+  // Finding a language by file path is slower, we so we use this as the
+  // fallback.
+  if (!result)
+    result = FindPlugin(file_path);
+  return result;
+}
+
 void Language::ForEach(std::function<bool(Language *)> callback) {
+  // If we want to iterate over all languages, we first have to complete the
+  // LanguagesMap.
+  static llvm::once_flag g_initialize;
+  llvm::call_once(g_initialize, [] {
+    for (unsigned lang = eLanguageTypeUnknown; lang < eNumLanguageTypes;
+         ++lang) {
+      FindPlugin(static_cast<lldb::LanguageType>(lang));
+    }
+  });
+
   std::lock_guard<std::mutex> guard(GetLanguagesMutex());
   LanguagesMap &map(GetLanguagesMap());
   for (const auto &entry : map) {
Index: source/Plugins/Language/ObjCPlusPlus/ObjCPlusPlusLanguage.h
===================================================================
--- source/Plugins/Language/ObjCPlusPlus/ObjCPlusPlusLanguage.h
+++ source/Plugins/Language/ObjCPlusPlus/ObjCPlusPlusLanguage.h
@@ -14,12 +14,15 @@
 // C++ Includes
 // Other libraries and framework includes
 // Project includes
+#include "Plugins/Language/ClangCommon/ClangHighlighter.h"
 #include "lldb/Target/Language.h"
 #include "lldb/lldb-private.h"
 
 namespace lldb_private {
 
 class ObjCPlusPlusLanguage : public Language {
+  ClangHighlighter m_highlighter;
+
 public:
   ObjCPlusPlusLanguage() = default;
 
@@ -29,6 +32,10 @@
     return lldb::eLanguageTypeObjC_plus_plus;
   }
 
+  bool IsSourceFile(llvm::StringRef file_path) const override;
+
+  const Highlighter *GetHighlighter() const override { return &m_highlighter; }
+
   //------------------------------------------------------------------
   // Static Functions
   //------------------------------------------------------------------
Index: source/Plugins/Language/ObjCPlusPlus/ObjCPlusPlusLanguage.cpp
===================================================================
--- source/Plugins/Language/ObjCPlusPlus/ObjCPlusPlusLanguage.cpp
+++ source/Plugins/Language/ObjCPlusPlus/ObjCPlusPlusLanguage.cpp
@@ -16,6 +16,15 @@
 using namespace lldb;
 using namespace lldb_private;
 
+bool ObjCPlusPlusLanguage::IsSourceFile(llvm::StringRef file_path) const {
+  const auto suffixes = {".h", ".mm"};
+  for (auto suffix : suffixes) {
+    if (file_path.endswith_lower(suffix))
+      return true;
+  }
+  return false;
+}
+
 void ObjCPlusPlusLanguage::Initialize() {
   PluginManager::RegisterPlugin(GetPluginNameStatic(), "Objective-C++ Language",
                                 CreateInstance);
Index: source/Plugins/Language/ObjCPlusPlus/CMakeLists.txt
===================================================================
--- source/Plugins/Language/ObjCPlusPlus/CMakeLists.txt
+++ source/Plugins/Language/ObjCPlusPlus/CMakeLists.txt
@@ -1,7 +1,8 @@
 add_lldb_library(lldbPluginObjCPlusPlusLanguage PLUGIN
   ObjCPlusPlusLanguage.cpp
-  
+
   LINK_LIBS
     lldbCore
     lldbTarget
+    lldbPluginClangCommon
 )
Index: source/Plugins/Language/ObjC/ObjCLanguage.h
===================================================================
--- source/Plugins/Language/ObjC/ObjCLanguage.h
+++ source/Plugins/Language/ObjC/ObjCLanguage.h
@@ -17,13 +17,16 @@
 
 // Other libraries and framework includes
 // Project includes
+#include "Plugins/Language/ClangCommon/ClangHighlighter.h"
 #include "lldb/Target/Language.h"
 #include "lldb/Utility/ConstString.h"
 #include "lldb/lldb-private.h"
 
 namespace lldb_private {
 
 class ObjCLanguage : public Language {
+  ClangHighlighter m_highlighter;
+
 public:
   class MethodName {
   public:
@@ -121,6 +124,10 @@
 
   bool IsNilReference(ValueObject &valobj) override;
 
+  bool IsSourceFile(llvm::StringRef file_path) const override;
+
+  const Highlighter *GetHighlighter() const override { return &m_highlighter; }
+
   //------------------------------------------------------------------
   // Static Functions
   //------------------------------------------------------------------
Index: source/Plugins/Language/ObjC/ObjCLanguage.cpp
===================================================================
--- source/Plugins/Language/ObjC/ObjCLanguage.cpp
+++ source/Plugins/Language/ObjC/ObjCLanguage.cpp
@@ -1102,3 +1102,12 @@
   bool isZero = valobj.GetValueAsUnsigned(0, &canReadValue) == 0;
   return canReadValue && isZero;
 }
+
+bool ObjCLanguage::IsSourceFile(llvm::StringRef file_path) const {
+  const auto suffixes = {".h", ".m", ".M"};
+  for (auto suffix : suffixes) {
+    if (file_path.endswith_lower(suffix))
+      return true;
+  }
+  return false;
+}
Index: source/Plugins/Language/ObjC/CMakeLists.txt
===================================================================
--- source/Plugins/Language/ObjC/CMakeLists.txt
+++ source/Plugins/Language/ObjC/CMakeLists.txt
@@ -31,6 +31,7 @@
     lldbTarget
     lldbUtility
     lldbPluginAppleObjCRuntime
+    lldbPluginClangCommon
 
   EXTRA_CXXFLAGS ${EXTRA_CXXFLAGS}
 )
Index: source/Plugins/Language/OCaml/OCamlLanguage.h
===================================================================
--- source/Plugins/Language/OCaml/OCamlLanguage.h
+++ source/Plugins/Language/OCaml/OCamlLanguage.h
@@ -31,6 +31,8 @@
     return lldb::eLanguageTypeOCaml;
   }
 
+  bool IsSourceFile(llvm::StringRef file_path) const override;
+
   static void Initialize();
 
   static void Terminate();
Index: source/Plugins/Language/OCaml/OCamlLanguage.cpp
===================================================================
--- source/Plugins/Language/OCaml/OCamlLanguage.cpp
+++ source/Plugins/Language/OCaml/OCamlLanguage.cpp
@@ -28,6 +28,15 @@
 using namespace lldb;
 using namespace lldb_private;
 
+bool OCamlLanguage::IsSourceFile(llvm::StringRef file_path) const {
+  const auto suffixes = {".ml", ".mli"};
+  for (auto suffix : suffixes) {
+    if (file_path.endswith_lower(suffix))
+      return true;
+  }
+  return false;
+}
+
 void OCamlLanguage::Initialize() {
   PluginManager::RegisterPlugin(GetPluginNameStatic(), "OCaml Language",
                                 CreateInstance);
Index: source/Plugins/Language/Java/JavaLanguage.h
===================================================================
--- source/Plugins/Language/Java/JavaLanguage.h
+++ source/Plugins/Language/Java/JavaLanguage.h
@@ -45,6 +45,8 @@
   bool IsNilReference(ValueObject &valobj) override;
 
   lldb::TypeCategoryImplSP GetFormatters() override;
+
+  bool IsSourceFile(llvm::StringRef file_path) const override;
 };
 
 } // namespace lldb_private
Index: source/Plugins/Language/Java/JavaLanguage.cpp
===================================================================
--- source/Plugins/Language/Java/JavaLanguage.cpp
+++ source/Plugins/Language/Java/JavaLanguage.cpp
@@ -99,3 +99,7 @@
   });
   return g_category;
 }
+
+bool JavaLanguage::IsSourceFile(llvm::StringRef file_path) const {
+  return file_path.endswith(".java");
+}
Index: source/Plugins/Language/Go/GoLanguage.h
===================================================================
--- source/Plugins/Language/Go/GoLanguage.h
+++ source/Plugins/Language/Go/GoLanguage.h
@@ -39,6 +39,8 @@
   HardcodedFormatters::HardcodedSyntheticFinder
   GetHardcodedSynthetics() override;
 
+  bool IsSourceFile(llvm::StringRef file_path) const override;
+
   //------------------------------------------------------------------
   // Static Functions
   //------------------------------------------------------------------
Index: source/Plugins/Language/Go/GoLanguage.cpp
===================================================================
--- source/Plugins/Language/Go/GoLanguage.cpp
+++ source/Plugins/Language/Go/GoLanguage.cpp
@@ -125,3 +125,7 @@
 
   return g_formatters;
 }
+
+bool GoLanguage::IsSourceFile(llvm::StringRef file_path) const {
+  return file_path.endswith(".go");
+}
Index: source/Plugins/Language/ClangCommon/ClangHighlighter.h
===================================================================
--- /dev/null
+++ source/Plugins/Language/ClangCommon/ClangHighlighter.h
@@ -0,0 +1,42 @@
+//===-- ClangHighlighter.h --------------------------------------*- C++ -*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef liblldb_ClangHighlighter_h_
+#define liblldb_ClangHighlighter_h_
+
+// C Includes
+// C++ Includes
+// Other libraries and framework includes
+#include "lldb/Utility/StreamString.h"
+#include "llvm/ADT/StringSet.h"
+
+// Project includes
+#include "lldb/Core/Highlighter.h"
+
+namespace lldb_private {
+
+class ClangHighlighter : public Highlighter {
+  llvm::StringSet<> keywords;
+
+public:
+  ClangHighlighter();
+  llvm::StringRef GetName() const override { return "clang"; }
+
+  std::size_t Highlight(const HighlightStyle &options, llvm::StringRef line,
+                        llvm::StringRef previous_lines,
+                        Stream &s) const override;
+
+  /// Returns true if the given string represents a keywords in any Clang
+  /// supported language.
+  bool isKeyword(llvm::StringRef token) const;
+};
+
+} // namespace lldb_private
+
+#endif // liblldb_ClangHighlighter_h_
Index: source/Plugins/Language/ClangCommon/ClangHighlighter.cpp
===================================================================
--- /dev/null
+++ source/Plugins/Language/ClangCommon/ClangHighlighter.cpp
@@ -0,0 +1,230 @@
+//===-- ClangHighlighter.cpp ------------------------------------*- C++ -*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "ClangHighlighter.h"
+
+#include "lldb/Target/Language.h"
+#include "lldb/Utility/AnsiTerminal.h"
+#include "lldb/Utility/StreamString.h"
+
+#include "clang/Basic/SourceManager.h"
+#include "clang/Lex/Lexer.h"
+#include "llvm/ADT/StringSet.h"
+#include "llvm/Support/MemoryBuffer.h"
+
+#include <unordered_set>
+
+using namespace lldb_private;
+
+bool ClangHighlighter::isKeyword(llvm::StringRef token) const {
+  return keywords.find(token) != keywords.end();
+}
+
+ClangHighlighter::ClangHighlighter() {
+#define KEYWORD(X, N) keywords.insert(#X);
+#include "clang/Basic/TokenKinds.def"
+}
+
+/// Determines which style should be applied to the given token.
+/// \param highlighter
+///     The current highlighter that should use the style.
+/// \param token
+///     The current token.
+/// \param tok_str
+///     The string in the source code the token represents.
+/// \param options
+///     The style we use for coloring the source code.
+/// \param in_pp_directive
+///     If we are currently in a preprocessor directive. NOTE: This is
+///     passed by reference and will be updated if the current token starts
+///     or ends a preprocessor directive.
+/// \return
+///     The ColorStyle that should be applied to the token.
+static HighlightStyle::ColorStyle
+determineClangStyle(const ClangHighlighter &highlighter,
+                    const clang::Token &token, llvm::StringRef tok_str,
+                    const HighlightStyle &options, bool &in_pp_directive) {
+  using namespace clang;
+
+  if (token.is(tok::comment)) {
+    // If we were in a preprocessor directive before, we now left it.
+    in_pp_directive = false;
+    return options.comment;
+  } else if (in_pp_directive || token.getKind() == tok::hash) {
+    // Let's assume that the rest of the line is a PP directive.
+    in_pp_directive = true;
+    // Preprocessor directives are hard to match, so we have to hack this in.
+    return options.pp_directive;
+  } else if (tok::isStringLiteral(token.getKind()))
+    return options.string_literal;
+  else if (tok::isLiteral(token.getKind()))
+    return options.scalar_literal;
+  else if (highlighter.isKeyword(tok_str))
+    return options.keyword;
+  else
+    switch (token.getKind()) {
+    case tok::raw_identifier:
+    case tok::identifier:
+      return options.identifier;
+    case tok::l_brace:
+    case tok::r_brace:
+      return options.braces;
+    case tok::l_square:
+    case tok::r_square:
+      return options.square_brackets;
+    case tok::l_paren:
+    case tok::r_paren:
+      return options.parentheses;
+    case tok::comma:
+      return options.comma;
+    case tok::coloncolon:
+    case tok::colon:
+      return options.colon;
+
+    case tok::amp:
+    case tok::ampamp:
+    case tok::ampequal:
+    case tok::star:
+    case tok::starequal:
+    case tok::plus:
+    case tok::plusplus:
+    case tok::plusequal:
+    case tok::minus:
+    case tok::arrow:
+    case tok::minusminus:
+    case tok::minusequal:
+    case tok::tilde:
+    case tok::exclaim:
+    case tok::exclaimequal:
+    case tok::slash:
+    case tok::slashequal:
+    case tok::percent:
+    case tok::percentequal:
+    case tok::less:
+    case tok::lessless:
+    case tok::lessequal:
+    case tok::lesslessequal:
+    case tok::spaceship:
+    case tok::greater:
+    case tok::greatergreater:
+    case tok::greaterequal:
+    case tok::greatergreaterequal:
+    case tok::caret:
+    case tok::caretequal:
+    case tok::pipe:
+    case tok::pipepipe:
+    case tok::pipeequal:
+    case tok::question:
+    case tok::equal:
+    case tok::equalequal:
+      return options.operators;
+    default:
+      break;
+    }
+  return HighlightStyle::ColorStyle();
+}
+
+std::size_t ClangHighlighter::Highlight(const HighlightStyle &options,
+                                        llvm::StringRef line,
+                                        llvm::StringRef previous_lines,
+                                        Stream &result) const {
+  using namespace clang;
+
+  std::size_t written_bytes = 0;
+
+  FileSystemOptions file_opts;
+  FileManager file_mgr(file_opts);
+
+  unsigned line_number = previous_lines.count('\n') + 1U;
+
+  // Let's build the actual source code Clang needs and setup some utility
+  // objects.
+  std::string full_source = previous_lines.str() + line.str();
+  llvm::IntrusiveRefCntPtr<DiagnosticIDs> diag_ids(new DiagnosticIDs());
+  llvm::IntrusiveRefCntPtr<DiagnosticOptions> diags_opts(
+      new DiagnosticOptions());
+  DiagnosticsEngine diags(diag_ids, diags_opts);
+  clang::SourceManager SM(diags, file_mgr);
+  std::unique_ptr<llvm::MemoryBuffer> buf =
+      llvm::MemoryBuffer::getMemBufferCopy(full_source);
+
+  FileID FID = SM.createFileID(clang::SourceManager::Unowned, buf.get());
+
+  // Let's just enable the latest ObjC and C++ which should get most tokens
+  // right.
+  LangOptions Opts;
+  Opts.ObjC2 = true;
+  Opts.CPlusPlus17 = true;
+  Opts.LineComment = true;
+
+  Lexer lex(FID, buf.get(), SM, Opts);
+  // The lexer should keep whitespace around.
+  lex.SetKeepWhitespaceMode(true);
+
+  // Keeps track if we have entered a PP directive.
+  bool in_pp_directive = false;
+
+  // True once we actually lexed the user provided line.
+  bool found_user_line = false;
+
+  Token token;
+  bool exit = false;
+  while (!exit) {
+    // Returns true if this is the last token we get from the lexer.
+    exit = lex.LexFromRawLexer(token);
+
+    bool invalid = false;
+    unsigned current_line_number =
+        SM.getSpellingLineNumber(token.getLocation(), &invalid);
+    if (current_line_number != line_number)
+      continue;
+    found_user_line = true;
+
+    // We don't need to print any tokens without a spelling line number.
+    if (invalid)
+      continue;
+
+    // Same as above but with the column number.
+    invalid = false;
+    unsigned start = SM.getSpellingColumnNumber(token.getLocation(), &invalid);
+    if (invalid)
+      continue;
+    // Column numbers start at 1, but indexes in our string start at 0.
+    --start;
+
+    // Annotations don't have a length, so let's skip them.
+    if (token.isAnnotation())
+      continue;
+
+    // Extract the token string from our source code.
+    llvm::StringRef tok_str = line.substr(start, token.getLength());
+
+    // If the token is just an empty string, we can skip all the work below.
+    if (tok_str.empty())
+      continue;
+
+    // See how we are supposed to highlight this token.
+    HighlightStyle::ColorStyle color =
+        determineClangStyle(*this, token, tok_str, options, in_pp_directive);
+
+    written_bytes += color.Apply(result, tok_str);
+  }
+
+  // If we went over the whole file but couldn't find our own file, then
+  // somehow our setup was wrong. When we're in release mode we just give the
+  // user the normal line and pretend we don't know how to highlight it. In
+  // debug mode we bail out with an assert as this should never happen.
+  if (!found_user_line) {
+    result << line;
+    written_bytes += line.size();
+    assert(false && "We couldn't find the user line in the input file?");
+  }
+
+  return written_bytes;
+}
Index: source/Plugins/Language/ClangCommon/CMakeLists.txt
===================================================================
--- /dev/null
+++ source/Plugins/Language/ClangCommon/CMakeLists.txt
@@ -0,0 +1,9 @@
+add_lldb_library(lldbPluginClangCommon PLUGIN
+  ClangHighlighter.cpp
+
+  LINK_LIBS
+    lldbCore
+    lldbUtility
+  LINK_COMPONENTS
+    Support
+)
Index: source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h
===================================================================
--- source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h
+++ source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h
@@ -19,13 +19,16 @@
 #include "llvm/ADT/StringRef.h"
 
 // Project includes
+#include "Plugins/Language/ClangCommon/ClangHighlighter.h"
 #include "lldb/Target/Language.h"
 #include "lldb/Utility/ConstString.h"
 #include "lldb/lldb-private.h"
 
 namespace lldb_private {
 
 class CPlusPlusLanguage : public Language {
+  ClangHighlighter m_highlighter;
+
 public:
   class MethodName {
   public:
@@ -90,6 +93,10 @@
   HardcodedFormatters::HardcodedSyntheticFinder
   GetHardcodedSynthetics() override;
 
+  bool IsSourceFile(llvm::StringRef file_path) const override;
+
+  const Highlighter *GetHighlighter() const override { return &m_highlighter; }
+
   //------------------------------------------------------------------
   // Static Functions
   //------------------------------------------------------------------
Index: source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
===================================================================
--- source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
+++ source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
@@ -1002,3 +1002,16 @@
 
   return g_formatters;
 }
+
+bool CPlusPlusLanguage::IsSourceFile(llvm::StringRef file_path) const {
+  const auto suffixes = {".cpp", ".cxx", ".c++", ".cc",  ".c",
+                         ".h",   ".hh",  ".hpp", ".hxx", ".h++"};
+  for (auto suffix : suffixes) {
+    if (file_path.endswith_lower(suffix))
+      return true;
+  }
+
+  // Check if we're in a STL path (where the files usually have no extension
+  // that we could check for.
+  return file_path.contains("/usr/include/c++/");
+}
Index: source/Plugins/Language/CPlusPlus/CMakeLists.txt
===================================================================
--- source/Plugins/Language/CPlusPlus/CMakeLists.txt
+++ source/Plugins/Language/CPlusPlus/CMakeLists.txt
@@ -24,6 +24,8 @@
     lldbSymbol
     lldbTarget
     lldbUtility
+    lldbPluginClangCommon
+
   LINK_COMPONENTS
     Support
 )
Index: source/Plugins/Language/CMakeLists.txt
===================================================================
--- source/Plugins/Language/CMakeLists.txt
+++ source/Plugins/Language/CMakeLists.txt
@@ -1,3 +1,4 @@
+add_subdirectory(ClangCommon)
 add_subdirectory(CPlusPlus)
 add_subdirectory(Go)
 add_subdirectory(Java)
Index: source/Core/SourceManager.cpp
===================================================================
--- source/Core/SourceManager.cpp
+++ source/Core/SourceManager.cpp
@@ -13,6 +13,7 @@
 #include "lldb/Core/AddressRange.h" // for AddressRange
 #include "lldb/Core/Debugger.h"
 #include "lldb/Core/FormatEntity.h" // for FormatEntity
+#include "lldb/Core/Highlighter.h"
 #include "lldb/Core/Module.h"
 #include "lldb/Core/ModuleList.h" // for ModuleList
 #include "lldb/Host/FileSystem.h"
@@ -27,6 +28,7 @@
 #include "lldb/Utility/DataBufferLLVM.h"
 #include "lldb/Utility/RegularExpression.h"
 #include "lldb/Utility/Stream.h"
+#include "lldb/Utility/StreamString.h"
 #include "lldb/lldb-enumerations.h" // for StopShowColumn::eStopSho...
 
 #include "llvm/ADT/Twine.h" // for Twine
@@ -103,6 +105,18 @@
   return file_sp;
 }
 
+static bool should_highlight_source(DebuggerSP debugger_sp) {
+  if (!debugger_sp)
+    return false;
+
+  // We don't use ANSI stop column formatting if the debugger doesn't think it
+  // should be using color.
+  if (!debugger_sp->GetUseColor())
+    return false;
+
+  return debugger_sp->GetHighlightSource();
+}
+
 static bool should_show_stop_column_with_ansi(DebuggerSP debugger_sp) {
   // We don't use ANSI stop column formatting if we can't lookup values from
   // the debugger.
@@ -114,6 +128,11 @@
   if (!debugger_sp->GetUseColor())
     return false;
 
+  // Don't use terminal attributes when we have highlighting enabled. This
+  // can mess up the command line.
+  if (debugger_sp->GetHighlightSource())
+    return false;
+
   // We only use ANSI stop column formatting if we're either supposed to show
   // ANSI where available (which we know we have when we get to this point), or
   // if we're only supposed to use ANSI.
@@ -515,6 +534,14 @@
   if (!m_data_sp)
     return 0;
 
+  std::string previous_content;
+
+  HighlightStyle style = HighlightStyle::MakeVimStyle();
+  HighlighterManager mgr;
+  std::string path = GetFileSpec().GetPath(/*denormalize*/ false);
+  auto &highlighter =
+      mgr.getHighlighterFor(lldb::LanguageType::eLanguageTypeUnknown, path);
+
   const uint32_t start_line =
       line <= context_before ? 1 : line - context_before;
   const uint32_t start_line_offset = GetLineOffset(start_line);
@@ -530,10 +557,19 @@
       size_t count = end_line_offset - start_line_offset;
       const uint8_t *cstr = m_data_sp->GetBytes() + start_line_offset;
 
+      auto ref = llvm::StringRef(reinterpret_cast<const char *>(cstr), count);
       bool displayed_line = false;
 
-      if (column && (column < count)) {
-        auto debugger_sp = m_debugger_wp.lock();
+      auto debugger_sp = m_debugger_wp.lock();
+      if (should_highlight_source(debugger_sp)) {
+        bytes_written +=
+            highlighter.Highlight(style, ref, previous_content, *s);
+        displayed_line = true;
+        // Add the new line to the previous lines.
+        previous_content += ref.str();
+      }
+
+      if (!displayed_line && column && (column < count)) {
         if (should_show_stop_column_with_ansi(debugger_sp) && debugger_sp) {
           // Check if we have any ANSI codes with which to mark this column. If
           // not, no need to do this work.
@@ -581,10 +617,10 @@
       // If we didn't end up displaying the line with ANSI codes for whatever
       // reason, display it now sans codes.
       if (!displayed_line)
-        bytes_written = s->Write(cstr, count);
+        bytes_written = s->PutCString(ref);
 
       // Ensure we get an end of line character one way or another.
-      if (!is_newline_char(cstr[count - 1]))
+      if (!is_newline_char(ref.back()))
         bytes_written += s->EOL();
     }
     return bytes_written;
Index: source/Core/Highlighter.cpp
===================================================================
--- /dev/null
+++ source/Core/Highlighter.cpp
@@ -0,0 +1,339 @@
+//===-- Highlighter.cpp -----------------------------------------*- C++ -*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Core/Highlighter.h"
+
+#include "lldb/Target/Language.h"
+#include "lldb/Utility/AnsiTerminal.h"
+#include "lldb/Utility/StreamString.h"
+
+#include "clang/Basic/SourceManager.h"
+#include "clang/Lex/Lexer.h"
+#include "llvm/ADT/StringSet.h"
+#include "llvm/Support/MemoryBuffer.h"
+
+#include <unordered_set>
+
+using namespace lldb_private;
+
+std::size_t HighlightStyle::ColorStyle::Apply(Stream &s,
+                                              llvm::StringRef value) const {
+  // If we have no prefix, skip the expensive call below.
+  if (!m_prefix.empty())
+    s << lldb_utility::ansi::FormatAnsiTerminalCodes(m_prefix);
+  s << value;
+  if (!m_suffix.empty())
+    s << lldb_utility::ansi::FormatAnsiTerminalCodes(m_suffix);
+  // Calculate how many bytes we have written.
+  return m_prefix.size() + value.size() + m_suffix.size();
+}
+
+std::size_t NoHighlighter::Highlight(const HighlightStyle &options,
+                                     llvm::StringRef line,
+                                     llvm::StringRef previous_lines,
+                                     Stream &s) const {
+  // We just forward the input to the output and do no highlighting.
+  s << line;
+  return line.size();
+}
+
+static HighlightStyle::ColorStyle GetColor(const char *c) {
+  return HighlightStyle::ColorStyle(c, "${ansi.normal}");
+}
+
+HighlightStyle HighlightStyle::MakeVimStyle() {
+  HighlightStyle result;
+  result.comment = GetColor("${ansi.fg.purple}");
+  result.scalar_literal = GetColor("${ansi.fg.red}");
+  result.keyword = GetColor("${ansi.fg.green}");
+  return result;
+}
+
+class ClangHighlighter : public Highlighter {
+  llvm::StringSet<> keywords;
+  /// Returns true if the given string represents a keywords in any Clang
+  /// supported language.
+  bool isKeyword(llvm::StringRef token) const;
+
+  /// Determines which style should be applied to the given token.
+  /// \param token
+  ///     The current token.
+  /// \param tok_str
+  ///     The string in the source code the token represents.
+  /// \param options
+  ///     The style we use for coloring the source code.
+  /// \param in_pp_directive
+  ///     If we are currently in a preprocessor directive. NOTE: This is
+  ///     passed by reference and will be updated if the current token starts
+  ///     or ends a preprocessor directive.
+  /// \return
+  ///     The ColorStyle that should be applied to the token.
+  HighlightStyle::ColorStyle determineClangStyle(const clang::Token &token,
+                                                 llvm::StringRef tok_str,
+                                                 const HighlightStyle &options,
+                                                 bool &in_pp_directive) const;
+
+public:
+  ClangHighlighter();
+  llvm::StringRef GetName() const override { return "clang"; }
+
+  std::size_t Highlight(const HighlightStyle &options, llvm::StringRef line,
+                        llvm::StringRef previous_lines,
+                        Stream &s) const override;
+  bool ShouldHighlightFile(lldb::LanguageType language,
+                           llvm::StringRef path) const override;
+};
+
+bool ClangHighlighter::isKeyword(llvm::StringRef token) const {
+  return keywords.find(token) != keywords.end();
+}
+
+ClangHighlighter::ClangHighlighter() {
+#define KEYWORD(X, N) keywords.insert(#X);
+#include "clang/Basic/TokenKinds.def"
+}
+
+HighlightStyle::ColorStyle ClangHighlighter::determineClangStyle(
+    const clang::Token &token, llvm::StringRef tok_str,
+    const HighlightStyle &options, bool &in_pp_directive) const {
+  using namespace clang;
+
+  if (token.is(tok::comment)) {
+    // If we were in a preprocessor directive before, we now left it.
+    in_pp_directive = false;
+    return options.comment;
+  } else if (in_pp_directive || token.getKind() == tok::hash) {
+    // Let's assume that the rest of the line is a PP directive.
+    in_pp_directive = true;
+    // Preprocessor directives are hard to match, so we have to hack this in.
+    return options.pp_directive;
+  } else if (tok::isStringLiteral(token.getKind()))
+    return options.string_literal;
+  else if (tok::isLiteral(token.getKind()))
+    return options.scalar_literal;
+  else if (isKeyword(tok_str))
+    return options.keyword;
+  else
+    switch (token.getKind()) {
+    case tok::raw_identifier:
+    case tok::identifier:
+      return options.identifier;
+    case tok::l_brace:
+    case tok::r_brace:
+      return options.braces;
+    case tok::l_square:
+    case tok::r_square:
+      return options.square_brackets;
+    case tok::l_paren:
+    case tok::r_paren:
+      return options.parentheses;
+    case tok::comma:
+      return options.comma;
+    case tok::coloncolon:
+    case tok::colon:
+      return options.colon;
+
+    case tok::amp:
+    case tok::ampamp:
+    case tok::ampequal:
+    case tok::star:
+    case tok::starequal:
+    case tok::plus:
+    case tok::plusplus:
+    case tok::plusequal:
+    case tok::minus:
+    case tok::arrow:
+    case tok::minusminus:
+    case tok::minusequal:
+    case tok::tilde:
+    case tok::exclaim:
+    case tok::exclaimequal:
+    case tok::slash:
+    case tok::slashequal:
+    case tok::percent:
+    case tok::percentequal:
+    case tok::less:
+    case tok::lessless:
+    case tok::lessequal:
+    case tok::lesslessequal:
+    case tok::spaceship:
+    case tok::greater:
+    case tok::greatergreater:
+    case tok::greaterequal:
+    case tok::greatergreaterequal:
+    case tok::caret:
+    case tok::caretequal:
+    case tok::pipe:
+    case tok::pipepipe:
+    case tok::pipeequal:
+    case tok::question:
+    case tok::equal:
+    case tok::equalequal:
+      return options.operators;
+    default:
+      break;
+    }
+  return HighlightStyle::ColorStyle();
+}
+
+std::size_t ClangHighlighter::Highlight(const HighlightStyle &options,
+                                        llvm::StringRef line,
+                                        llvm::StringRef previous_lines,
+                                        Stream &result) const {
+  using namespace clang;
+
+  std::size_t written_bytes = 0;
+
+  FileSystemOptions file_opts;
+  FileManager file_mgr(file_opts);
+
+  unsigned line_number = previous_lines.count('\n') + 1U;
+
+  // Let's build the actual source code Clang needs and setup some utility
+  // objects.
+  std::string full_source = previous_lines.str() + line.str();
+  llvm::IntrusiveRefCntPtr<DiagnosticIDs> diag_ids(new DiagnosticIDs());
+  llvm::IntrusiveRefCntPtr<DiagnosticOptions> diags_opts(
+      new DiagnosticOptions());
+  DiagnosticsEngine diags(diag_ids, diags_opts);
+  clang::SourceManager SM(diags, file_mgr);
+  std::unique_ptr<llvm::MemoryBuffer> buf =
+      llvm::MemoryBuffer::getMemBufferCopy(full_source);
+
+  FileID FID = SM.createFileID(clang::SourceManager::Unowned, buf.get());
+
+  // Let's just enable the latest ObjC and C++ which should get most tokens
+  // right.
+  LangOptions Opts;
+  Opts.ObjC2 = true;
+  Opts.CPlusPlus17 = true;
+  Opts.LineComment = true;
+
+  Lexer lex(FID, buf.get(), SM, Opts);
+  // The lexer should keep whitespace around.
+  lex.SetKeepWhitespaceMode(true);
+
+  // Keeps track if we have entered a PP directive.
+  bool in_pp_directive = false;
+
+  // True once we actually lexed the user provided line.
+  bool found_user_line = false;
+
+  Token token;
+  bool exit = false;
+  while (!exit) {
+    // Returns true if this is the last token we get from the lexer.
+    exit = lex.LexFromRawLexer(token);
+
+    bool invalid = false;
+    unsigned current_line_number =
+        SM.getSpellingLineNumber(token.getLocation(), &invalid);
+    if (current_line_number != line_number)
+      continue;
+    found_user_line = true;
+
+    // We don't need to print any tokens without a spelling line number.
+    if (invalid)
+      continue;
+
+    // Same as above but with the column number.
+    invalid = false;
+    unsigned start = SM.getSpellingColumnNumber(token.getLocation(), &invalid);
+    if (invalid)
+      continue;
+    // Column numbers start at 1, but indexes in our string start at 0.
+    --start;
+
+    // Annotations don't have a length, so let's skip them.
+    if (token.isAnnotation())
+      continue;
+
+    // Extract the token string from our source code.
+    llvm::StringRef tok_str = line.substr(start, token.getLength());
+
+    // If the token is just an empty string, we can skip all the work below.
+    if (tok_str.empty())
+      continue;
+
+    // See how we are supposed to highlight this token.
+    HighlightStyle::ColorStyle color =
+        determineClangStyle(token, tok_str, options, in_pp_directive);
+
+    written_bytes += color.Apply(result, tok_str);
+  }
+
+  // If we went over the whole file but couldn't find our own file, then
+  // somehow our setup was wrong. When we're in release mode we just give the
+  // user the normal line and pretend we don't know how to highlight it. In
+  // debug mode we bail out with an assert as this should never happen.
+  if (!found_user_line) {
+    result << line;
+    written_bytes += line.size();
+    assert(false && "We couldn't find the user line in the input file?");
+  }
+
+  return written_bytes;
+}
+
+bool ClangHighlighter::ShouldHighlightFile(lldb::LanguageType language,
+                                           llvm::StringRef path) const {
+  switch (language) {
+  case lldb::LanguageType::eLanguageTypeC:
+  case lldb::LanguageType::eLanguageTypeObjC:
+  case lldb::LanguageType::eLanguageTypeObjC_plus_plus:
+  case lldb::LanguageType::eLanguageTypeC89:
+  case lldb::LanguageType::eLanguageTypeC99:
+  case lldb::LanguageType::eLanguageTypeC11:
+  case lldb::LanguageType::eLanguageTypeC_plus_plus:
+  case lldb::LanguageType::eLanguageTypeC_plus_plus_03:
+  case lldb::LanguageType::eLanguageTypeC_plus_plus_11:
+  case lldb::LanguageType::eLanguageTypeC_plus_plus_14:
+  case lldb::LanguageType::eLanguageTypeOpenCL:
+    return true;
+  default:
+    break;
+  }
+
+  // User didn't provide any language, so we have to guess based on the file
+  // path.
+  const auto suffixes = {".cpp", ".cxx", ".c++", ".cc",  ".c",
+                         ".h",   ".hh",  ".hpp", ".hxx", ".h++"};
+  for (auto suffix : suffixes) {
+    if (path.endswith_lower(suffix))
+      return true;
+  }
+
+  // One final effort to check if we're in the STL path and should highlight.
+  return path.contains("/c++/");
+}
+
+HighlighterManager::HighlighterManager() {
+  m_highlighters.push_back(llvm::make_unique<ClangHighlighter>());
+
+  // Our final highlighter will always match and just do nothing.
+  m_highlighters.push_back(llvm::make_unique<NoHighlighter>());
+}
+
+const Highlighter &
+HighlighterManager::getHighlighterFor(lldb::LanguageType language_type,
+                                      llvm::StringRef path) const {
+  Language *language = lldb_private::Language::FindPlugin(language_type, path);
+  if (language && language->GetHighlighter())
+    return *language->GetHighlighter();
+  return m_no_highlighter;
+}
+
+std::string Highlighter::Highlight(const HighlightStyle &options,
+                                   llvm::StringRef line,
+                                   llvm::StringRef previous_lines) const {
+  StreamString s;
+  Highlight(options, line, previous_lines, s);
+  s.Flush();
+  return s.GetString().str();
+}
Index: source/Core/Debugger.cpp
===================================================================
--- source/Core/Debugger.cpp
+++ source/Core/Debugger.cpp
@@ -233,6 +233,8 @@
      nullptr,
      "The number of sources lines to display that come before the "
      "current source line when displaying a stopped context."},
+    {"highlight-source", OptionValue::eTypeBoolean, true, true, nullptr,
+     nullptr, "If true, LLDB will highlight the displayed source code."},
     {"stop-show-column", OptionValue::eTypeEnum, false,
      eStopShowColumnAnsiOrCaret, nullptr, s_stop_show_column_values,
      "If true, LLDB will use the column information from the debug info to "
@@ -296,6 +298,7 @@
   ePropertyStopDisassemblyDisplay,
   ePropertyStopLineCountAfter,
   ePropertyStopLineCountBefore,
+  ePropertyHighlightSource,
   ePropertyStopShowColumn,
   ePropertyStopShowColumnAnsiPrefix,
   ePropertyStopShowColumnAnsiSuffix,
@@ -470,6 +473,12 @@
   return ret;
 }
 
+bool Debugger::GetHighlightSource() const {
+  const uint32_t idx = ePropertyHighlightSource;
+  return m_collection_sp->GetPropertyAtIndexAsBoolean(
+      nullptr, idx, g_properties[idx].default_uint_value);
+}
+
 StopShowColumn Debugger::GetStopShowColumn() const {
   const uint32_t idx = ePropertyStopShowColumn;
   return (lldb::StopShowColumn)m_collection_sp->GetPropertyAtIndexAsEnumeration(
Index: source/Core/CMakeLists.txt
===================================================================
--- source/Core/CMakeLists.txt
+++ source/Core/CMakeLists.txt
@@ -25,6 +25,7 @@
   FileLineResolver.cpp
   FileSpecList.cpp
   FormatEntity.cpp
+  Highlighter.cpp
   IOHandler.cpp
   Listener.cpp
   Mangled.cpp
Index: packages/Python/lldbsuite/test/source-manager/TestSourceManager.py
===================================================================
--- packages/Python/lldbsuite/test/source-manager/TestSourceManager.py
+++ packages/Python/lldbsuite/test/source-manager/TestSourceManager.py
@@ -22,6 +22,8 @@
     # return re.compile(r"\[4m%s\[0m" % inner_regex_text)
     return "4.+\033\\[4m%s\033\\[0m" % inner_regex_text
 
+def ansi_color_surround_regex(inner_regex_text):
+    return "\033\\[3[0-7]m%s\033\\[0m" % inner_regex_text
 
 class SourceManagerTestCase(TestBase):
 
@@ -47,7 +49,7 @@
         # the character column after the initial whitespace.
         return len(stop_line) - len(stop_line.lstrip()) + 1
 
-    def do_display_source_python_api(self, use_color, column_marker_regex):
+    def do_display_source_python_api(self, use_color, needle_regex, highlight_source=False):
         self.build()
         exe = self.getBuildArtifact("a.out")
         self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)
@@ -69,6 +71,9 @@
         # Setup whether we should use ansi escape sequences, including color
         # and styles such as underline.
         self.dbg.SetUseColor(use_color)
+        # Disable syntax highlighting if needed.
+
+        self.runCmd("settings set highlight-source " + str(highlight_source).lower())
 
         filespec = lldb.SBFileSpec(self.file, False)
         source_mgr = self.dbg.GetSourceManager()
@@ -87,10 +92,10 @@
         # => 4        printf("Hello world.\n"); // Set break point at this line.
         #    5        return 0;
         #    6    }
-        self.expect(stream.GetData(), "Source code displayed correctly",
+        self.expect(stream.GetData(), "Source code displayed correctly:\n" + stream.GetData(),
                     exe=False,
                     patterns=['=> %d.*Hello world' % self.line,
-                              column_marker_regex])
+                              needle_regex])
 
         # Boundary condition testings for SBStream().  LLDB should not crash!
         stream.Print(None)
@@ -111,6 +116,24 @@
         underline_regex = ansi_underline_surround_regex(r".")
         self.do_display_source_python_api(use_color, underline_regex)
 
+    @add_test_categories(['pyapi'])
+    def test_display_source_python_ansi_terminal_syntax_highlighting(self):
+        """Test display of source using the SBSourceManager API and check for
+        the syntax highlighted output"""
+        use_color = True
+        syntax_highlighting = True;
+
+        # Just pick 'int' as something that should be colored.
+        color_regex = ansi_color_surround_regex("int")
+        self.do_display_source_python_api(use_color, color_regex, syntax_highlighting)
+
+        # Same for 'char'.
+        color_regex = ansi_color_surround_regex("char")
+        self.do_display_source_python_api(use_color, color_regex, syntax_highlighting)
+
+        # Test that we didn't color unrelated identifiers.
+        self.do_display_source_python_api(use_color, r" printf\(", syntax_highlighting)
+
     def test_move_and_then_display_source(self):
         """Test that target.source-map settings work by moving main.c to hidden/main.c."""
         self.build()
Index: include/lldb/Target/Language.h
===================================================================
--- include/lldb/Target/Language.h
+++ include/lldb/Target/Language.h
@@ -20,6 +20,7 @@
 
 // Other libraries and framework includes
 // Project includes
+#include "lldb/Core/Highlighter.h"
 #include "lldb/Core/PluginInterface.h"
 #include "lldb/DataFormatters/DumpValueObjectOptions.h"
 #include "lldb/DataFormatters/FormatClasses.h"
@@ -152,13 +153,24 @@
 
   static Language *FindPlugin(lldb::LanguageType language);
 
+  /// Returns the Language associated with the given file path or a nullptr
+  /// if there is no known language.
+  static Language *FindPlugin(llvm::StringRef file_path);
+
+  static Language *FindPlugin(lldb::LanguageType language,
+                              llvm::StringRef file_path);
+
   // return false from callback to stop iterating
   static void ForEach(std::function<bool(Language *)> callback);
 
   virtual lldb::LanguageType GetLanguageType() const = 0;
 
   virtual bool IsTopLevelFunction(Function &function);
 
+  virtual bool IsSourceFile(llvm::StringRef file_path) const = 0;
+
+  virtual const Highlighter *GetHighlighter() const { return nullptr; }
+
   virtual lldb::TypeCategoryImplSP GetFormatters();
 
   virtual HardcodedFormatters::HardcodedFormatFinder GetHardcodedFormats();
Index: include/lldb/Core/Highlighter.h
===================================================================
--- /dev/null
+++ include/lldb/Core/Highlighter.h
@@ -0,0 +1,165 @@
+//===-- Highlighter.h -------------------------------------------*- C++ -*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef liblldb_Highlighter_h_
+#define liblldb_Highlighter_h_
+
+#include <utility>
+#include <vector>
+
+#include "lldb/Utility/Stream.h"
+#include "lldb/lldb-enumerations.h"
+#include "llvm/ADT/StringRef.h"
+
+namespace lldb_private {
+
+//----------------------------------------------------------------------
+/// Represents style that the highlighter should apply to the given source code.
+/// Stores information about how every kind of token should be annotated.
+//----------------------------------------------------------------------
+struct HighlightStyle {
+
+  //----------------------------------------------------------------------
+  /// A pair of strings that should be placed around a certain token. Usually
+  /// stores color codes in these strings (the suffix string is often used for
+  /// resetting the terminal attributes back to normal).
+  //----------------------------------------------------------------------
+  class ColorStyle {
+    std::string m_prefix;
+    std::string m_suffix;
+
+  public:
+    ColorStyle() = default;
+    ColorStyle(llvm::StringRef prefix, llvm::StringRef suffix) {
+      Set(prefix, suffix);
+    }
+
+    /// Applies this style to the given value.
+    /// \param s
+    ///     The stream to which the result should be appended.
+    /// \param value
+    ///     The value that we should place our strings around.
+    /// \return
+    ///     The number of bytes that have been written to the given string.
+    std::size_t Apply(Stream &s, llvm::StringRef value) const;
+
+    /// Sets the prefix and suffix strings.
+    /// @param prefix
+    /// @param suffix
+    void Set(llvm::StringRef prefix, llvm::StringRef suffix) {
+      m_prefix = prefix;
+      m_suffix = suffix;
+    }
+  };
+
+  /// Matches identifiers to variable or functions.
+  ColorStyle identifier;
+  /// Matches any string or character literals in the language: "foo" or 'f'
+  ColorStyle string_literal;
+  /// Matches scalar value literals like '42' or '0.1'.
+  ColorStyle scalar_literal;
+  /// Matches all reserved keywords in the language.
+  ColorStyle keyword;
+  /// Matches any comments in the language.
+  ColorStyle comment;
+  /// Matches commas: ','
+  ColorStyle comma;
+  /// Matches one colon: ':'
+  ColorStyle colon;
+  /// Matches any semicolon: ';'
+  ColorStyle semicolons;
+  /// Matches operators like '+', '-', '%', '&', '='
+  ColorStyle operators;
+
+  /// Matches '{' or '}'
+  ColorStyle braces;
+  /// Matches '[' or ']'
+  ColorStyle square_brackets;
+  /// Matches '(' or ')'
+  ColorStyle parentheses;
+
+  //-----------------------------------------------------------------------
+  // C language specific options
+  //-----------------------------------------------------------------------
+
+  /// Matches directives to a preprocessor (if the language has any).
+  ColorStyle pp_directive;
+
+  /// Returns a HighlightStyle that is based on vim's default highlight style.
+  static HighlightStyle MakeVimStyle();
+};
+
+//----------------------------------------------------------------------
+/// Annotates source code with color attributes.
+//----------------------------------------------------------------------
+class Highlighter {
+public:
+  Highlighter() = default;
+  virtual ~Highlighter() = default;
+  DISALLOW_COPY_AND_ASSIGN(Highlighter);
+
+  /// Returns a human readable name for the selected highlighter.
+  virtual llvm::StringRef GetName() const = 0;
+
+  /// Highlights the given line
+  /// \param options
+  /// \param line
+  ///     The user supplied line that needs to be highlighted.
+  /// \param previous_lines
+  ///     Any previous lines the user has written which we should only use
+  ///     for getting the context of the Highlighting right.
+  /// \param s
+  ///     The stream to which the highlighted version of the user string should
+  ///     be written.
+  /// \return
+  ///     The number of bytes that have been written to the stream.
+  virtual std::size_t Highlight(const HighlightStyle &options,
+                                llvm::StringRef line,
+                                llvm::StringRef previous_lines,
+                                Stream &s) const = 0;
+
+  /// Utility method for calling Highlight without a stream.
+  std::string Highlight(const HighlightStyle &options, llvm::StringRef line,
+                        llvm::StringRef previous_lines = "") const;
+};
+
+/// A default highlighter that does nothing. Used as a fallback.
+class NoHighlighter : public Highlighter {
+public:
+  llvm::StringRef GetName() const override { return "none"; }
+
+  std::size_t Highlight(const HighlightStyle &options, llvm::StringRef line,
+                        llvm::StringRef previous_lines,
+                        Stream &s) const override;
+};
+
+/// Manages the available highlighters.
+class HighlighterManager {
+  /// All available highlighters in the list of priority.
+  std::vector<std::unique_ptr<Highlighter>> m_highlighters;
+  NoHighlighter m_no_highlighter;
+
+public:
+  HighlighterManager();
+  /// Queries all known highlighter for one that can highlight some source code.
+  /// \param language_type
+  ///     The language type hat the caller thinks the source code was given in.
+  /// \param path
+  ///     The path to the file the source code is from. Used as a fallback when
+  ///     the user can't provide a language.
+  /// \return
+  ///     The highlighter that wants to highlight the source code. Could be an
+  ///     empty highlighter that does nothing.
+  const Highlighter &getHighlighterFor(lldb::LanguageType language_type,
+                                       llvm::StringRef path) const;
+};
+
+} // namespace lldb_private
+
+#endif // liblldb_Highlighter_h_
Index: include/lldb/Core/Debugger.h
===================================================================
--- include/lldb/Core/Debugger.h
+++ include/lldb/Core/Debugger.h
@@ -272,6 +272,8 @@
 
   bool SetUseColor(bool use_color);
 
+  bool GetHighlightSource() const;
+
   lldb::StopShowColumn GetStopShowColumn() const;
 
   const FormatEntity::Entry *GetStopShowColumnAnsiPrefix() const;
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to