-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[lldb] Improve editline completion formatting #116456
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
I've made this a draft because this still needs a test. |
This patch improves the formatting of editline completions. The current implementation is naive and doesn't account for the terminal width. Concretely, the old implementation suffered from the following issues: - We would unconditionally pad to the longest completion. If that completion exceeds the width of the terminal, that would result in a lot of superfluous white space and line wrapping. - When printing the description, we wouldn't account for the presence of newlines, and they would continue without leading padding. The new code accounts for both. If the completion exceeds the available terminal width, we show what fits on the current lined followed by ellipsis. We also no longer pad beyond the length of the current line. Finally, we print the description line by line, with the proper leading padding. If a line of the description exceeds the available terminal width, we print ellipsis and won't print the next line. Before: ``` Available completions: _regexp-attach -- Attach to process by ID or name. _regexp-break -- Set a breakpoint using one of several shorthand formats. _regexp-bt -- Show backtrace of the current thread's call sta ck. Any numeric argument displays at most that many frames. The argument 'al l' displays all threads. Use 'settings set frame-format' to customize the pr inting of individual frames and 'settings set thread-format' to customize th e thread header. Frame recognizers may filter thelist. Use 'thread backtrace -u (--unfiltered)' to see them all. _regexp-display -- Evaluate an expression at every stop (see 'help target stop-hook'.) ``` After: ``` Available completions: _regexp-attach -- Attach to process by ID or name. _regexp-break -- Set a breakpoint using one of several shorth... _regexp-bt -- Show backtrace of the current thread's call ... _regexp-display -- Evaluate an expression at every stop (see 'h... ``` rdar://135818198
a694405
to
3434c7d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The string manipulations are fairly crude. I tried to suggest replacements using more advanced features. I'm not sure I got all of the offset calculations right, but I think they demonstrate the general idea, which is:
- using existing printf features (e.g. for padding and size limiting) instead of rolling your own
- avoiding explicit cursor position tracking by ensuring each block of code always ends at a predefined position.
@llvm/pr-subscribers-lldb Author: Jonas Devlieghere (JDevlieghere) ChangesThis patch improves the formatting of editline completions. The current implementation is naive and doesn't account for the terminal width. Concretely, the old implementation suffered from the following issues:
The new code accounts for both. If the completion exceeds the available terminal width, we show what fits on the current lined followed by ellipsis. We also no longer pad beyond the length of the current line. Finally, we print the description line by line, with the proper leading padding. If a line of the description exceeds the available terminal width, we print ellipsis and won't print the next line. Before:
After:
rdar://135818198 Full diff: https://github.com/llvm/llvm-project/pull/116456.diff 3 Files Affected:
diff --git a/lldb/include/lldb/Host/Editline.h b/lldb/include/lldb/Host/Editline.h
index 57e2c831e3499d..e8e8a6c0d4f67e 100644
--- a/lldb/include/lldb/Host/Editline.h
+++ b/lldb/include/lldb/Host/Editline.h
@@ -238,6 +238,8 @@ class Editline {
/// Convert the current input lines into a UTF8 StringList
StringList GetInputAsStringList(int line_count = UINT32_MAX);
+ size_t GetTerminalWidth() { return m_terminal_width; }
+
private:
/// Sets the lowest line number for multi-line editing sessions. A value of
/// zero suppresses line number printing in the prompt.
diff --git a/lldb/source/Host/common/Editline.cpp b/lldb/source/Host/common/Editline.cpp
index f95f854c5f220c..3ecf973f29f269 100644
--- a/lldb/source/Host/common/Editline.cpp
+++ b/lldb/source/Host/common/Editline.cpp
@@ -927,12 +927,88 @@ unsigned char Editline::BufferEndCommand(int ch) {
static void
PrintCompletion(FILE *output_file,
llvm::ArrayRef<CompletionResult::Completion> results,
- size_t max_len) {
+ size_t max_completion_length, size_t max_length) {
+ constexpr size_t ellipsis_length = 3;
+ constexpr size_t padding_length = 8;
+ constexpr size_t separator_length = 4;
+
+ const size_t description_col =
+ std::min(max_completion_length + padding_length, max_length);
+
for (const CompletionResult::Completion &c : results) {
- fprintf(output_file, "\t%-*s", (int)max_len, c.GetCompletion().c_str());
- if (!c.GetDescription().empty())
- fprintf(output_file, " -- %s", c.GetDescription().c_str());
- fprintf(output_file, "\n");
+ if (c.GetCompletion().empty())
+ continue;
+ ;
+
+ // Print the leading padding.
+ fprintf(output_file, " ");
+
+ // Print the completion with trailing padding to the description column if
+ // that fits on the screen. Otherwise print whatever fits on the screen
+ // followed by ellipsis.
+ const size_t completion_length = c.GetCompletion().size();
+ if (padding_length + completion_length < max_length) {
+ fprintf(output_file, "%-*s",
+ static_cast<int>(description_col - padding_length),
+ c.GetCompletion().c_str());
+ } else {
+ // If the completion doesn't fit on the screen, print ellipsis and don't
+ // bother with the description.
+ fprintf(output_file, "%s...\n",
+ c.GetCompletion()
+ .substr(0, max_length - padding_length - ellipsis_length)
+ .c_str());
+ continue;
+ }
+
+ // If we don't have a description, or we don't have enough space left to
+ // print the separator followed by the ellipsis, we're done.
+ if (c.GetDescription().empty() ||
+ description_col + separator_length + ellipsis_length >= max_length) {
+ fprintf(output_file, "\n");
+ continue;
+ }
+
+ // Print the separator.
+ fprintf(output_file, " -- ");
+
+ // Descriptions can contain newlines. We want to print them below each
+ // other, aligned after the separator. For example, foo has a
+ // two-line description:
+ //
+ // foo -- Something that fits on the line.
+ // More information below.
+ //
+ // However, as soon as a line exceed the available screen width and
+ // print ellipsis, we don't print the next line. For example, foo has a
+ // three-line description:
+ //
+ // foo -- Something that fits on the line.
+ // Something much longer that doesn't fit...
+ //
+ // Because we had to print ellipsis on line two, we don't print the
+ // third line.
+ bool first = true;
+ for (llvm::StringRef line : llvm::split(c.GetDescription(), '\n')) {
+ if (line.empty())
+ break;
+ if (!first)
+ fprintf(output_file, "%*s",
+ static_cast<int>(description_col + separator_length), "");
+
+ first = false;
+ const size_t position = description_col + separator_length;
+ const size_t description_lenth = line.size();
+ if (position + description_lenth < max_length) {
+ fprintf(output_file, "%s\n", line.str().c_str());
+ } else {
+ fprintf(output_file, "%s...\n",
+ line.substr(0, max_length - position - ellipsis_length)
+ .str()
+ .c_str());
+ continue;
+ }
+ }
}
}
@@ -953,7 +1029,8 @@ void Editline::DisplayCompletions(
const size_t max_len = longest->GetCompletion().size();
if (results.size() < page_size) {
- PrintCompletion(editline.m_output_file, results, max_len);
+ PrintCompletion(editline.m_output_file, results, max_len,
+ editline.GetTerminalWidth());
return;
}
@@ -963,7 +1040,7 @@ void Editline::DisplayCompletions(
size_t next_size = all ? remaining : std::min(page_size, remaining);
PrintCompletion(editline.m_output_file, results.slice(cur_pos, next_size),
- max_len);
+ max_len, editline.GetTerminalWidth());
cur_pos += next_size;
diff --git a/lldb/test/API/terminal/TestEditlineCompletions.py b/lldb/test/API/terminal/TestEditlineCompletions.py
new file mode 100644
index 00000000000000..5e8d5c767237b4
--- /dev/null
+++ b/lldb/test/API/terminal/TestEditlineCompletions.py
@@ -0,0 +1,70 @@
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+from lldbsuite.test.lldbpexpect import PExpectTest
+
+
+class EditlineCompletionsTest(PExpectTest):
+
+ @skipIfAsan
+ @skipIfEditlineSupportMissing
+ @skipIfEditlineWideCharSupportMissing
+ def test_completion_truncated(self):
+ """Test that the completion is correctly truncated."""
+ self.launch(dimensions=(10, 20))
+ self.child.send("_regexp-\t")
+ self.child.expect(" _regexp-a...")
+ self.child.expect(" _regexp-b...")
+
+ @skipIfAsan
+ @skipIfEditlineSupportMissing
+ @skipIfEditlineWideCharSupportMissing
+ def test_description_truncated(self):
+ """Test that the description is correctly truncated."""
+ self.launch(dimensions=(10, 70))
+ self.child.send("_regexp-\t")
+ self.child.expect(
+ " _regexp-attach -- Attach to process by ID or name."
+ )
+ self.child.expect(
+ " _regexp-break -- Set a breakpoint using one of several..."
+ )
+
+ @skipIfAsan
+ @skipIfEditlineSupportMissing
+ @skipIfEditlineWideCharSupportMissing
+ def test_separator_omitted(self):
+ """Test that the separated is correctly omitted."""
+ self.launch(dimensions=(10, 32), timeout=1)
+ self.child.send("_regexp-\t")
+ self.child.expect(" _regexp-attach \r\n")
+ self.child.expect(" _regexp-break \r\n")
+
+ @skipIfAsan
+ @skipIfEditlineSupportMissing
+ @skipIfEditlineWideCharSupportMissing
+ def test_separator(self):
+ """Test that the separated is correctly printed."""
+ self.launch(dimensions=(10, 33), timeout=1)
+ self.child.send("_regexp-\t")
+ self.child.expect(" _regexp-attach -- A...")
+ self.child.expect(" _regexp-break -- S...")
+
+ @skipIfAsan
+ @skipIfEditlineSupportMissing
+ @skipIfEditlineWideCharSupportMissing
+ def test_multiline_description(self):
+ """Test that multi-line descriptions are correctly padded and truncated."""
+ self.launch(dimensions=(10, 72), timeout=1)
+ self.child.send("k\t")
+ self.child.expect(
+ " kdp-remote -- Connect to a process via remote KDP server."
+ )
+ self.child.expect(
+ " If no UDP port is specified, port 41139 is assu..."
+ )
+ self.child.expect(
+ " kdp-remote is an abbreviation for 'process conn..."
+ )
+ self.child.expect(" kill -- Terminate the current target process.")
|
✅ With the latest revision this PR passed the Python code formatter. |
The change makes this more useful for completion, where you are more getting reminded than reading the whole help. LGTM. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Much better, thanks.
This patch improves the formatting of editline completions. The current implementation is naive and doesn't account for the terminal width. Concretely, the old implementation suffered from the following issues: - We would unconditionally pad to the longest completion. If that completion exceeds the width of the terminal, that would result in a lot of superfluous white space and line wrapping. - When printing the description, we wouldn't account for the presence of newlines, and they would continue without leading padding. The new code accounts for both. If the completion exceeds the available terminal width, we show what fits on the current lined followed by ellipsis. We also no longer pad beyond the length of the current line. Finally, we print the description line by line, with the proper leading padding. If a line of the description exceeds the available terminal width, we print ellipsis and won't print the next line. Before: ``` Available completions: _regexp-attach -- Attach to process by ID or name. _regexp-break -- Set a breakpoint using one of several shorthand formats. _regexp-bt -- Show backtrace of the current thread's call sta ck. Any numeric argument displays at most that many frames. The argument 'al l' displays all threads. Use 'settings set frame-format' to customize the pr inting of individual frames and 'settings set thread-format' to customize th e thread header. Frame recognizers may filter thelist. Use 'thread backtrace -u (--unfiltered)' to see them all. _regexp-display -- Evaluate an expression at every stop (see 'help target stop-hook'.) ``` After: ``` Available completions: _regexp-attach -- Attach to process by ID or name. _regexp-break -- Set a breakpoint using one of several shorth... _regexp-bt -- Show backtrace of the current thread's call ... _regexp-display -- Evaluate an expression at every stop (see 'h... ``` rdar://135818198 (cherry picked from commit dd78d7c)
…78d7c7be5b [🍒 stable/20240723] [lldb] Improve editline completion formatting (llvm#116456)
This patch improves the formatting of editline completions. The current implementation is naive and doesn't account for the terminal width. Concretely, the old implementation suffered from the following issues: - We would unconditionally pad to the longest completion. If that completion exceeds the width of the terminal, that would result in a lot of superfluous white space and line wrapping. - When printing the description, we wouldn't account for the presence of newlines, and they would continue without leading padding. The new code accounts for both. If the completion exceeds the available terminal width, we show what fits on the current lined followed by ellipsis. We also no longer pad beyond the length of the current line. Finally, we print the description line by line, with the proper leading padding. If a line of the description exceeds the available terminal width, we print ellipsis and won't print the next line. Before: ``` Available completions: _regexp-attach -- Attach to process by ID or name. _regexp-break -- Set a breakpoint using one of several shorthand formats. _regexp-bt -- Show backtrace of the current thread's call sta ck. Any numeric argument displays at most that many frames. The argument 'al l' displays all threads. Use 'settings set frame-format' to customize the pr inting of individual frames and 'settings set thread-format' to customize th e thread header. Frame recognizers may filter thelist. Use 'thread backtrace -u (--unfiltered)' to see them all. _regexp-display -- Evaluate an expression at every stop (see 'help target stop-hook'.) ``` After: ``` Available completions: _regexp-attach -- Attach to process by ID or name. _regexp-break -- Set a breakpoint using one of several shorth... _regexp-bt -- Show backtrace of the current thread's call ... _regexp-display -- Evaluate an expression at every stop (see 'h... ``` rdar://135818198 (cherry picked from commit dd78d7c)
Release note improvements to editline completion (llvm#116456).
Currently, we arbitrarily paginate editline completions to 40 elements. On large terminals, that leaves some real-estate unused. On small terminals, it's pretty annoying to not see the first completions. We can address both issues by using the terminal height for pagination. This builds on the improvements of #116456.
…119914) Currently, we arbitrarily paginate editline completions to 40 elements. On large terminals, that leaves some real-estate unused. On small terminals, it's pretty annoying to not see the first completions. We can address both issues by using the terminal height for pagination. This builds on the improvements of llvm#116456. (cherry picked from commit 3dfc1d9)
This patch improves the formatting of editline completions. The current implementation is naive and doesn't account for the terminal width.
Concretely, the old implementation suffered from the following issues:
The new code accounts for both. If the completion exceeds the available terminal width, we show what fits on the current lined followed by ellipsis. We also no longer pad beyond the length of the current line. Finally, we print the description line by line, with the proper leading padding. If a line of the description exceeds the available terminal width, we print ellipsis and won't print the next line.
Before:
After:
rdar://135818198