teemperor created this revision. Herald added a subscriber: mgorny. This patch adds syntax highlighting support to LLDB. When enabled (and lldb is allowed to use colors), printed source code is annotated with the ANSI color escape sequences.
So far we have only one highlighter which is based on Clang and is responsible for all languages that are supported by Clang. It essentially just runs the raw lexer over the input and then surrounds the specific tokens with the configured escape sequences. https://reviews.llvm.org/D49334 Files: include/lldb/Core/Debugger.h include/lldb/Core/Highlighter.h source/Core/CMakeLists.txt source/Core/Debugger.cpp source/Core/Highlighter.cpp source/Core/SourceManager.cpp unittests/Core/CMakeLists.txt unittests/Core/HighlighterTest.cpp
Index: unittests/Core/HighlighterTest.cpp =================================================================== --- /dev/null +++ unittests/Core/HighlighterTest.cpp @@ -0,0 +1,204 @@ +//===-- 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" + +using namespace lldb_private; +using namespace llvm; + +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) { + ASSERT_EQ(getName(lldb::eLanguageTypeC), "clang"); + ASSERT_EQ(getName(lldb::eLanguageTypeC11), "clang"); + ASSERT_EQ(getName(lldb::eLanguageTypeC89), "clang"); + ASSERT_EQ(getName(lldb::eLanguageTypeC99), "clang"); + ASSERT_EQ(getName(lldb::eLanguageTypeC_plus_plus), "clang"); + ASSERT_EQ(getName(lldb::eLanguageTypeC_plus_plus_03), "clang"); + ASSERT_EQ(getName(lldb::eLanguageTypeC_plus_plus_11), "clang"); + ASSERT_EQ(getName(lldb::eLanguageTypeC_plus_plus_14), "clang"); + ASSERT_EQ(getName(lldb::eLanguageTypeObjC), "clang"); + ASSERT_EQ(getName(lldb::eLanguageTypeObjC_plus_plus), "clang"); + ASSERT_EQ(getName(lldb::eLanguageTypeOpenCL), "clang"); + + ASSERT_NE(getName(lldb::eLanguageTypeJulia), "clang"); + ASSERT_NE(getName(lldb::eLanguageTypeJava), "clang"); + ASSERT_NE(getName(lldb::eLanguageTypeHaskell), "clang"); +} + +TEST(HighlighterTest, HighlighterSelectionPath) { + ASSERT_EQ(getName("myfile.cc"), "clang"); + ASSERT_EQ(getName("moo.cpp"), "clang"); + ASSERT_EQ(getName("mar.cxx"), "clang"); + ASSERT_EQ(getName("foo.C"), "clang"); + ASSERT_EQ(getName("bar.CC"), "clang"); + ASSERT_EQ(getName("a/dir.CC"), "clang"); + ASSERT_EQ(getName("/a/dir.hpp"), "clang"); + ASSERT_EQ(getName("header.h"), "clang"); + + ASSERT_NE(getName("/dev/null"), "clang"); + ASSERT_NE(getName("Factory.java"), "clang"); + ASSERT_NE(getName("poll.py"), "clang"); + ASSERT_NE(getName("reducer.hs"), "clang"); +} + +TEST(HighlighterTest, FallbackHighlighter) { + 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); + + ASSERT_STREQ(output.c_str(), code); +} + +TEST(HighlighterTest, DefaultHighlighter) { + 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); + + ASSERT_STREQ(output.c_str(), code); +} + +//------------------------------------------------------------------------------ +// Tests highlighting with the Clang highlighter. +//------------------------------------------------------------------------------ + +static std::string highlightC(llvm::StringRef code, HighlightStyle style) { + 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); + ASSERT_STREQ("", output.c_str()); +} + +TEST(HighlighterTest, ClangScalarLiterals) { + HighlightStyle s; + s.scalar_literal.Set("<scalar>", "</scalar>"); + + std::string output = highlightC(" int i = 22;", s); + ASSERT_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); + ASSERT_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); + ASSERT_STREQ(" f = \"", output.c_str()); +} + +TEST(HighlighterTest, Keywords) { + HighlightStyle s; + s.keyword.Set("<k>", "</k>"); + + std::string output = highlightC(" return 1; ", s); + ASSERT_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); + ASSERT_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); + ASSERT_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); + ASSERT_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); + ASSERT_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); + ASSERT_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); + ASSERT_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); + ASSERT_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); + ASSERT_STREQ(" <id>foo</id> <id>c</id> = <id>bar</id>(); return 1;", + output.c_str()); +} Index: unittests/Core/CMakeLists.txt =================================================================== --- unittests/Core/CMakeLists.txt +++ unittests/Core/CMakeLists.txt @@ -1,6 +1,7 @@ add_lldb_unittest(LLDBCoreTests BroadcasterTest.cpp DataExtractorTest.cpp + HighlighterTest.cpp ListenerTest.cpp ScalarTest.cpp StateTest.cpp 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,11 +28,13 @@ #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 #include <memory> +#include <sstream> #include <utility> // for pair #include <assert.h> // for assert @@ -103,6 +106,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 +129,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 +535,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 +558,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 +618,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/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 do nothing here. + 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, + llvm::StringRef path) const { + for (auto &h : m_highlighters) { + if (h->ShouldHighlightFile(language, path)) + return *h; + } + llvm_unreachable("No highlighter initialized?"); +} + +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 @@ -24,6 +24,7 @@ FileLineResolver.cpp FileSpecList.cpp FormatEntity.cpp + Highlighter.cpp IOHandler.cpp Listener.cpp Mangled.cpp Index: include/lldb/Core/Highlighter.h =================================================================== --- /dev/null +++ include/lldb/Core/Highlighter.h @@ -0,0 +1,181 @@ +//===-- 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; + + /// Makes the Highlighter decide if it wants to color the given source code. + /// \param language + /// The language that 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 + /// True if the Highlighter is sure he can highlight the file; otherwise + /// returns false. + virtual bool ShouldHighlightFile(lldb::LanguageType language, + llvm::StringRef path) const = 0; +}; + +/// A default highlighter that does nothing. Used as a fallback highlighter. +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; + bool ShouldHighlightFile(lldb::LanguageType language, + llvm::StringRef path) const override { + return true; + } +}; + +/// 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 + /// The language that 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, + 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