45 changes: 41 additions & 4 deletions lldb/source/Core/SourceManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -103,6 +104,18 @@ SourceManager::FileSP SourceManager::GetFile(const FileSpec &file_spec) {
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.
Expand All @@ -114,6 +127,11 @@ static bool should_show_stop_column_with_ansi(DebuggerSP debugger_sp) {
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.
Expand Down Expand Up @@ -515,6 +533,16 @@ size_t SourceManager::File::DisplaySourceLines(uint32_t line, uint32_t column,
if (!m_data_sp)
return 0;

std::string previous_content;

HighlightStyle style = HighlightStyle::MakeVimStyle();
HighlighterManager mgr;
std::string path = GetFileSpec().GetPath(/*denormalize*/ false);
// FIXME: Find a way to get the definitive language this file was written in
// and pass it to the highlighter.
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);
Expand All @@ -530,10 +558,19 @@ size_t SourceManager::File::DisplaySourceLines(uint32_t line, uint32_t column,
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.
Expand Down Expand Up @@ -581,10 +618,10 @@ size_t SourceManager::File::DisplaySourceLines(uint32_t line, uint32_t column,
// 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;
Expand Down
1 change: 1 addition & 0 deletions lldb/source/Plugins/Language/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
add_subdirectory(ClangCommon)
add_subdirectory(CPlusPlus)
add_subdirectory(Go)
add_subdirectory(Java)
Expand Down
2 changes: 2 additions & 0 deletions lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ add_lldb_library(lldbPluginCPlusPlusLanguage PLUGIN
lldbSymbol
lldbTarget
lldbUtility
lldbPluginClangCommon

LINK_COMPONENTS
Support
)
13 changes: 13 additions & 0 deletions lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1002,3 +1002,16 @@ CPlusPlusLanguage::GetHardcodedSynthetics() {

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++/");
}
7 changes: 7 additions & 0 deletions lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -90,6 +93,10 @@ class CPlusPlusLanguage : public Language {
HardcodedFormatters::HardcodedSyntheticFinder
GetHardcodedSynthetics() override;

bool IsSourceFile(llvm::StringRef file_path) const override;

const Highlighter *GetHighlighter() const override { return &m_highlighter; }

//------------------------------------------------------------------
// Static Functions
//------------------------------------------------------------------
Expand Down
9 changes: 9 additions & 0 deletions lldb/source/Plugins/Language/ClangCommon/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
add_lldb_library(lldbPluginClangCommon PLUGIN
ClangHighlighter.cpp

LINK_LIBS
lldbCore
lldbUtility
LINK_COMPONENTS
Support
)
227 changes: 227 additions & 0 deletions lldb/source/Plugins/Language/ClangCommon/ClangHighlighter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
//===-- 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"

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);
auto buf = llvm::MemoryBuffer::getMemBuffer(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;
}
42 changes: 42 additions & 0 deletions lldb/source/Plugins/Language/ClangCommon/ClangHighlighter.h
Original file line number Diff line number Diff line change
@@ -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/Stream.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_
4 changes: 4 additions & 0 deletions lldb/source/Plugins/Language/Go/GoLanguage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,7 @@ GoLanguage::GetHardcodedSynthetics() {

return g_formatters;
}

bool GoLanguage::IsSourceFile(llvm::StringRef file_path) const {
return file_path.endswith(".go");
}
2 changes: 2 additions & 0 deletions lldb/source/Plugins/Language/Go/GoLanguage.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ class GoLanguage : public Language {
HardcodedFormatters::HardcodedSyntheticFinder
GetHardcodedSynthetics() override;

bool IsSourceFile(llvm::StringRef file_path) const override;

//------------------------------------------------------------------
// Static Functions
//------------------------------------------------------------------
Expand Down
4 changes: 4 additions & 0 deletions lldb/source/Plugins/Language/Java/JavaLanguage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,7 @@ lldb::TypeCategoryImplSP JavaLanguage::GetFormatters() {
});
return g_category;
}

bool JavaLanguage::IsSourceFile(llvm::StringRef file_path) const {
return file_path.endswith(".java");
}
2 changes: 2 additions & 0 deletions lldb/source/Plugins/Language/Java/JavaLanguage.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ class JavaLanguage : public Language {
bool IsNilReference(ValueObject &valobj) override;

lldb::TypeCategoryImplSP GetFormatters() override;

bool IsSourceFile(llvm::StringRef file_path) const override;
};

} // namespace lldb_private
Expand Down
9 changes: 9 additions & 0 deletions lldb/source/Plugins/Language/OCaml/OCamlLanguage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions lldb/source/Plugins/Language/OCaml/OCamlLanguage.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ class OCamlLanguage : public Language {
return lldb::eLanguageTypeOCaml;
}

bool IsSourceFile(llvm::StringRef file_path) const override;

static void Initialize();

static void Terminate();
Expand Down
1 change: 1 addition & 0 deletions lldb/source/Plugins/Language/ObjC/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ add_lldb_library(lldbPluginObjCLanguage PLUGIN
lldbTarget
lldbUtility
lldbPluginAppleObjCRuntime
lldbPluginClangCommon

EXTRA_CXXFLAGS ${EXTRA_CXXFLAGS}
)
9 changes: 9 additions & 0 deletions lldb/source/Plugins/Language/ObjC/ObjCLanguage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1102,3 +1102,12 @@ bool ObjCLanguage::IsNilReference(ValueObject &valobj) {
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;
}
7 changes: 7 additions & 0 deletions lldb/source/Plugins/Language/ObjC/ObjCLanguage.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -121,6 +124,10 @@ class ObjCLanguage : public Language {

bool IsNilReference(ValueObject &valobj) override;

bool IsSourceFile(llvm::StringRef file_path) const override;

const Highlighter *GetHighlighter() const override { return &m_highlighter; }

//------------------------------------------------------------------
// Static Functions
//------------------------------------------------------------------
Expand Down
3 changes: 2 additions & 1 deletion lldb/source/Plugins/Language/ObjCPlusPlus/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
add_lldb_library(lldbPluginObjCPlusPlusLanguage PLUGIN
ObjCPlusPlusLanguage.cpp

LINK_LIBS
lldbCore
lldbTarget
lldbPluginClangCommon
)
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -29,6 +32,10 @@ class ObjCPlusPlusLanguage : public Language {
return lldb::eLanguageTypeObjC_plus_plus;
}

bool IsSourceFile(llvm::StringRef file_path) const override;

const Highlighter *GetHighlighter() const override { return &m_highlighter; }

//------------------------------------------------------------------
// Static Functions
//------------------------------------------------------------------
Expand Down
32 changes: 32 additions & 0 deletions lldb/source/Target/Language.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,39 @@ Language *Language::FindPlugin(lldb::LanguageType language) {
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) {
Expand Down
1 change: 1 addition & 0 deletions lldb/unittests/Language/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
add_subdirectory(CPlusPlus)
add_subdirectory(Highlighting)
11 changes: 11 additions & 0 deletions lldb/unittests/Language/Highlighting/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
add_lldb_unittest(HighlighterTests
HighlighterTest.cpp

LINK_LIBS
lldbPluginCPlusPlusLanguage
lldbPluginObjCLanguage
lldbPluginObjCPlusPlusLanguage
lldbPluginJavaLanguage
lldbPluginOCamlLanguage
lldbPluginGoLanguage
)
221 changes: 221 additions & 0 deletions lldb/unittests/Language/Highlighting/HighlighterTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
//===-- 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 "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;

namespace {
class HighlighterTest : public testing::Test {
public:
static void SetUpTestCase();
static void TearDownTestCase();
};
} // namespace

void HighlighterTest::SetUpTestCase() {
// 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();
}

void HighlighterTest::TearDownTestCase() {
CPlusPlusLanguage::Terminate();
GoLanguage::Terminate();
JavaLanguage::Terminate();
ObjCLanguage::Terminate();
ObjCPlusPlusLanguage::Terminate();
OCamlLanguage::Terminate();
}

static std::string getName(lldb::LanguageType type) {
HighlighterManager m;
return m.getHighlighterFor(type, "").GetName().str();
}

static std::string getName(llvm::StringRef path) {
HighlighterManager m;
return m.getHighlighterFor(lldb::eLanguageTypeUnknown, path).GetName().str();
}

TEST_F(HighlighterTest, HighlighterSelectionType) {
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_F(HighlighterTest, HighlighterSelectionPath) {
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_F(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);

EXPECT_STREQ(output.c_str(), code);
}

TEST_F(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);

EXPECT_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_F(HighlighterTest, ClangEmptyInput) {
HighlightStyle s;
EXPECT_EQ("", highlightC("", s));
}

TEST_F(HighlighterTest, ClangScalarLiterals) {
HighlightStyle s;
s.scalar_literal.Set("<scalar>", "</scalar>");

EXPECT_EQ(" int i = <scalar>22</scalar>;", highlightC(" int i = 22;", s));
}

TEST_F(HighlighterTest, ClangStringLiterals) {
HighlightStyle s;
s.string_literal.Set("<str>", "</str>");

EXPECT_EQ("const char *f = 22 + <str>\"foo\"</str>;",
highlightC("const char *f = 22 + \"foo\";", s));
}

TEST_F(HighlighterTest, ClangUnterminatedString) {
HighlightStyle s;
s.string_literal.Set("<str>", "</str>");

EXPECT_EQ(" f = \"", highlightC(" f = \"", s));
}

TEST_F(HighlighterTest, Keywords) {
HighlightStyle s;
s.keyword.Set("<k>", "</k>");

EXPECT_EQ(" <k>return</k> 1; ", highlightC(" return 1; ", s));
}

TEST_F(HighlighterTest, Colons) {
HighlightStyle s;
s.colon.Set("<c>", "</c>");

EXPECT_EQ("foo<c>:</c><c>:</c>bar<c>:</c>", highlightC("foo::bar:", s));
}

TEST_F(HighlighterTest, ClangBraces) {
HighlightStyle s;
s.braces.Set("<b>", "</b>");

EXPECT_EQ("a<b>{</b><b>}</b>", highlightC("a{}", s));
}

TEST_F(HighlighterTest, ClangSquareBrackets) {
HighlightStyle s;
s.square_brackets.Set("<sb>", "</sb>");

EXPECT_EQ("a<sb>[</sb><sb>]</sb>", highlightC("a[]", s));
}

TEST_F(HighlighterTest, ClangCommas) {
HighlightStyle s;
s.comma.Set("<comma>", "</comma>");

EXPECT_EQ(" bool f = foo()<comma>,</comma> 1;",
highlightC(" bool f = foo(), 1;", s));
}

TEST_F(HighlighterTest, ClangPPDirectives) {
HighlightStyle s;
s.pp_directive.Set("<pp>", "</pp>");

EXPECT_EQ("<pp>#</pp><pp>include</pp><pp> </pp><pp>\"foo\"</pp><pp> </pp>//c",
highlightC("#include \"foo\" //c", s));
}

TEST_F(HighlighterTest, ClangComments) {
HighlightStyle s;
s.comment.Set("<cc>", "</cc>");

EXPECT_EQ(" <cc>/*com */</cc> <cc>// com /*n*/</cc>",
highlightC(" /*com */ // com /*n*/", s));
}

TEST_F(HighlighterTest, ClangOperators) {
HighlightStyle s;
s.operators.Set("[", "]");

EXPECT_EQ(" 1[+]2[/]a[*]f[&]x[|][~]l", highlightC(" 1+2/a*f&x|~l", s));
}

TEST_F(HighlighterTest, ClangIdentifiers) {
HighlightStyle s;
s.identifier.Set("<id>", "</id>");

EXPECT_EQ(" <id>foo</id> <id>c</id> = <id>bar</id>(); return 1;",
highlightC(" foo c = bar(); return 1;", s));
}