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; 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 +#include + +#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> 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: 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: 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/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 + +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 diag_ids(new DiagnosticIDs()); + llvm::IntrusiveRefCntPtr diags_opts( + new DiagnosticOptions()); + DiagnosticsEngine diags(diag_ids, diags_opts); + clang::SourceManager SM(diags, file_mgr); + std::unique_ptr 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()); + + // Our final highlighter will always match and just do nothing. + m_highlighters.push_back(llvm::make_unique()); +} + +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/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 +#include #include // for pair #include // 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(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: 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: 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("", ""); + + std::string output = highlightC(" int i = 22;", s); + ASSERT_STREQ(" int i = 22;", output.c_str()); +} + +TEST(HighlighterTest, ClangStringLiterals) { + HighlightStyle s; + s.string_literal.Set("", ""); + + std::string output = highlightC("const char *f = 22 + \"foo\";", s); + ASSERT_STREQ("const char *f = 22 + \"foo\";", output.c_str()); +} + +TEST(HighlighterTest, ClangUnterminatedString) { + HighlightStyle s; + s.string_literal.Set("", ""); + + std::string output = highlightC(" f = \"", s); + ASSERT_STREQ(" f = \"", output.c_str()); +} + +TEST(HighlighterTest, Keywords) { + HighlightStyle s; + s.keyword.Set("", ""); + + std::string output = highlightC(" return 1; ", s); + ASSERT_STREQ(" return 1; ", output.c_str()); +} + +TEST(HighlighterTest, Colons) { + HighlightStyle s; + s.colon.Set("", ""); + + std::string output = highlightC("foo::bar:", s); + ASSERT_STREQ("foo::bar:", output.c_str()); +} + +TEST(HighlighterTest, ClangBraces) { + HighlightStyle s; + s.braces.Set("", ""); + + std::string output = highlightC("a{}", s); + ASSERT_STREQ("a{}", output.c_str()); +} + +TEST(HighlighterTest, ClangSquareBrackets) { + HighlightStyle s; + s.square_brackets.Set("", ""); + + std::string output = highlightC("a[]", s); + ASSERT_STREQ("a[]", output.c_str()); +} + +TEST(HighlighterTest, ClangCommas) { + HighlightStyle s; + s.comma.Set("", ""); + + std::string output = highlightC(" bool f = foo(), 1;", s); + ASSERT_STREQ(" bool f = foo(), 1;", output.c_str()); +} + +TEST(HighlighterTest, ClangPPDirectives) { + HighlightStyle s; + s.pp_directive.Set("", ""); + + std::string output = highlightC(" #include \"foo\" // comment\n", s); + ASSERT_STREQ(" #include \"foo\" " + "// comment\n", + output.c_str()); +} + +TEST(HighlighterTest, ClangComments) { + HighlightStyle s; + s.comment.Set("", ""); + + std::string output = highlightC(" /*com */ // com /*n*/", s); + ASSERT_STREQ(" /*com */ // com /*n*/", 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("", ""); + + std::string output = highlightC(" foo c = bar(); return 1;", s); + ASSERT_STREQ(" foo c = bar(); return 1;", + output.c_str()); +}