[Lldb-commits] [lldb] eed9e97 - [lldb] Highlight matching keywords in apropos output (#194997)
via lldb-commits
lldb-commits at lists.llvm.org
Fri May 1 08:38:27 PDT 2026
Author: Jonas Devlieghere
Date: 2026-05-01T08:38:22-07:00
New Revision: eed9e970ee1448c86b925302d6e7548aa31da0fc
URL: https://github.com/llvm/llvm-project/commit/eed9e970ee1448c86b925302d6e7548aa31da0fc
DIFF: https://github.com/llvm/llvm-project/commit/eed9e970ee1448c86b925302d6e7548aa31da0fc.diff
LOG: [lldb] Highlight matching keywords in apropos output (#194997)
When color is enabled, `apropos` now highlights occurrences of the
search term in both command names/help text and settings descriptions
using the configurable regex match ANSI settings.
Implements #194877
Added:
Modified:
lldb/include/lldb/Interpreter/CommandInterpreter.h
lldb/include/lldb/Interpreter/Property.h
lldb/include/lldb/Utility/Stream.h
lldb/source/Commands/CommandObjectApropos.cpp
lldb/source/Interpreter/CommandInterpreter.cpp
lldb/source/Interpreter/Property.cpp
lldb/source/Utility/Stream.cpp
lldb/test/API/commands/apropos/formatting/TestAproposFormatting.py
lldb/unittests/Utility/StreamTest.cpp
llvm/docs/ReleaseNotes.md
Removed:
################################################################################
diff --git a/lldb/include/lldb/Interpreter/CommandInterpreter.h b/lldb/include/lldb/Interpreter/CommandInterpreter.h
index 26e0767951e7f..9f9a165e856a0 100644
--- a/lldb/include/lldb/Interpreter/CommandInterpreter.h
+++ b/lldb/include/lldb/Interpreter/CommandInterpreter.h
@@ -449,12 +449,14 @@ class CommandInterpreter : public Broadcaster,
void GetAliasHelp(const char *alias_name, StreamString &help_string);
- void OutputFormattedHelpText(Stream &strm, llvm::StringRef prefix,
- llvm::StringRef help_text);
-
- void OutputFormattedHelpText(Stream &stream, llvm::StringRef command_word,
- llvm::StringRef separator,
- llvm::StringRef help_text, size_t max_word_len);
+ void OutputFormattedHelpText(
+ Stream &strm, llvm::StringRef prefix, llvm::StringRef help_text,
+ std::optional<Stream::HighlightSettings> highlight = std::nullopt);
+
+ void OutputFormattedHelpText(
+ Stream &stream, llvm::StringRef command_word, llvm::StringRef separator,
+ llvm::StringRef help_text, size_t max_word_len,
+ std::optional<Stream::HighlightSettings> highlight = std::nullopt);
// this mimics OutputFormattedHelpText but it does perform a much simpler
// formatting, basically ensuring line alignment. This is only good if you
diff --git a/lldb/include/lldb/Interpreter/Property.h b/lldb/include/lldb/Interpreter/Property.h
index 74da58275dba8..6e43c990fb366 100644
--- a/lldb/include/lldb/Interpreter/Property.h
+++ b/lldb/include/lldb/Interpreter/Property.h
@@ -11,9 +11,11 @@
#include "lldb/Interpreter/OptionValue.h"
#include "lldb/Utility/Flags.h"
+#include "lldb/Utility/Stream.h"
#include "lldb/lldb-defines.h"
#include "lldb/lldb-private-types.h"
+#include <optional>
#include <string>
namespace lldb_private {
@@ -60,9 +62,10 @@ class Property {
bool DumpQualifiedName(Stream &strm) const;
- void DumpDescription(CommandInterpreter &interpreter, Stream &strm,
- uint32_t output_width,
- bool display_qualified_name) const;
+ void DumpDescription(
+ CommandInterpreter &interpreter, Stream &strm, uint32_t output_width,
+ bool display_qualified_name,
+ std::optional<Stream::HighlightSettings> highlight = std::nullopt) const;
void SetValueChangedCallback(std::function<void()> callback);
diff --git a/lldb/include/lldb/Utility/Stream.h b/lldb/include/lldb/Utility/Stream.h
index f54d1785502a8..9f2290c227ab7 100644
--- a/lldb/include/lldb/Utility/Stream.h
+++ b/lldb/include/lldb/Utility/Stream.h
@@ -38,10 +38,11 @@ class Stream {
llvm::StringRef pattern; ///< Regex pattern for highlighting.
llvm::StringRef prefix; ///< ANSI color code to start colorization.
llvm::StringRef suffix; ///< ANSI color code to end colorization.
+ bool ignore_case = false; ///< Whether to match case-insensitively.
HighlightSettings(llvm::StringRef p, llvm::StringRef pre,
- llvm::StringRef suf)
- : pattern(p), prefix(pre), suffix(suf) {}
+ llvm::StringRef suf, bool ic = false)
+ : pattern(p), prefix(pre), suffix(suf), ignore_case(ic) {}
};
/// Utility class for counting the bytes that were written to a stream in a
diff --git a/lldb/source/Commands/CommandObjectApropos.cpp b/lldb/source/Commands/CommandObjectApropos.cpp
index 7c2d3068f68d7..dc31e60f7fc2a 100644
--- a/lldb/source/Commands/CommandObjectApropos.cpp
+++ b/lldb/source/Commands/CommandObjectApropos.cpp
@@ -7,11 +7,13 @@
//===----------------------------------------------------------------------===//
#include "CommandObjectApropos.h"
+#include "lldb/Core/Debugger.h"
#include "lldb/Interpreter/CommandInterpreter.h"
#include "lldb/Interpreter/CommandReturnObject.h"
#include "lldb/Interpreter/Property.h"
#include "lldb/Utility/Args.h"
#include "lldb/Utility/StreamString.h"
+#include "llvm/Support/Regex.h"
using namespace lldb;
using namespace lldb_private;
@@ -35,6 +37,15 @@ void CommandObjectApropos::DoExecute(Args &args, CommandReturnObject &result) {
if (!search_word.empty()) {
ReturnStatus return_status = eReturnStatusSuccessFinishNoResult;
+ std::string escaped_search_word;
+ std::optional<Stream::HighlightSettings> highlight;
+ Debugger &dbg = GetDebugger();
+ if (dbg.GetUseColor()) {
+ escaped_search_word = llvm::Regex::escape(search_word);
+ highlight.emplace(escaped_search_word, dbg.GetRegexMatchAnsiPrefix(),
+ dbg.GetRegexMatchAnsiSuffix(), true);
+ }
+
// Find all commands matching the search word.
StringList commands_found;
StringList commands_help;
@@ -54,7 +65,8 @@ void CommandObjectApropos::DoExecute(Args &args, CommandReturnObject &result) {
for (size_t i = 0; i < commands_found.GetSize(); ++i)
m_interpreter.OutputFormattedHelpText(
result.GetOutputStream(), commands_found.GetStringAtIndex(i),
- "--", commands_help.GetStringAtIndex(i), commands_max_len);
+ "--", commands_help.GetStringAtIndex(i), commands_max_len,
+ highlight);
return_status = eReturnStatusSuccessFinishResult;
}
@@ -86,7 +98,7 @@ void CommandObjectApropos::DoExecute(Args &args, CommandReturnObject &result) {
for (size_t i = 0; i < num_properties; ++i)
properties[i]->DumpDescription(
m_interpreter, result.GetOutputStream(), properties_max_len,
- dump_qualified_name);
+ dump_qualified_name, highlight);
return_status = eReturnStatusSuccessFinishResult;
}
diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp
index fb3c1beb1bfea..e0909b7d6c8df 100644
--- a/lldb/source/Interpreter/CommandInterpreter.cpp
+++ b/lldb/source/Interpreter/CommandInterpreter.cpp
@@ -3100,15 +3100,25 @@ void CommandInterpreter::SetSynchronous(bool value) {
m_synchronous_execution = value;
}
-void CommandInterpreter::OutputFormattedHelpText(Stream &strm,
- llvm::StringRef prefix,
- llvm::StringRef help_text) {
+void CommandInterpreter::OutputFormattedHelpText(
+ Stream &strm, llvm::StringRef prefix, llvm::StringRef help_text,
+ std::optional<Stream::HighlightSettings> highlight) {
const uint32_t max_columns = m_debugger.GetTerminalWidth();
size_t line_width_max = max_columns - prefix.size();
if (line_width_max < 16)
line_width_max = help_text.size() + prefix.size();
+ // Apply highlighting to the full text before line splitting so that matches
+ // spanning a line break are highlighted on both lines.
+ std::string highlighted_storage;
+ if (highlight) {
+ StreamString ss;
+ ss.PutCStringColorHighlighted(help_text, highlight);
+ highlighted_storage = std::string(ss.GetString());
+ help_text = highlighted_storage;
+ }
+
strm.IndentMore(prefix.size());
bool prefixed_yet = false;
// Even if we have no help text we still want to emit the command name.
@@ -3117,7 +3127,7 @@ void CommandInterpreter::OutputFormattedHelpText(Stream &strm,
while (!help_text.empty()) {
// Prefix the first line, indent subsequent lines to line up
if (!prefixed_yet) {
- strm << prefix;
+ strm.PutCStringColorHighlighted(prefix, highlight);
prefixed_yet = true;
} else
strm.Indent();
@@ -3144,15 +3154,15 @@ void CommandInterpreter::OutputFormattedHelpText(Stream &strm,
strm.IndentLess(prefix.size());
}
-void CommandInterpreter::OutputFormattedHelpText(Stream &strm,
- llvm::StringRef word_text,
- llvm::StringRef separator,
- llvm::StringRef help_text,
- size_t max_word_len) {
+void CommandInterpreter::OutputFormattedHelpText(
+ Stream &strm, llvm::StringRef word_text, llvm::StringRef separator,
+ llvm::StringRef help_text, size_t max_word_len,
+ std::optional<Stream::HighlightSettings> highlight) {
StreamString prefix_stream;
prefix_stream.Printf(" %-*s %*s ", (int)max_word_len, word_text.data(),
(int)separator.size(), separator.data());
- OutputFormattedHelpText(strm, prefix_stream.GetString(), help_text);
+ OutputFormattedHelpText(strm, prefix_stream.GetString(), help_text,
+ highlight);
}
void CommandInterpreter::OutputHelpText(Stream &strm, llvm::StringRef word_text,
diff --git a/lldb/source/Interpreter/Property.cpp b/lldb/source/Interpreter/Property.cpp
index 56e45363be89a..b049b765e520e 100644
--- a/lldb/source/Interpreter/Property.cpp
+++ b/lldb/source/Interpreter/Property.cpp
@@ -272,9 +272,10 @@ void Property::Dump(const ExecutionContext *exe_ctx, Stream &strm,
}
}
-void Property::DumpDescription(CommandInterpreter &interpreter, Stream &strm,
- uint32_t output_width,
- bool display_qualified_name) const {
+void Property::DumpDescription(
+ CommandInterpreter &interpreter, Stream &strm, uint32_t output_width,
+ bool display_qualified_name,
+ std::optional<Stream::HighlightSettings> highlight) const {
if (!m_value_sp)
return;
llvm::StringRef desc = GetDescription();
@@ -295,10 +296,10 @@ void Property::DumpDescription(CommandInterpreter &interpreter, Stream &strm,
StreamString qualified_name;
DumpQualifiedName(qualified_name);
interpreter.OutputFormattedHelpText(strm, qualified_name.GetString(),
- "--", desc, output_width);
+ "--", desc, output_width, highlight);
} else {
interpreter.OutputFormattedHelpText(strm, m_name, "--", desc,
- output_width);
+ output_width, highlight);
}
}
}
diff --git a/lldb/source/Utility/Stream.cpp b/lldb/source/Utility/Stream.cpp
index c37fa1e6317a1..ad3eb416f9888 100644
--- a/lldb/source/Utility/Stream.cpp
+++ b/lldb/source/Utility/Stream.cpp
@@ -79,7 +79,9 @@ void Stream::PutCStringColorHighlighted(
return;
}
- llvm::Regex reg_pattern(pattern_info->pattern);
+ llvm::Regex reg_pattern(pattern_info->pattern, pattern_info->ignore_case
+ ? llvm::Regex::IgnoreCase
+ : llvm::Regex::NoFlags);
llvm::SmallVector<llvm::StringRef, 1> matches;
llvm::StringRef remaining = text;
std::string format_str = lldb_private::ansi::FormatAnsiTerminalCodes(
diff --git a/lldb/test/API/commands/apropos/formatting/TestAproposFormatting.py b/lldb/test/API/commands/apropos/formatting/TestAproposFormatting.py
index b9073348f7583..73dad81c958a9 100644
--- a/lldb/test/API/commands/apropos/formatting/TestAproposFormatting.py
+++ b/lldb/test/API/commands/apropos/formatting/TestAproposFormatting.py
@@ -33,3 +33,46 @@ def test_apropos_with_settings_alignment(self):
self.expect_prompt()
self.quit()
+
+ @skipIfAsan
+ @skipIfEditlineSupportMissing
+ def test_apropos_highlights_matches(self):
+ """Test that apropos highlights matching keywords in output."""
+ self.launch(use_colors=True, dimensions=(100, 200))
+
+ ansi_green = "\x1b[32m"
+ ansi_reset = "\x1b[0m"
+ self.child.sendline(
+ "settings set show-regex-match-ansi-prefix ${ansi.fg.green}"
+ )
+ self.expect_prompt()
+
+ self.child.sendline("apropos disass")
+ # Check command name highlighting.
+ self.child.expect_exact(ansi_green + "disass" + ansi_reset + "emble")
+ # Check settings name highlighting.
+ self.child.expect_exact(ansi_green + "disass" + ansi_reset + "embly-format")
+ self.expect_prompt()
+ self.quit()
+
+ @skipIfAsan
+ @skipIfEditlineSupportMissing
+ def test_apropos_highlights_across_line_break(self):
+ """Test that apropos highlights matches that span a line break."""
+ self.launch(use_colors=True, dimensions=(24, 68))
+
+ ansi_green = "\x1b[32m"
+ ansi_reset = "\x1b[0m"
+ self.child.sendline(
+ "settings set show-regex-match-ansi-prefix ${ansi.fg.green}"
+ )
+ self.expect_prompt()
+
+ self.child.sendline('apropos "will override this"')
+ # The narrow terminal forces "will override this" to wrap across
+ # lines. Verify the ANSI highlight spans the match: green starts
+ # before "will" and the reset comes after "this".
+ self.child.expect_exact(ansi_green + "will")
+ self.child.expect_exact("this" + ansi_reset)
+ self.expect_prompt()
+ self.quit()
diff --git a/lldb/unittests/Utility/StreamTest.cpp b/lldb/unittests/Utility/StreamTest.cpp
index 7fb8bfd0ef3dd..b870a8e8a26f3 100644
--- a/lldb/unittests/Utility/StreamTest.cpp
+++ b/lldb/unittests/Utility/StreamTest.cpp
@@ -727,3 +727,21 @@ TEST_F(StreamTest, PutSLEB128) {
EXPECT_EQ("0x6533", TakeValue());
EXPECT_EQ(6U, bytes);
}
+
+TEST_F(StreamTest, PutCStringColorHighlightedCaseInsensitive) {
+ Stream::HighlightSettings settings("hello", "[", "]", true);
+ s.PutCStringColorHighlighted("Say Hello World", settings);
+ EXPECT_EQ("Say [Hello] World", TakeValue());
+}
+
+TEST_F(StreamTest, PutCStringColorHighlightedCaseSensitive) {
+ Stream::HighlightSettings settings("hello", "[", "]", false);
+ s.PutCStringColorHighlighted("Say Hello World", settings);
+ EXPECT_EQ("Say Hello World", TakeValue());
+}
+
+TEST_F(StreamTest, PutCStringColorHighlightedMultipleMatches) {
+ Stream::HighlightSettings settings("o", "[", "]", false);
+ s.PutCStringColorHighlighted("foo bar boo", settings);
+ EXPECT_EQ("f[o][o] bar b[o][o]", TakeValue());
+}
diff --git a/llvm/docs/ReleaseNotes.md b/llvm/docs/ReleaseNotes.md
index 4fba67c7d8334..ae353ccf2897c 100644
--- a/llvm/docs/ReleaseNotes.md
+++ b/llvm/docs/ReleaseNotes.md
@@ -239,6 +239,7 @@ Makes programs 10x faster by doing Special New Thing.
* Breakpoint commands now accept `.` to refer to the location(s) at which the current thread is stopped. For
example, `breakpoint disable .` disables the just-hit breakpoint location. Another usage is to automate a
command to run at the current location: `breakpoint command add -o 'p my_var' .`.
+* The `apropos` command now highlights matching keywords in its output when color is enabled.
#### Deprecated APIs
More information about the lldb-commits
mailing list