Skip to content

Commit 28acbe5

Browse files
authored
Merge pull request swiftlang#7630 from apple/jdevlieghere/rdar/115390406
Cherrypick Unicode & Color Support
2 parents 234f778 + a082ffb commit 28acbe5

File tree

10 files changed

+126
-31
lines changed

10 files changed

+126
-31
lines changed

lldb/include/lldb/Core/Debugger.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,10 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
297297

298298
llvm::StringRef GetPrompt() const;
299299

300+
llvm::StringRef GetPromptAnsiPrefix() const;
301+
302+
llvm::StringRef GetPromptAnsiSuffix() const;
303+
300304
void SetPrompt(llvm::StringRef p);
301305
void SetPrompt(const char *) = delete;
302306

lldb/include/lldb/Host/Editline.h

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,14 @@ class Editline {
216216
m_fix_indentation_callback_chars = indent_chars;
217217
}
218218

219+
void SetPromptAnsiPrefix(std::string prefix) {
220+
m_prompt_ansi_prefix = std::move(prefix);
221+
}
222+
223+
void SetPromptAnsiSuffix(std::string suffix) {
224+
m_prompt_ansi_suffix = std::move(suffix);
225+
}
226+
219227
void SetSuggestionAnsiPrefix(std::string prefix) {
220228
m_suggestion_ansi_prefix = std::move(prefix);
221229
}
@@ -250,9 +258,8 @@ class Editline {
250258
void SetCurrentLine(int line_index);
251259

252260
/// Determines the width of the prompt in characters. The width is guaranteed
253-
/// to be the same for
254-
/// all lines of the current multi-line session.
255-
int GetPromptWidth();
261+
/// to be the same for all lines of the current multi-line session.
262+
size_t GetPromptWidth();
256263

257264
/// Returns true if the underlying EditLine session's keybindings are
258265
/// Emacs-based, or false if
@@ -404,6 +411,8 @@ class Editline {
404411
CompleteCallbackType m_completion_callback;
405412
SuggestionCallbackType m_suggestion_callback;
406413

414+
std::string m_prompt_ansi_prefix;
415+
std::string m_prompt_ansi_suffix;
407416
std::string m_suggestion_ansi_prefix;
408417
std::string m_suggestion_ansi_suffix;
409418

lldb/packages/Python/lldbsuite/test/decorators.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,6 +1102,10 @@ def skipIfEditlineSupportMissing(func):
11021102
return _get_bool_config_skip_if_decorator("editline")(func)
11031103

11041104

1105+
def skipIfEditlineWideCharSupportMissing(func):
1106+
return _get_bool_config_skip_if_decorator("editline_wchar")(func)
1107+
1108+
11051109
def skipIfFBSDVMCoreSupportMissing(func):
11061110
return _get_bool_config_skip_if_decorator("fbsdvmcore")(func)
11071111

lldb/packages/Python/lldbsuite/test/lldbpexpect.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ def launch(
2828
dimensions=None,
2929
run_under=None,
3030
post_spawn=None,
31+
encoding=None,
3132
use_colors=False,
3233
):
3334
logfile = getattr(sys.stdout, "buffer", sys.stdout) if self.TraceOn() else None
@@ -60,6 +61,7 @@ def launch(
6061
timeout=timeout,
6162
dimensions=dimensions,
6263
env=env,
64+
encoding=encoding,
6365
)
6466
self.child.ptyproc.delayafterclose = timeout / 10
6567
self.child.ptyproc.delayafterterminate = timeout / 10

lldb/source/API/SBDebugger.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -781,6 +781,9 @@ SBStructuredData SBDebugger::GetBuildConfiguration() {
781781
AddBoolConfigEntry(
782782
*config_up, "editline", LLDB_ENABLE_LIBEDIT,
783783
"A boolean value that indicates if editline support is enabled in LLDB");
784+
AddBoolConfigEntry(*config_up, "editline_wchar", LLDB_EDITLINE_USE_WCHAR,
785+
"A boolean value that indicates if editline wide "
786+
"characters support is enabled in LLDB");
784787
AddBoolConfigEntry(
785788
*config_up, "lzma", LLDB_ENABLE_LZMA,
786789
"A boolean value that indicates if lzma support is enabled in LLDB");

lldb/source/Core/CoreProperties.td

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,14 @@ let Definition = "debugger" in {
9999
DefaultEnumValue<"OptionValueString::eOptionEncodeCharacterEscapeSequences">,
100100
DefaultStringValue<"(lldb) ">,
101101
Desc<"The debugger command line prompt displayed for the user.">;
102+
def PromptAnsiPrefix: Property<"prompt-ansi-prefix", "String">,
103+
Global,
104+
DefaultStringValue<"${ansi.faint}">,
105+
Desc<"When in a color-enabled terminal, use the ANSI terminal code specified in this format immediately before the prompt.">;
106+
def PromptAnsiSuffix: Property<"prompt-ansi-suffix", "String">,
107+
Global,
108+
DefaultStringValue<"${ansi.normal}">,
109+
Desc<"When in a color-enabled terminal, use the ANSI terminal code specified in this format immediately after the prompt.">;
102110
def ScriptLanguage: Property<"script-lang", "Enum">,
103111
Global,
104112
DefaultEnumValue<"eScriptLanguagePython">,

lldb/source/Core/Debugger.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,13 @@ Status Debugger::SetPropertyValue(const ExecutionContext *exe_ctx,
235235
// use-color changed. Ping the prompt so it can reset the ansi terminal
236236
// codes.
237237
SetPrompt(GetPrompt());
238+
} else if (property_path ==
239+
g_debugger_properties[ePropertyPromptAnsiPrefix].name ||
240+
property_path ==
241+
g_debugger_properties[ePropertyPromptAnsiSuffix].name) {
242+
// Prompt colors changed. Ping the prompt so it can reset the ansi
243+
// terminal codes.
244+
SetPrompt(GetPrompt());
238245
} else if (property_path ==
239246
g_debugger_properties[ePropertyUseSourceCache].name) {
240247
// use-source-cache changed. Wipe out the cache contents if it was
@@ -301,6 +308,18 @@ llvm::StringRef Debugger::GetPrompt() const {
301308
idx, g_debugger_properties[idx].default_cstr_value);
302309
}
303310

311+
llvm::StringRef Debugger::GetPromptAnsiPrefix() const {
312+
const uint32_t idx = ePropertyPromptAnsiPrefix;
313+
return GetPropertyAtIndexAs<llvm::StringRef>(
314+
idx, g_debugger_properties[idx].default_cstr_value);
315+
}
316+
317+
llvm::StringRef Debugger::GetPromptAnsiSuffix() const {
318+
const uint32_t idx = ePropertyPromptAnsiSuffix;
319+
return GetPropertyAtIndexAs<llvm::StringRef>(
320+
idx, g_debugger_properties[idx].default_cstr_value);
321+
}
322+
304323
void Debugger::SetPrompt(llvm::StringRef p) {
305324
constexpr uint32_t idx = ePropertyPrompt;
306325
SetPropertyAtIndex(idx, p);

lldb/source/Core/IOHandler.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -474,8 +474,15 @@ bool IOHandlerEditline::SetPrompt(llvm::StringRef prompt) {
474474
m_prompt = std::string(prompt);
475475

476476
#if LLDB_ENABLE_LIBEDIT
477-
if (m_editline_up)
477+
if (m_editline_up) {
478478
m_editline_up->SetPrompt(m_prompt.empty() ? nullptr : m_prompt.c_str());
479+
if (m_debugger.GetUseColor()) {
480+
m_editline_up->SetPromptAnsiPrefix(
481+
ansi::FormatAnsiTerminalCodes(m_debugger.GetPromptAnsiPrefix()));
482+
m_editline_up->SetPromptAnsiSuffix(
483+
ansi::FormatAnsiTerminalCodes(m_debugger.GetPromptAnsiSuffix()));
484+
}
485+
}
479486
#endif
480487
return true;
481488
}

lldb/source/Host/common/Editline.cpp

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "lldb/Utility/Timeout.h"
2626

2727
#include "llvm/Support/FileSystem.h"
28+
#include "llvm/Support/Locale.h"
2829
#include "llvm/Support/Threading.h"
2930

3031
using namespace lldb_private;
@@ -52,10 +53,6 @@ int setupterm(char *term, int fildes, int *errret);
5253

5354
/// https://www.ecma-international.org/publications/files/ECMA-ST/Ecma-048.pdf
5455
#define ESCAPE "\x1b"
55-
/// Faint, decreased intensity or second colour.
56-
#define ANSI_FAINT ESCAPE "[2m"
57-
/// Normal colour or normal intensity (neither bold nor faint).
58-
#define ANSI_UNFAINT ESCAPE "[0m"
5956
#define ANSI_CLEAR_BELOW ESCAPE "[J"
6057
#define ANSI_CLEAR_RIGHT ESCAPE "[K"
6158
#define ANSI_SET_COLUMN_N ESCAPE "[%dG"
@@ -101,6 +98,10 @@ bool IsOnlySpaces(const EditLineStringType &content) {
10198
return true;
10299
}
103100

101+
static size_t ColumnWidth(llvm::StringRef str) {
102+
return llvm::sys::locale::columnWidth(str);
103+
}
104+
104105
static int GetOperation(HistoryOperation op) {
105106
// The naming used by editline for the history operations is counter
106107
// intuitive to how it's used in LLDB's editline implementation.
@@ -328,14 +329,16 @@ std::string Editline::PromptForIndex(int line_index) {
328329
std::string continuation_prompt = prompt;
329330
if (m_set_continuation_prompt.length() > 0) {
330331
continuation_prompt = m_set_continuation_prompt;
331-
332332
// Ensure that both prompts are the same length through space padding
333-
while (continuation_prompt.length() < prompt.length()) {
334-
continuation_prompt += ' ';
335-
}
336-
while (prompt.length() < continuation_prompt.length()) {
337-
prompt += ' ';
338-
}
333+
const size_t prompt_width = ColumnWidth(prompt);
334+
const size_t cont_prompt_width = ColumnWidth(continuation_prompt);
335+
const size_t padded_prompt_width =
336+
std::max(prompt_width, cont_prompt_width);
337+
if (prompt_width < padded_prompt_width)
338+
prompt += std::string(padded_prompt_width - prompt_width, ' ');
339+
else if (cont_prompt_width < padded_prompt_width)
340+
continuation_prompt +=
341+
std::string(padded_prompt_width - cont_prompt_width, ' ');
339342
}
340343

341344
if (use_line_numbers) {
@@ -353,7 +356,7 @@ void Editline::SetCurrentLine(int line_index) {
353356
m_current_prompt = PromptForIndex(line_index);
354357
}
355358

356-
int Editline::GetPromptWidth() { return (int)PromptForIndex(0).length(); }
359+
size_t Editline::GetPromptWidth() { return ColumnWidth(PromptForIndex(0)); }
357360

358361
bool Editline::IsEmacs() {
359362
const char *editor;
@@ -424,15 +427,13 @@ void Editline::MoveCursor(CursorLocation from, CursorLocation to) {
424427
void Editline::DisplayInput(int firstIndex) {
425428
fprintf(m_output_file, ANSI_SET_COLUMN_N ANSI_CLEAR_BELOW, 1);
426429
int line_count = (int)m_input_lines.size();
427-
const char *faint = m_color_prompts ? ANSI_FAINT : "";
428-
const char *unfaint = m_color_prompts ? ANSI_UNFAINT : "";
429-
430430
for (int index = firstIndex; index < line_count; index++) {
431-
fprintf(m_output_file, "%s"
432-
"%s"
433-
"%s" EditLineStringFormatSpec " ",
434-
faint, PromptForIndex(index).c_str(), unfaint,
435-
m_input_lines[index].c_str());
431+
fprintf(m_output_file,
432+
"%s"
433+
"%s"
434+
"%s" EditLineStringFormatSpec " ",
435+
m_prompt_ansi_prefix.c_str(), PromptForIndex(index).c_str(),
436+
m_prompt_ansi_suffix.c_str(), m_input_lines[index].c_str());
436437
if (index < line_count - 1)
437438
fprintf(m_output_file, "\n");
438439
}
@@ -441,7 +442,7 @@ void Editline::DisplayInput(int firstIndex) {
441442
int Editline::CountRowsForLine(const EditLineStringType &content) {
442443
std::string prompt =
443444
PromptForIndex(0); // Prompt width is constant during an edit session
444-
int line_length = (int)(content.length() + prompt.length());
445+
int line_length = (int)(content.length() + ColumnWidth(prompt));
445446
return (line_length / m_terminal_width) + 1;
446447
}
447448

@@ -541,14 +542,16 @@ unsigned char Editline::RecallHistory(HistoryOperation op) {
541542
int Editline::GetCharacter(EditLineGetCharType *c) {
542543
const LineInfoW *info = el_wline(m_editline);
543544

544-
// Paint a faint version of the desired prompt over the version libedit draws
545-
// (will only be requested if colors are supported)
545+
// Paint a ANSI formatted version of the desired prompt over the version
546+
// libedit draws. (will only be requested if colors are supported)
546547
if (m_needs_prompt_repaint) {
547548
MoveCursor(CursorLocation::EditingCursor, CursorLocation::EditingPrompt);
548-
fprintf(m_output_file, "%s"
549-
"%s"
550-
"%s",
551-
ANSI_FAINT, Prompt(), ANSI_UNFAINT);
549+
fprintf(m_output_file,
550+
"%s"
551+
"%s"
552+
"%s",
553+
m_prompt_ansi_prefix.c_str(), Prompt(),
554+
m_prompt_ansi_suffix.c_str());
552555
MoveCursor(CursorLocation::EditingPrompt, CursorLocation::EditingCursor);
553556
m_needs_prompt_repaint = false;
554557
}

lldb/test/API/terminal/TestEditline.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,39 @@ def test_left_right_arrow(self):
4444
)
4545

4646
self.quit()
47+
48+
@skipIfAsan
49+
@skipIfEditlineSupportMissing
50+
@skipIfEditlineWideCharSupportMissing
51+
def test_prompt_unicode(self):
52+
"""Test that we can use Unicode in the LLDB prompt."""
53+
self.launch(use_colors=True, encoding="utf-8")
54+
self.child.send('settings set prompt "🐛 "\n')
55+
# Check that the cursor is at position 4 ([4G)
56+
# Prompt: 🐛 _
57+
# Column: 1..4
58+
self.child.expect(re.escape("🐛 \x1b[0m\x1b[4G"))
59+
60+
@skipIfAsan
61+
@skipIfEditlineSupportMissing
62+
def test_prompt_color(self):
63+
"""Test that we can change the prompt color with prompt-ansi-prefix."""
64+
self.launch(use_colors=True)
65+
self.child.send('settings set prompt-ansi-prefix "${ansi.fg.red}"\n')
66+
# Make sure this change is reflected immediately. Check that the color
67+
# is set (31) and the cursor position (8) is correct.
68+
# Prompt: (lldb) _
69+
# Column: 1....6.8
70+
self.child.expect(re.escape("\x1b[31m(lldb) \x1b[0m\x1b[8G"))
71+
72+
@skipIfAsan
73+
@skipIfEditlineSupportMissing
74+
def test_prompt_no_color(self):
75+
"""Test that prompt-ansi-prefix doesn't color the prompt when colors are off."""
76+
self.launch(use_colors=False)
77+
self.child.send('settings set prompt-ansi-prefix "${ansi.fg.red}"\n')
78+
# Send foo so we can match the newline before the prompt and the foo
79+
# after the prompt.
80+
self.child.send("foo")
81+
# Check that there are no escape codes.
82+
self.child.expect(re.escape("\n(lldb) foo"))

0 commit comments

Comments
 (0)