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
_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to