[Lldb-commits] [lldb] [lldb] Show completion description in show-autosuggestion mode (PR #208677)
via lldb-commits
lldb-commits at lists.llvm.org
Fri Jul 10 02:51:30 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-lldb
Author: Raphael Isemann (Teemperor)
<details>
<summary>Changes</summary>
When showing an inline autosuggestion, this now also shows the description of the completion next to it. This allows users to see what a command they are about about to tab-complete actually does.
assisted-by: claude
---
Full diff: https://github.com/llvm/llvm-project/pull/208677.diff
5 Files Affected:
- (modified) lldb/include/lldb/Core/IOHandler.h (+2-1)
- (modified) lldb/include/lldb/Host/Editline.h (+11-2)
- (modified) lldb/source/Core/IOHandler.cpp (+16-5)
- (modified) lldb/source/Host/common/Editline.cpp (+47-9)
- (modified) lldb/test/API/iohandler/autosuggestion/TestAutosuggestion.py (+59-5)
``````````diff
diff --git a/lldb/include/lldb/Core/IOHandler.h b/lldb/include/lldb/Core/IOHandler.h
index 2672bbe5da2b3..3f4b80f23f8b1 100644
--- a/lldb/include/lldb/Core/IOHandler.h
+++ b/lldb/include/lldb/Core/IOHandler.h
@@ -415,7 +415,8 @@ class IOHandlerEditline : public IOHandler {
int FixIndentationCallback(Editline *editline, const StringList &lines,
int cursor_position);
- std::optional<std::string> SuggestionCallback(llvm::StringRef line);
+ std::optional<std::string> SuggestionCallback(llvm::StringRef line,
+ std::string &description);
void AutoCompleteCallback(CompletionRequest &request);
diff --git a/lldb/include/lldb/Host/Editline.h b/lldb/include/lldb/Host/Editline.h
index 39e2c6b73eef6..51e562b76491d 100644
--- a/lldb/include/lldb/Host/Editline.h
+++ b/lldb/include/lldb/Host/Editline.h
@@ -97,8 +97,17 @@ using IsInputCompleteCallbackType =
using FixIndentationCallbackType =
llvm::unique_function<int(Editline *, StringList &, int)>;
-using SuggestionCallbackType =
- llvm::unique_function<std::optional<std::string>(llvm::StringRef)>;
+/// Computes the inline autosuggestion for the given \p line.
+///
+/// Returns the text that should be appended to \p line as a grayed-out inline
+/// suggestion, or std::nullopt if there is no suggestion. When a suggestion is
+/// returned, \p description is filled with an optional human-readable
+/// description of the suggestion (for example, what tab completion would
+/// insert). The description is only displayed to the user in parentheses after
+/// the suggestion and is never inserted into the line when the suggestion is
+/// accepted.
+using SuggestionCallbackType = llvm::unique_function<std::optional<std::string>(
+ llvm::StringRef line, std::string &description)>;
using CompleteCallbackType = llvm::unique_function<void(CompletionRequest &)>;
diff --git a/lldb/source/Core/IOHandler.cpp b/lldb/source/Core/IOHandler.cpp
index f19fe3204ddf5..febfd9e42552a 100644
--- a/lldb/source/Core/IOHandler.cpp
+++ b/lldb/source/Core/IOHandler.cpp
@@ -267,9 +267,10 @@ IOHandlerEditline::IOHandlerEditline(
m_editline_up->SetRedrawCallback([this]() { this->RedrawCallback(); });
if (debugger.GetAutosuggestionMode() != eAutosuggestionOff) {
- m_editline_up->SetSuggestionCallback([this](llvm::StringRef line) {
- return this->SuggestionCallback(line);
- });
+ m_editline_up->SetSuggestionCallback(
+ [this](llvm::StringRef line, std::string &description) {
+ return this->SuggestionCallback(line, description);
+ });
m_editline_up->SetSuggestionAnsiPrefix(ansi::FormatAnsiTerminalCodes(
debugger.GetAutosuggestionAnsiPrefix()));
m_editline_up->SetSuggestionAnsiSuffix(ansi::FormatAnsiTerminalCodes(
@@ -439,7 +440,8 @@ int IOHandlerEditline::FixIndentationCallback(Editline *editline,
}
std::optional<std::string>
-IOHandlerEditline::SuggestionCallback(llvm::StringRef line) {
+IOHandlerEditline::SuggestionCallback(llvm::StringRef line,
+ std::string &description) {
// In tab-mode, we just display what tab would complete if the user
// would tab.
if (m_debugger.GetAutosuggestionMode() == eAutosuggestionTabMode) {
@@ -450,8 +452,17 @@ IOHandlerEditline::SuggestionCallback(llvm::StringRef line) {
result.GetMatches(matches);
std::string longest_prefix = matches.LongestCommonPrefix();
llvm::StringRef cursor_arg_prefix = request.GetCursorArgumentPrefix();
- if (longest_prefix.size() > cursor_arg_prefix.size())
+ if (longest_prefix.size() > cursor_arg_prefix.size()) {
+ // When the suggestion corresponds to a single completion, also surface
+ // its description (if any) so the user can see what the completion means.
+ if (result.GetNumberOfResults() == 1) {
+ StringList descriptions;
+ result.GetDescriptions(descriptions);
+ if (descriptions.GetSize() == 1)
+ description = descriptions[0];
+ }
return longest_prefix.substr(cursor_arg_prefix.size());
+ }
return std::nullopt;
}
return m_delegate.IOHandlerSuggestion(*this, line);
diff --git a/lldb/source/Host/common/Editline.cpp b/lldb/source/Host/common/Editline.cpp
index 5be0450cb17e9..1017014307781 100644
--- a/lldb/source/Host/common/Editline.cpp
+++ b/lldb/source/Host/common/Editline.cpp
@@ -1157,6 +1157,14 @@ unsigned char Editline::TabCommand(int ch) {
// completed.
if (to_add == " ")
return CC_REDISPLAY;
+ // If an autosuggestion (and its description) was drawn, it was written
+ // directly to the terminal and is invisible to libedit, so a plain
+ // refresh would leave the trailing description on screen. Force a full
+ // redisplay, which clears the line before redrawing.
+ if (m_previous_autosuggestion_size > 0) {
+ m_previous_autosuggestion_size = 0;
+ return CC_REDISPLAY;
+ }
return CC_REFRESH;
}
case CompletionMode::Partial: {
@@ -1200,7 +1208,10 @@ unsigned char Editline::ApplyAutosuggestCommand(int ch) {
llvm::StringRef line(line_info->buffer,
line_info->lastchar - line_info->buffer);
- if (std::optional<std::string> to_add = m_suggestion_callback(line))
+ // The description is only shown inline and never inserted into the line.
+ std::string description;
+ if (std::optional<std::string> to_add =
+ m_suggestion_callback(line, description))
el_insertstr(m_editline, to_add->c_str());
return CC_REDISPLAY;
@@ -1218,13 +1229,45 @@ unsigned char Editline::TypedCharacter(int ch) {
llvm::StringRef line(line_info->buffer,
line_info->lastchar - line_info->buffer);
- if (std::optional<std::string> to_add = m_suggestion_callback(line)) {
+ std::string description;
+ if (std::optional<std::string> to_add =
+ m_suggestion_callback(line, description)) {
LockedStreamFile locked_stream = m_output_stream_sp->Lock();
+ // The suggestion is what gets inserted when accepted; the description (if
+ // any) is only shown in parentheses after it and never inserted. Only show
+ // the description up to its first newline so it stays on a single line.
+ std::string suggestion = to_add.value();
+ if (!description.empty())
+ suggestion +=
+ " (" + description.substr(0, description.find('\n')) + ")";
+
+ int editline_cursor_position =
+ (int)((line_info->cursor - line_info->buffer) + GetPromptWidth());
+ int editline_cursor_row = editline_cursor_position / m_terminal_width;
+ int toColumn =
+ editline_cursor_position - (editline_cursor_row * m_terminal_width);
+
+ // The suggestion is drawn starting at the cursor and must fit on the
+ // remaining columns of the current row. If it is wider it would wrap onto
+ // the next line and corrupt the redraw, since we reposition the cursor by
+ // column only. Truncate it (with an ellipsis) so it always fits.
+ if (m_terminal_width > toColumn) {
+ const size_t available_width = m_terminal_width - toColumn;
+ if (suggestion.length() > available_width) {
+ llvm::StringRef ellipsis = "...";
+ if (available_width > ellipsis.size())
+ suggestion = suggestion.substr(0, available_width - ellipsis.size()) +
+ ellipsis.str();
+ else
+ suggestion = suggestion.substr(0, available_width);
+ }
+ }
+
std::string to_add_color =
- m_suggestion_ansi_prefix + to_add.value() + m_suggestion_ansi_suffix;
+ m_suggestion_ansi_prefix + suggestion + m_suggestion_ansi_suffix;
fputs(typed.c_str(), locked_stream.GetFile().GetStream());
fputs(to_add_color.c_str(), locked_stream.GetFile().GetStream());
- size_t new_autosuggestion_size = line.size() + to_add->length();
+ size_t new_autosuggestion_size = line.size() + suggestion.length();
// Print spaces to hide any remains of a previous longer autosuggestion.
if (new_autosuggestion_size < m_previous_autosuggestion_size) {
size_t spaces_to_print =
@@ -1234,11 +1277,6 @@ unsigned char Editline::TypedCharacter(int ch) {
}
m_previous_autosuggestion_size = new_autosuggestion_size;
- int editline_cursor_position =
- (int)((line_info->cursor - line_info->buffer) + GetPromptWidth());
- int editline_cursor_row = editline_cursor_position / m_terminal_width;
- int toColumn =
- editline_cursor_position - (editline_cursor_row * m_terminal_width);
fprintf(locked_stream.GetFile().GetStream(), ANSI_SET_COLUMN_N, toColumn);
return CC_REFRESH;
}
diff --git a/lldb/test/API/iohandler/autosuggestion/TestAutosuggestion.py b/lldb/test/API/iohandler/autosuggestion/TestAutosuggestion.py
index 1a6eef29a7f99..7eb347c8bb1ab 100644
--- a/lldb/test/API/iohandler/autosuggestion/TestAutosuggestion.py
+++ b/lldb/test/API/iohandler/autosuggestion/TestAutosuggestion.py
@@ -17,6 +17,7 @@ class TestCase(PExpectTest):
ANSI_RESET = "\x1b[0m"
ANSI_RED = "\x1b[31m"
ANSI_CYAN = "\x1b[36m"
+ ANSI_CLEAR_RIGHT = "\x1b[K"
# PExpect uses many timeouts internally and doesn't play well
# under ASAN on a loaded machine..
@@ -161,6 +162,10 @@ def test_autosuggestion_tab_mode(self):
tab completion would insert, instead of consulting command history."""
self.launch(
use_colors=True,
+ # Use a wide terminal so the full description fits on one line and
+ # is not truncated (see test_autosuggestion_tab_mode_description_
+ # truncation for the truncation behavior).
+ dimensions=(100, 500),
extra_args=[
"-o",
"settings set show-autosuggestion tab-mode",
@@ -180,25 +185,74 @@ def test_autosuggestion_tab_mode(self):
self.child.send("hel")
# Note that the ANSI_RESET color prevents us from accepting a longer
- # suggestion as a valid test outcome.
+ # suggestion as a valid test outcome. Because 'hel' has a single
+ # completion ('help'), tab-mode also shows that command's description
+ # in parentheses after the suggested 'p'.
+ help_description = (
+ "Show a list of all debugger commands, "
+ "or give details about a specific command."
+ )
self.child.expect_exact(
cursor_horizontal_abs("(lldb) he")
+ "l"
+ self.ANSI_FAINT
- + "p"
+ + "p (" + help_description + ")"
+ self.ANSI_RESET
)
# Applying the suggestion with Ctrl-F should leave the line as 'help'
- # (not 'help frame'). Running it lists all commands.
+ # (not 'help frame', and without the parenthesized description). Running
+ # it lists all commands.
self.child.send(ctrl_f + "\n")
self.child.expect_exact("Debugger commands:")
# Tab completion must still work in tab-mode. Pressing tab on 'hel'
- # should complete to 'help'; running it lists all commands.
- self.child.send("hel\t\n")
+ # should complete to 'help'; running it lists all commands. Accepting a
+ # completion must also clear the previously shown suggestion and its
+ # description from the line (rather than leaving the description behind).
+ self.child.send("hel")
+ self.child.expect_exact(
+ self.ANSI_FAINT + "p (" + help_description + ")" + self.ANSI_RESET
+ )
+ self.child.send("\t")
+ # The whole line is cleared (\r + erase-to-end-of-line) and redrawn as
+ # 'help ', so no part of the description is left on screen.
+ self.child.expect_exact(
+ "\r" + self.ANSI_CLEAR_RIGHT + "(lldb) help "
+ )
+ self.child.send("\n")
self.child.expect_exact("Debugger commands:")
+ @skipIfAsan
+ @skipIfEditlineSupportMissing
+ def test_autosuggestion_tab_mode_description_truncation(self):
+ """Test that a tab-mode suggestion (including its description) that is
+ wider than the remaining space on the line is truncated with an
+ ellipsis, so it does not wrap and corrupt the terminal."""
+ # Use a narrow terminal so the 'help' description does not fit.
+ self.launch(
+ use_colors=True,
+ dimensions=(24, 40),
+ extra_args=[
+ "-o",
+ "settings set show-autosuggestion tab-mode",
+ "-o",
+ "settings set use-color true",
+ ],
+ )
+
+ # The suggestion for 'hel' starts at column 10 ('(lldb) hel'), leaving
+ # 30 columns on the 40-wide terminal. The suggestion is truncated to
+ # those 30 columns, the last three being the ellipsis.
+ self.child.send("hel")
+ self.child.expect_exact(
+ cursor_horizontal_abs("(lldb) he")
+ + "l"
+ + self.ANSI_FAINT
+ + "p (Show a list of all debug..."
+ + self.ANSI_RESET
+ )
+
@skipIfAsan
@skipIfEditlineSupportMissing
def test_autosuggestion_tab_mode_no_common_prefix(self):
``````````
</details>
https://github.com/llvm/llvm-project/pull/208677
More information about the lldb-commits
mailing list