Skip to content

Commit 2841cdb

Browse files
authored
[lldb] Support format string in the prompt (llvm#123430)
Implement ansi::StripAnsiTerminalCodes and fix a long standing bug where using format strings in lldb's prompt resulted in an incorrect prompt column width.
1 parent ae13998 commit 2841cdb

File tree

7 files changed

+82
-24
lines changed

7 files changed

+82
-24
lines changed

lldb/include/lldb/Host/Editline.h

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ using namespace line_editor;
152152
class Editline {
153153
public:
154154
Editline(const char *editor_name, FILE *input_file, FILE *output_file,
155-
FILE *error_file, std::recursive_mutex &output_mutex);
155+
FILE *error_file, bool color, std::recursive_mutex &output_mutex);
156156

157157
~Editline();
158158

@@ -212,19 +212,23 @@ class Editline {
212212
}
213213

214214
void SetPromptAnsiPrefix(std::string prefix) {
215-
m_prompt_ansi_prefix = std::move(prefix);
215+
if (m_color)
216+
m_prompt_ansi_prefix = std::move(prefix);
216217
}
217218

218219
void SetPromptAnsiSuffix(std::string suffix) {
219-
m_prompt_ansi_suffix = std::move(suffix);
220+
if (m_color)
221+
m_prompt_ansi_suffix = std::move(suffix);
220222
}
221223

222224
void SetSuggestionAnsiPrefix(std::string prefix) {
223-
m_suggestion_ansi_prefix = std::move(prefix);
225+
if (m_color)
226+
m_suggestion_ansi_prefix = std::move(prefix);
224227
}
225228

226229
void SetSuggestionAnsiSuffix(std::string suffix) {
227-
m_suggestion_ansi_suffix = std::move(suffix);
230+
if (m_color)
231+
m_suggestion_ansi_suffix = std::move(suffix);
228232
}
229233

230234
/// Prompts for and reads a single line of user input.
@@ -400,6 +404,7 @@ class Editline {
400404
CompleteCallbackType m_completion_callback;
401405
SuggestionCallbackType m_suggestion_callback;
402406

407+
bool m_color;
403408
std::string m_prompt_ansi_prefix;
404409
std::string m_prompt_ansi_suffix;
405410
std::string m_suggestion_ansi_prefix;

lldb/include/lldb/Utility/AnsiTerminal.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,32 @@ inline std::string FormatAnsiTerminalCodes(llvm::StringRef format,
171171
}
172172
return fmt;
173173
}
174+
175+
inline std::string StripAnsiTerminalCodes(llvm::StringRef str) {
176+
std::string stripped;
177+
while (!str.empty()) {
178+
llvm::StringRef left, right;
179+
180+
std::tie(left, right) = str.split(ANSI_ESC_START);
181+
stripped += left;
182+
183+
// ANSI_ESC_START not found.
184+
if (left == str && right.empty())
185+
break;
186+
187+
size_t end = right.find_first_not_of("0123456789;");
188+
if (end < right.size() && (right[end] == 'm' || right[end] == 'G')) {
189+
str = right.substr(end + 1);
190+
} else {
191+
// ANSI_ESC_END not found.
192+
stripped += ANSI_ESC_START;
193+
str = right;
194+
}
195+
}
196+
return stripped;
174197
}
198+
199+
} // namespace ansi
175200
} // namespace lldb_private
176201

177202
#endif

lldb/source/Core/IOHandler.cpp

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ IOHandlerEditline::IOHandlerEditline(
264264
if (use_editline) {
265265
m_editline_up = std::make_unique<Editline>(editline_name, GetInputFILE(),
266266
GetOutputFILE(), GetErrorFILE(),
267-
GetOutputMutex());
267+
m_color, GetOutputMutex());
268268
m_editline_up->SetIsInputCompleteCallback(
269269
[this](Editline *editline, StringList &lines) {
270270
return this->IsInputCompleteCallback(editline, lines);
@@ -278,12 +278,10 @@ IOHandlerEditline::IOHandlerEditline(
278278
m_editline_up->SetSuggestionCallback([this](llvm::StringRef line) {
279279
return this->SuggestionCallback(line);
280280
});
281-
if (m_color) {
282-
m_editline_up->SetSuggestionAnsiPrefix(ansi::FormatAnsiTerminalCodes(
283-
debugger.GetAutosuggestionAnsiPrefix()));
284-
m_editline_up->SetSuggestionAnsiSuffix(ansi::FormatAnsiTerminalCodes(
285-
debugger.GetAutosuggestionAnsiSuffix()));
286-
}
281+
m_editline_up->SetSuggestionAnsiPrefix(ansi::FormatAnsiTerminalCodes(
282+
debugger.GetAutosuggestionAnsiPrefix()));
283+
m_editline_up->SetSuggestionAnsiSuffix(ansi::FormatAnsiTerminalCodes(
284+
debugger.GetAutosuggestionAnsiSuffix()));
287285
}
288286
// See if the delegate supports fixing indentation
289287
const char *indent_chars = delegate.IOHandlerGetFixIndentationCharacters();
@@ -478,12 +476,10 @@ bool IOHandlerEditline::SetPrompt(llvm::StringRef prompt) {
478476
#if LLDB_ENABLE_LIBEDIT
479477
if (m_editline_up) {
480478
m_editline_up->SetPrompt(m_prompt.empty() ? nullptr : m_prompt.c_str());
481-
if (m_color) {
482-
m_editline_up->SetPromptAnsiPrefix(
483-
ansi::FormatAnsiTerminalCodes(m_debugger.GetPromptAnsiPrefix()));
484-
m_editline_up->SetPromptAnsiSuffix(
485-
ansi::FormatAnsiTerminalCodes(m_debugger.GetPromptAnsiSuffix()));
486-
}
479+
m_editline_up->SetPromptAnsiPrefix(
480+
ansi::FormatAnsiTerminalCodes(m_debugger.GetPromptAnsiPrefix()));
481+
m_editline_up->SetPromptAnsiSuffix(
482+
ansi::FormatAnsiTerminalCodes(m_debugger.GetPromptAnsiSuffix()));
487483
}
488484
#endif
489485
return true;

lldb/source/Host/common/Editline.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "lldb/Host/Editline.h"
1515
#include "lldb/Host/FileSystem.h"
1616
#include "lldb/Host/Host.h"
17+
#include "lldb/Utility/AnsiTerminal.h"
1718
#include "lldb/Utility/CompletionRequest.h"
1819
#include "lldb/Utility/FileSpec.h"
1920
#include "lldb/Utility/LLDBAssert.h"
@@ -85,7 +86,8 @@ bool IsOnlySpaces(const EditLineStringType &content) {
8586
}
8687

8788
static size_t ColumnWidth(llvm::StringRef str) {
88-
return llvm::sys::locale::columnWidth(str);
89+
std::string stripped = ansi::StripAnsiTerminalCodes(str);
90+
return llvm::sys::locale::columnWidth(stripped);
8991
}
9092

9193
static int GetOperation(HistoryOperation op) {
@@ -610,7 +612,7 @@ int Editline::GetCharacter(EditLineGetCharType *c) {
610612
}
611613

612614
const char *Editline::Prompt() {
613-
if (!m_prompt_ansi_prefix.empty() || !m_prompt_ansi_suffix.empty())
615+
if (m_color)
614616
m_needs_prompt_repaint = true;
615617
return m_current_prompt.c_str();
616618
}
@@ -1471,11 +1473,11 @@ Editline *Editline::InstanceFor(EditLine *editline) {
14711473
}
14721474

14731475
Editline::Editline(const char *editline_name, FILE *input_file,
1474-
FILE *output_file, FILE *error_file,
1476+
FILE *output_file, FILE *error_file, bool color,
14751477
std::recursive_mutex &output_mutex)
14761478
: m_editor_status(EditorStatus::Complete), m_input_file(input_file),
14771479
m_output_file(output_file), m_error_file(error_file),
1478-
m_input_connection(fileno(input_file), false),
1480+
m_input_connection(fileno(input_file), false), m_color(color),
14791481
m_output_mutex(output_mutex) {
14801482
// Get a shared history instance
14811483
m_editor_name = (editline_name == nullptr) ? "lldb-tmp" : editline_name;

lldb/test/API/terminal/TestEditline.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
Test that the lldb editline handling is configured correctly.
33
"""
44

5-
65
import lldb
76
from lldbsuite.test.decorators import *
87
from lldbsuite.test.lldbtest import *
@@ -69,6 +68,22 @@ def test_prompt_color(self):
6968
# Column: 1....6.8
7069
self.child.expect(re.escape("\x1b[31m(lldb) \x1b[0m\x1b[8G"))
7170

71+
@skipIfAsan
72+
@skipIfEditlineSupportMissing
73+
def test_prompt_format_color(self):
74+
"""Test that we can change the prompt color with a format string."""
75+
self.launch(use_colors=True)
76+
# Clear the prefix and suffix setting to simplify the output.
77+
self.expect('settings set prompt-ansi-prefix ""')
78+
self.expect('settings set prompt-ansi-suffix ""')
79+
self.expect('settings set prompt "${ansi.fg.red}(lldb) ${ansi.normal}"')
80+
self.child.send("foo")
81+
# Make sure this change is reflected immediately. Check that the color
82+
# is set (31) and the cursor position (8) is correct.
83+
# Prompt: (lldb) _
84+
# Column: 1....6.8
85+
self.child.expect(re.escape("\x1b[31m(lldb) \x1b[0m\x1b[8Gfoo"))
86+
7287
@skipIfAsan
7388
@skipIfEditlineSupportMissing
7489
def test_prompt_no_color(self):

lldb/unittests/Editline/EditlineTest.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ EditlineAdapter::EditlineAdapter()
118118
// Create an Editline instance.
119119
_editline_sp.reset(new lldb_private::Editline(
120120
"gtest editor", *_el_secondary_file, *_el_secondary_file,
121-
*_el_secondary_file, output_mutex));
121+
*_el_secondary_file, /*color=*/false, output_mutex));
122122
_editline_sp->SetPrompt("> ");
123123

124124
// Hookup our input complete callback.

lldb/unittests/Utility/AnsiTerminalTest.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,21 @@ TEST(AnsiTerminal, Empty) { EXPECT_EQ("", ansi::FormatAnsiTerminalCodes("")); }
1616

1717
TEST(AnsiTerminal, WhiteSpace) {
1818
EXPECT_EQ(" ", ansi::FormatAnsiTerminalCodes(" "));
19+
EXPECT_EQ(" ", ansi::StripAnsiTerminalCodes(" "));
1920
}
2021

2122
TEST(AnsiTerminal, AtEnd) {
2223
EXPECT_EQ("abc\x1B[30m",
2324
ansi::FormatAnsiTerminalCodes("abc${ansi.fg.black}"));
25+
26+
EXPECT_EQ("abc", ansi::StripAnsiTerminalCodes("abc\x1B[30m"));
2427
}
2528

2629
TEST(AnsiTerminal, AtStart) {
2730
EXPECT_EQ("\x1B[30mabc",
2831
ansi::FormatAnsiTerminalCodes("${ansi.fg.black}abc"));
32+
33+
EXPECT_EQ("abc", ansi::StripAnsiTerminalCodes("\x1B[30mabc"));
2934
}
3035

3136
TEST(AnsiTerminal, KnownPrefix) {
@@ -45,10 +50,20 @@ TEST(AnsiTerminal, Incomplete) {
4550
TEST(AnsiTerminal, Twice) {
4651
EXPECT_EQ("\x1B[30m\x1B[31mabc",
4752
ansi::FormatAnsiTerminalCodes("${ansi.fg.black}${ansi.fg.red}abc"));
53+
54+
EXPECT_EQ("abc", ansi::StripAnsiTerminalCodes("\x1B[30m\x1B[31mabc"));
4855
}
4956

5057
TEST(AnsiTerminal, Basic) {
5158
EXPECT_EQ(
5259
"abc\x1B[31mabc\x1B[0mabc",
5360
ansi::FormatAnsiTerminalCodes("abc${ansi.fg.red}abc${ansi.normal}abc"));
61+
62+
EXPECT_EQ("abcabcabc",
63+
ansi::StripAnsiTerminalCodes("abc\x1B[31mabc\x1B[0mabc"));
64+
}
65+
66+
TEST(AnsiTerminal, InvalidEscapeCode) {
67+
EXPECT_EQ("abc\x1B[31kabcabc",
68+
ansi::StripAnsiTerminalCodes("abc\x1B[31kabc\x1B[0mabc"));
5469
}

0 commit comments

Comments
 (0)