Index: lldb/trunk/include/lldb/Core/Debugger.h =================================================================== --- lldb/trunk/include/lldb/Core/Debugger.h +++ lldb/trunk/include/lldb/Core/Debugger.h @@ -277,9 +277,9 @@ lldb::StopShowColumn GetStopShowColumn() const; - const FormatEntity::Entry *GetStopShowColumnAnsiPrefix() const; + llvm::StringRef GetStopShowColumnAnsiPrefix() const; - const FormatEntity::Entry *GetStopShowColumnAnsiSuffix() const; + llvm::StringRef GetStopShowColumnAnsiSuffix() const; uint32_t GetStopSourceLineCount(bool before) const; Index: lldb/trunk/include/lldb/Core/Highlighter.h =================================================================== --- lldb/trunk/include/lldb/Core/Highlighter.h +++ lldb/trunk/include/lldb/Core/Highlighter.h @@ -53,6 +53,11 @@ void Set(llvm::StringRef prefix, llvm::StringRef suffix); }; + /// The style for the token which is below the cursor of the user. Note that + /// this style is overwritten by the SourceManager with the values of + /// stop-show-column-ansi-prefix/stop-show-column-ansi-suffix. + ColorStyle selected; + /// Matches identifiers to variable or functions. ColorStyle identifier; /// Matches any string or character literals in the language: "foo" or 'f' @@ -106,6 +111,9 @@ /// \param options /// \param line /// The user supplied line that needs to be highlighted. + /// \param cursor_pos + /// The cursor position of the user in this line, starting at 0 (which + /// means the cursor is on the first character in 'line'). /// \param previous_lines /// Any previous lines the user has written which we should only use /// for getting the context of the Highlighting right. @@ -113,25 +121,29 @@ /// The stream to which the highlighted version of the user string should /// be written. virtual void Highlight(const HighlightStyle &options, llvm::StringRef line, + llvm::Optional cursor_pos, 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::Optional cursor_pos, llvm::StringRef previous_lines = "") const; }; -/// A default highlighter that does nothing. Used as a fallback. -class NoHighlighter : public Highlighter { +/// A default highlighter that only highlights the user cursor, but doesn't +/// do any other highlighting. +class DefaultHighlighter : public Highlighter { public: llvm::StringRef GetName() const override { return "none"; } void Highlight(const HighlightStyle &options, llvm::StringRef line, + llvm::Optional cursor_pos, llvm::StringRef previous_lines, Stream &s) const override; }; /// Manages the available highlighters. class HighlighterManager { - NoHighlighter m_no_highlighter; + DefaultHighlighter m_default; public: /// Queries all known highlighter for one that can highlight some source code. @@ -145,6 +157,7 @@ /// empty highlighter that does nothing. const Highlighter &getHighlighterFor(lldb::LanguageType language_type, llvm::StringRef path) const; + const Highlighter &getDefaultHighlighter() const { return m_default; } }; } // namespace lldb_private Index: lldb/trunk/include/lldb/Core/SourceManager.h =================================================================== --- lldb/trunk/include/lldb/Core/SourceManager.h +++ lldb/trunk/include/lldb/Core/SourceManager.h @@ -52,7 +52,7 @@ void UpdateIfNeeded(); - size_t DisplaySourceLines(uint32_t line, uint32_t column, + size_t DisplaySourceLines(uint32_t line, llvm::Optional column, uint32_t context_before, uint32_t context_after, Stream *s); void FindLinesMatchingRegex(RegularExpression ®ex, uint32_t start_line, Index: lldb/trunk/packages/Python/lldbsuite/test/source-manager/TestSourceManager.py =================================================================== --- lldb/trunk/packages/Python/lldbsuite/test/source-manager/TestSourceManager.py +++ lldb/trunk/packages/Python/lldbsuite/test/source-manager/TestSourceManager.py @@ -113,7 +113,7 @@ """Test display of source using the SBSourceManager API, using a dumb terminal and thus no color support (the default).""" use_color = True - underline_regex = ansi_underline_surround_regex(r".") + underline_regex = ansi_underline_surround_regex(r"printf") self.do_display_source_python_api(use_color, underline_regex) @add_test_categories(['pyapi']) @@ -132,7 +132,8 @@ 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) + self.do_display_source_python_api(use_color, r" main\(", syntax_highlighting) + self.do_display_source_python_api(use_color, r"\);", 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.""" Index: lldb/trunk/source/Core/Debugger.cpp =================================================================== --- lldb/trunk/source/Core/Debugger.cpp +++ lldb/trunk/source/Core/Debugger.cpp @@ -177,9 +177,6 @@ // args}}:\n}{${function.changed}\n{${module.file.basename}`}{${function.name- // without-args}}:\n}{${current-pc-arrow} }{${addr-file-or-load}}: -#define DEFAULT_STOP_SHOW_COLUMN_ANSI_PREFIX "${ansi.underline}" -#define DEFAULT_STOP_SHOW_COLUMN_ANSI_SUFFIX "${ansi.normal}" - static OptionEnumValueElement s_stop_show_column_values[] = { {eStopShowColumnAnsiOrCaret, "ansi-or-caret", "Highlight the stop column with ANSI terminal codes when color/ANSI mode " @@ -239,13 +236,13 @@ eStopShowColumnAnsiOrCaret, nullptr, s_stop_show_column_values, "If true, LLDB will use the column information from the debug info to " "mark the current position when displaying a stopped context."}, - {"stop-show-column-ansi-prefix", OptionValue::eTypeFormatEntity, true, 0, - DEFAULT_STOP_SHOW_COLUMN_ANSI_PREFIX, nullptr, + {"stop-show-column-ansi-prefix", OptionValue::eTypeString, true, 0, + "${ansi.underline}", nullptr, "When displaying the column marker in a color-enabled (i.e. ANSI) " "terminal, use the ANSI terminal code specified in this format at the " "immediately before the column to be marked."}, - {"stop-show-column-ansi-suffix", OptionValue::eTypeFormatEntity, true, 0, - DEFAULT_STOP_SHOW_COLUMN_ANSI_SUFFIX, nullptr, + {"stop-show-column-ansi-suffix", OptionValue::eTypeString, true, 0, + "${ansi.normal}", nullptr, "When displaying the column marker in a color-enabled (i.e. ANSI) " "terminal, use the ANSI terminal code specified in this format " "immediately after the column to be marked."}, @@ -485,14 +482,14 @@ nullptr, idx, g_properties[idx].default_uint_value); } -const FormatEntity::Entry *Debugger::GetStopShowColumnAnsiPrefix() const { +llvm::StringRef Debugger::GetStopShowColumnAnsiPrefix() const { const uint32_t idx = ePropertyStopShowColumnAnsiPrefix; - return m_collection_sp->GetPropertyAtIndexAsFormatEntity(nullptr, idx); + return m_collection_sp->GetPropertyAtIndexAsString(nullptr, idx, ""); } -const FormatEntity::Entry *Debugger::GetStopShowColumnAnsiSuffix() const { +llvm::StringRef Debugger::GetStopShowColumnAnsiSuffix() const { const uint32_t idx = ePropertyStopShowColumnAnsiSuffix; - return m_collection_sp->GetPropertyAtIndexAsFormatEntity(nullptr, idx); + return m_collection_sp->GetPropertyAtIndexAsString(nullptr, idx, ""); } uint32_t Debugger::GetStopSourceLineCount(bool before) const { Index: lldb/trunk/source/Core/Highlighter.cpp =================================================================== --- lldb/trunk/source/Core/Highlighter.cpp +++ lldb/trunk/source/Core/Highlighter.cpp @@ -25,11 +25,28 @@ m_suffix = lldb_utility::ansi::FormatAnsiTerminalCodes(suffix); } -void 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; +void DefaultHighlighter::Highlight(const HighlightStyle &options, + llvm::StringRef line, + llvm::Optional cursor_pos, + llvm::StringRef previous_lines, + Stream &s) const { + // If we don't have a valid cursor, then we just print the line as-is. + if (!cursor_pos || *cursor_pos >= line.size()) { + s << line; + return; + } + + // If we have a valid cursor, we have to apply the 'selected' style around + // the character below the cursor. + + // Split the line around the character which is below the cursor. + size_t column = *cursor_pos; + // Print the characters before the cursor. + s << line.substr(0, column); + // Print the selected character with the defined color codes. + options.selected.Apply(s, line.substr(column, 1)); + // Print the rest of the line. + s << line.substr(column + 1U); } static HighlightStyle::ColorStyle GetColor(const char *c) { @@ -50,14 +67,15 @@ Language *language = lldb_private::Language::FindPlugin(language_type, path); if (language && language->GetHighlighter()) return *language->GetHighlighter(); - return m_no_highlighter; + return m_default; } std::string Highlighter::Highlight(const HighlightStyle &options, llvm::StringRef line, + llvm::Optional cursor_pos, llvm::StringRef previous_lines) const { StreamString s; - Highlight(options, line, previous_lines, s); + Highlight(options, line, cursor_pos, previous_lines, s); s.Flush(); return s.GetString().str(); } Index: lldb/trunk/source/Core/SourceManager.cpp =================================================================== --- lldb/trunk/source/Core/SourceManager.cpp +++ lldb/trunk/source/Core/SourceManager.cpp @@ -127,11 +127,6 @@ 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. @@ -201,8 +196,16 @@ return_value += s->Printf("%s%2.2s %-4u\t", prefix, line == curr_line ? current_line_cstr : "", line); - size_t this_line_size = m_last_file_sp->DisplaySourceLines( - line, line == curr_line ? column : 0, 0, 0, s); + + // So far we treated column 0 as a special 'no column value', but + // DisplaySourceLines starts counting columns from 0 (and no column is + // expressed by passing an empty optional). + llvm::Optional columnToHighlight; + if (line == curr_line && column) + columnToHighlight = column - 1; + + size_t this_line_size = + m_last_file_sp->DisplaySourceLines(line, columnToHighlight, 0, 0, s); if (column != 0 && line == curr_line && should_show_stop_column_with_caret(m_debugger_wp.lock())) { // Display caret cursor. @@ -521,7 +524,8 @@ } } -size_t SourceManager::File::DisplaySourceLines(uint32_t line, uint32_t column, +size_t SourceManager::File::DisplaySourceLines(uint32_t line, + llvm::Optional column, uint32_t context_before, uint32_t context_after, Stream *s) { @@ -535,15 +539,24 @@ size_t bytes_written = s->GetWrittenBytes(); - std::string previous_content; + auto debugger_sp = m_debugger_wp.lock(); + + HighlightStyle style; + // Use the default Vim style if source highlighting is enabled. + if (should_highlight_source(debugger_sp)) + style = HighlightStyle::MakeVimStyle(); + + // If we should mark the stop column with color codes, then copy the prefix + // and suffix to our color style. + if (should_show_stop_column_with_ansi(debugger_sp)) + style.selected.Set(debugger_sp->GetStopShowColumnAnsiPrefix(), + debugger_sp->GetStopShowColumnAnsiSuffix()); - 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 auto &h = mgr.getHighlighterFor(lldb::eLanguageTypeUnknown, path); const uint32_t start_line = line <= context_before ? 1 : line - context_before; @@ -560,64 +573,8 @@ const uint8_t *cstr = m_data_sp->GetBytes() + start_line_offset; auto ref = llvm::StringRef(reinterpret_cast(cstr), count); - bool displayed_line = false; - - auto debugger_sp = m_debugger_wp.lock(); - if (should_highlight_source(debugger_sp)) { - 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. - auto ansi_prefix_entry = debugger_sp->GetStopShowColumnAnsiPrefix(); - auto ansi_suffix_entry = debugger_sp->GetStopShowColumnAnsiSuffix(); - - // We only bother breaking up the line to format the marked column if - // there is any marking specified on both sides of the marked column. - // In ANSI-terminal-sequence land, there must be a post if there is a - // pre format, and vice versa. - if (ansi_prefix_entry && ansi_suffix_entry) { - // Mark the current column with the desired escape sequence for - // formatting the column (e.g. underline, inverse, etc.) - - // First print the part before the column to mark. - s->Write(cstr, column - 1); - - // Write the pre escape sequence. - const SymbolContext *sc = nullptr; - const ExecutionContext *exe_ctx = nullptr; - const Address addr = LLDB_INVALID_ADDRESS; - ValueObject *valobj = nullptr; - const bool function_changed = false; - const bool initial_function = false; - - FormatEntity::Format(*ansi_prefix_entry, *s, sc, exe_ctx, &addr, - valobj, function_changed, initial_function); - - s->Write(cstr + column - 1, 1); - - // Write the post escape sequence. - FormatEntity::Format(*ansi_suffix_entry, *s, sc, exe_ctx, &addr, - valobj, function_changed, initial_function); - - // And finish up with the rest of the line. - s->Write(cstr + column, count - column); - - // Keep track of the fact that we just wrote the line. - displayed_line = true; - } - } - } - // If we didn't end up displaying the line with ANSI codes for whatever - // reason, display it now sans codes. - if (!displayed_line) - s->PutCString(ref); + h.Highlight(style, ref, column, "", *s); // Ensure we get an end of line character one way or another. if (!is_newline_char(ref.back())) Index: lldb/trunk/source/Plugins/Language/ClangCommon/ClangHighlighter.h =================================================================== --- lldb/trunk/source/Plugins/Language/ClangCommon/ClangHighlighter.h +++ lldb/trunk/source/Plugins/Language/ClangCommon/ClangHighlighter.h @@ -29,6 +29,7 @@ llvm::StringRef GetName() const override { return "clang"; } void Highlight(const HighlightStyle &options, llvm::StringRef line, + llvm::Optional cursor_pos, llvm::StringRef previous_lines, Stream &s) const override; /// Returns true if the given string represents a keywords in any Clang Index: lldb/trunk/source/Plugins/Language/ClangCommon/ClangHighlighter.cpp =================================================================== --- lldb/trunk/source/Plugins/Language/ClangCommon/ClangHighlighter.cpp +++ lldb/trunk/source/Plugins/Language/ClangCommon/ClangHighlighter.cpp @@ -130,6 +130,7 @@ void ClangHighlighter::Highlight(const HighlightStyle &options, llvm::StringRef line, + llvm::Optional cursor_pos, llvm::StringRef previous_lines, Stream &result) const { using namespace clang; @@ -168,6 +169,8 @@ // True once we actually lexed the user provided line. bool found_user_line = false; + // True if we already highlighted the token under the cursor, false otherwise. + bool highlighted_cursor = false; Token token; bool exit = false; while (!exit) { @@ -204,11 +207,22 @@ if (tok_str.empty()) continue; + // If the cursor is inside this token, we have to apply the 'selected' + // highlight style before applying the actual token color. + llvm::StringRef to_print = tok_str; + StreamString storage; + auto end = start + token.getLength(); + if (cursor_pos && end > *cursor_pos && !highlighted_cursor) { + highlighted_cursor = true; + options.selected.Apply(storage, tok_str); + to_print = storage.GetString(); + } + // See how we are supposed to highlight this token. HighlightStyle::ColorStyle color = determineClangStyle(*this, token, tok_str, options, in_pp_directive); - color.Apply(result, tok_str); + color.Apply(result, to_print); } // If we went over the whole file but couldn't find our own file, then Index: lldb/trunk/unittests/Language/Highlighting/HighlighterTest.cpp =================================================================== --- lldb/trunk/unittests/Language/Highlighting/HighlighterTest.cpp +++ lldb/trunk/unittests/Language/Highlighting/HighlighterTest.cpp @@ -99,31 +99,49 @@ style.semicolons.Set("<", ">"); const char *code = "program Hello;"; - std::string output = h.Highlight(style, code); + std::string output = h.Highlight(style, code, llvm::Optional()); EXPECT_STREQ(output.c_str(), code); } -TEST_F(HighlighterTest, DefaultHighlighter) { +static std::string +highlightDefault(llvm::StringRef code, HighlightStyle style, + llvm::Optional cursor = llvm::Optional()) { HighlighterManager mgr; - const Highlighter &h = mgr.getHighlighterFor(lldb::eLanguageTypeC, "main.c"); - - HighlightStyle style; + return mgr.getDefaultHighlighter().Highlight(style, code, cursor); +} +TEST_F(HighlighterTest, DefaultHighlighter) { const char *code = "int my_main() { return 22; } \n"; - std::string output = h.Highlight(style, code); - EXPECT_STREQ(output.c_str(), code); + HighlightStyle style; + EXPECT_EQ(code, highlightDefault(code, style)); +} + +TEST_F(HighlighterTest, DefaultHighlighterWithCursor) { + HighlightStyle style; + style.selected.Set("", ""); + EXPECT_EQ("a bc", highlightDefault("a bc", style, 0)); + EXPECT_EQ("a bc", highlightDefault("a bc", style, 1)); + EXPECT_EQ("a bc", highlightDefault("a bc", style, 2)); + EXPECT_EQ("a bc", highlightDefault("a bc", style, 3)); } +TEST_F(HighlighterTest, DefaultHighlighterWithCursorOutOfBounds) { + HighlightStyle style; + style.selected.Set("", ""); + EXPECT_EQ("a bc", highlightDefault("a bc", style, 4)); +} //------------------------------------------------------------------------------ // Tests highlighting with the Clang highlighter. //------------------------------------------------------------------------------ -static std::string highlightC(llvm::StringRef code, HighlightStyle style) { +static std::string +highlightC(llvm::StringRef code, HighlightStyle style, + llvm::Optional cursor = llvm::Optional()) { HighlighterManager mgr; const Highlighter &h = mgr.getHighlighterFor(lldb::eLanguageTypeC, "main.c"); - return h.Highlight(style, code); + return h.Highlight(style, code, cursor); } TEST_F(HighlighterTest, ClangEmptyInput) { @@ -219,3 +237,67 @@ EXPECT_EQ(" foo c = bar(); return 1;", highlightC(" foo c = bar(); return 1;", s)); } + +TEST_F(HighlighterTest, ClangCursorPos) { + HighlightStyle s; + s.selected.Set("", ""); + + EXPECT_EQ(" foo c = bar(); return 1;", + highlightC(" foo c = bar(); return 1;", s, 0)); + EXPECT_EQ(" foo c = bar(); return 1;", + highlightC(" foo c = bar(); return 1;", s, 1)); + EXPECT_EQ(" foo c = bar(); return 1;", + highlightC(" foo c = bar(); return 1;", s, 2)); + EXPECT_EQ(" foo c = bar(); return 1;", + highlightC(" foo c = bar(); return 1;", s, 3)); + EXPECT_EQ(" foo c = bar(); return 1;", + highlightC(" foo c = bar(); return 1;", s, 4)); + EXPECT_EQ(" foo c = bar(); return 1;", + highlightC(" foo c = bar(); return 1;", s, 5)); +} + +TEST_F(HighlighterTest, ClangCursorPosEndOfLine) { + HighlightStyle s; + s.selected.Set("", ""); + + EXPECT_EQ("f", highlightC("f", s, 1)); +} + +TEST_F(HighlighterTest, ClangCursorOutOfBounds) { + HighlightStyle s; + s.selected.Set("", ""); + EXPECT_EQ("f", highlightC("f", s, 2)); + EXPECT_EQ("f", highlightC("f", s, 3)); + EXPECT_EQ("f", highlightC("f", s, 4)); +} + +TEST_F(HighlighterTest, ClangCursorPosBeforeOtherToken) { + HighlightStyle s; + s.selected.Set("", ""); + s.identifier.Set("", ""); + + EXPECT_EQ(" foo c = bar(); return 1;", + highlightC(" foo c = bar(); return 1;", s, 0)); +} + +TEST_F(HighlighterTest, ClangCursorPosAfterOtherToken) { + HighlightStyle s; + s.selected.Set("", ""); + s.identifier.Set("", ""); + + EXPECT_EQ(" foo c = bar(); return 1;", + highlightC(" foo c = bar(); return 1;", s, 4)); +} + +TEST_F(HighlighterTest, ClangCursorPosInOtherToken) { + HighlightStyle s; + s.selected.Set("", ""); + s.identifier.Set("", ""); + + EXPECT_EQ(" foo c = bar(); return 1;", + highlightC(" foo c = bar(); return 1;", s, 1)); + EXPECT_EQ(" foo c = bar(); return 1;", + highlightC(" foo c = bar(); return 1;", s, 2)); + EXPECT_EQ(" foo c = bar(); return 1;", + highlightC(" foo c = bar(); return 1;", s, 3)); +}