Skip to content

Commit ed0cf28

Browse files
committed
[lldb] Implement a statusline in LLDB
Add a statusline to command-line LLDB to display progress events and other information related to the current state of the debugger. The statusline is a dedicated area displayed the bottom of the screen. The contents of the status line are configurable through a setting consisting of LLDB’s format strings. The statusline is configurable through the `statusline-format` setting. The default configuration shows the target name, the current file, the stop reason and the current progress event. ``` (lldb) settings show statusline-format statusline-format (format-string) = "${ansi.bg.cyan}${ansi.fg.black}{${target.file.basename}}{ | ${line.file.basename}:${line.number}:${line.column}}{ | ${thread.stop-reason}}{ | {${progress.count} }${progress.message}}" ``` The statusline is enabled by default, but can be disabled with the following setting: ``` (lldb) settings set show-statusline false ``` The statusline supersedes the current progress reporting implementation. Consequently, the following settings no longer have any effect (but continue to exist): ``` show-progress -- Whether to show progress or not if the debugger's output is an interactive color-enabled terminal. show-progress-ansi-prefix -- When displaying progress in a color-enabled terminal, use the ANSI terminal code specified in this format immediately before the progress message. show-progress-ansi-suffix -- When displaying progress in a color-enabled terminal, use the ANSI terminal code specified in this format immediately after the progress message. ``` RFC: https://discourse.llvm.org/t/rfc-lldb-statusline/83948
1 parent 51759ff commit ed0cf28

File tree

10 files changed

+375
-135
lines changed

10 files changed

+375
-135
lines changed

lldb/include/lldb/Core/Debugger.h

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "lldb/Core/FormatEntity.h"
2020
#include "lldb/Core/IOHandler.h"
2121
#include "lldb/Core/SourceManager.h"
22+
#include "lldb/Core/Statusline.h"
2223
#include "lldb/Core/UserSettingsController.h"
2324
#include "lldb/Host/HostThread.h"
2425
#include "lldb/Host/StreamFile.h"
@@ -308,6 +309,10 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
308309

309310
bool SetShowProgress(bool show_progress);
310311

312+
bool GetShowStatusline() const;
313+
314+
const FormatEntity::Entry *GetStatuslineFormat() const;
315+
311316
llvm::StringRef GetShowProgressAnsiPrefix() const;
312317

313318
llvm::StringRef GetShowProgressAnsiSuffix() const;
@@ -604,6 +609,14 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
604609
return m_source_file_cache;
605610
}
606611

612+
struct ProgressReport {
613+
uint64_t id;
614+
uint64_t completed;
615+
uint64_t total;
616+
std::string message;
617+
};
618+
std::optional<ProgressReport> GetCurrentProgressReport() const;
619+
607620
protected:
608621
friend class CommandInterpreter;
609622
friend class REPL;
@@ -653,6 +666,8 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
653666

654667
void PrintProgress(const ProgressEventData &data);
655668

669+
bool StatuslineSupported();
670+
656671
void PushIOHandler(const lldb::IOHandlerSP &reader_sp,
657672
bool cancel_top_handler = true);
658673

@@ -728,7 +743,7 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
728743
IOHandlerStack m_io_handler_stack;
729744
std::recursive_mutex m_io_handler_synchronous_mutex;
730745

731-
std::optional<uint64_t> m_current_event_id;
746+
std::optional<Statusline> m_statusline;
732747

733748
llvm::StringMap<std::weak_ptr<LogHandler>> m_stream_handlers;
734749
std::shared_ptr<CallbackLogHandler> m_callback_handler_sp;
@@ -745,6 +760,12 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
745760
lldb::TargetSP m_dummy_target_sp;
746761
Diagnostics::CallbackID m_diagnostics_callback_id;
747762

763+
/// Bookkeeping for command line progress events.
764+
/// @{
765+
llvm::SmallVector<ProgressReport, 4> m_progress_reports;
766+
mutable std::mutex m_progress_reports_mutex;
767+
/// @}
768+
748769
std::mutex m_destroy_callback_mutex;
749770
lldb::callback_token_t m_destroy_callback_next_token = 0;
750771
struct DestroyCallbackInfo {

lldb/include/lldb/Core/FormatEntity.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,9 @@ struct Entry {
100100
LineEntryColumn,
101101
LineEntryStartAddress,
102102
LineEntryEndAddress,
103-
CurrentPCArrow
103+
CurrentPCArrow,
104+
ProgressCount,
105+
ProgressMessage,
104106
};
105107

106108
struct Definition {

lldb/include/lldb/Core/Statusline.h

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//===-- Statusline.h -----------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLDB_CORE_STATUSLINE_H
10+
#define LLDB_CORE_STATUSLINE_H
11+
12+
#include "lldb/lldb-forward.h"
13+
#include "llvm/ADT/StringRef.h"
14+
#include <csignal>
15+
#include <string>
16+
17+
namespace lldb_private {
18+
class Statusline {
19+
public:
20+
Statusline(Debugger &debugger);
21+
~Statusline();
22+
23+
/// Reduce the scroll window and draw the statusline.
24+
void Enable();
25+
26+
/// Hide the statusline and extend the scroll window.
27+
void Disable();
28+
29+
/// Redraw the statusline. If update is false, this will redraw the last
30+
/// string.
31+
void Redraw(bool update = true);
32+
33+
/// Inform the statusline that the terminal dimensions have changed.
34+
void TerminalSizeChanged();
35+
36+
private:
37+
/// Draw the statusline with the given text.
38+
void Draw(llvm::StringRef msg);
39+
40+
/// Update terminal dimensions.
41+
void UpdateTerminalProperties();
42+
43+
/// Set the scroll window to the given height.
44+
void SetScrollWindow(uint64_t height);
45+
46+
/// Clear the statusline (without redrawing the background).
47+
void Reset();
48+
49+
Debugger &m_debugger;
50+
std::string m_last_str;
51+
52+
volatile std::sig_atomic_t m_terminal_size_has_changed = 1;
53+
uint64_t m_terminal_width = 0;
54+
uint64_t m_terminal_height = 0;
55+
uint64_t m_scroll_height = 0;
56+
57+
static constexpr llvm::StringLiteral k_ansi_suffix = "${ansi.normal}";
58+
};
59+
} // namespace lldb_private
60+
#endif // LLDB_CORE_STATUSLINE_H

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,8 @@ def setUpCommands(cls):
765765
# Disable fix-its by default so that incorrect expressions in tests don't
766766
# pass just because Clang thinks it has a fix-it.
767767
"settings set target.auto-apply-fixits false",
768+
# Disable the statusline in PExpect tests.
769+
"settings set show-statusline false",
768770
# Testsuite runs in parallel and the host can have also other load.
769771
"settings set plugin.process.gdb-remote.packet-timeout 60",
770772
'settings set symbols.clang-modules-cache-path "{}"'.format(

lldb/source/Core/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ add_lldb_library(lldbCore
4646
Opcode.cpp
4747
PluginManager.cpp
4848
Progress.cpp
49+
Statusline.cpp
4950
RichManglingContext.cpp
5051
SearchFilter.cpp
5152
Section.cpp

lldb/source/Core/CoreProperties.td

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,14 @@ let Definition = "debugger" in {
172172
Global,
173173
DefaultStringValue<"${ansi.normal}">,
174174
Desc<"When displaying progress in a color-enabled terminal, use the ANSI terminal code specified in this format immediately after the progress message.">;
175+
def ShowStatusline: Property<"show-statusline", "Boolean">,
176+
Global,
177+
DefaultTrue,
178+
Desc<"Whether to show a statusline at the bottom of the terminal.">;
179+
def StatuslineFormat: Property<"statusline-format", "FormatEntity">,
180+
Global,
181+
DefaultStringValue<"${ansi.bg.blue}${ansi.fg.black}{${target.file.basename}}{ | ${line.file.basename}:${line.number}:${line.column}}{ | ${thread.stop-reason}}{ | {${progress.count} }${progress.message}}">,
182+
Desc<"List of statusline format entities.">;
175183
def UseSourceCache: Property<"use-source-cache", "Boolean">,
176184
Global,
177185
DefaultTrue,

lldb/source/Core/Debugger.cpp

Lines changed: 82 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,19 @@ Status Debugger::SetPropertyValue(const ExecutionContext *exe_ctx,
243243
// Prompt colors changed. Ping the prompt so it can reset the ansi
244244
// terminal codes.
245245
SetPrompt(GetPrompt());
246+
} else if (property_path ==
247+
g_debugger_properties[ePropertyShowStatusline].name) {
248+
// Statusline setting changed. If we have a statusline instance, update it
249+
// now. Otherwise it will get created in the default event handler.
250+
if (StatuslineSupported())
251+
m_statusline.emplace(*this);
252+
else
253+
m_statusline.reset();
254+
} else if (property_path ==
255+
g_debugger_properties[ePropertyStatuslineFormat].name) {
256+
// Statusline format changed. Redraw the statusline.
257+
if (m_statusline)
258+
m_statusline->Redraw();
246259
} else if (property_path ==
247260
g_debugger_properties[ePropertyUseSourceCache].name) {
248261
// use-source-cache changed. Wipe out the cache contents if it was
@@ -376,6 +389,8 @@ bool Debugger::SetTerminalWidth(uint64_t term_width) {
376389

377390
if (auto handler_sp = m_io_handler_stack.Top())
378391
handler_sp->TerminalSizeChanged();
392+
if (m_statusline)
393+
m_statusline->TerminalSizeChanged();
379394

380395
return success;
381396
}
@@ -392,6 +407,8 @@ bool Debugger::SetTerminalHeight(uint64_t term_height) {
392407

393408
if (auto handler_sp = m_io_handler_stack.Top())
394409
handler_sp->TerminalSizeChanged();
410+
if (m_statusline)
411+
m_statusline->TerminalSizeChanged();
395412

396413
return success;
397414
}
@@ -454,6 +471,17 @@ llvm::StringRef Debugger::GetShowProgressAnsiSuffix() const {
454471
idx, g_debugger_properties[idx].default_cstr_value);
455472
}
456473

474+
bool Debugger::GetShowStatusline() const {
475+
const uint32_t idx = ePropertyShowStatusline;
476+
return GetPropertyAtIndexAs<bool>(
477+
idx, g_debugger_properties[idx].default_uint_value != 0);
478+
}
479+
480+
const FormatEntity::Entry *Debugger::GetStatuslineFormat() const {
481+
constexpr uint32_t idx = ePropertyStatuslineFormat;
482+
return GetPropertyAtIndexAs<const FormatEntity::Entry *>(idx);
483+
}
484+
457485
bool Debugger::GetUseAutosuggestion() const {
458486
const uint32_t idx = ePropertyShowAutosuggestion;
459487
return GetPropertyAtIndexAs<bool>(
@@ -1093,12 +1121,18 @@ void Debugger::SetErrorFile(FileSP file_sp) {
10931121
}
10941122

10951123
void Debugger::SaveInputTerminalState() {
1124+
if (m_statusline)
1125+
m_statusline->Disable();
10961126
int fd = GetInputFile().GetDescriptor();
10971127
if (fd != File::kInvalidDescriptor)
10981128
m_terminal_state.Save(fd, true);
10991129
}
11001130

1101-
void Debugger::RestoreInputTerminalState() { m_terminal_state.Restore(); }
1131+
void Debugger::RestoreInputTerminalState() {
1132+
m_terminal_state.Restore();
1133+
if (m_statusline)
1134+
m_statusline->Enable();
1135+
}
11021136

11031137
ExecutionContext Debugger::GetSelectedExecutionContext() {
11041138
bool adopt_selected = true;
@@ -1920,6 +1954,15 @@ void Debugger::CancelForwardEvents(const ListenerSP &listener_sp) {
19201954
m_forward_listener_sp.reset();
19211955
}
19221956

1957+
bool Debugger::StatuslineSupported() {
1958+
if (GetShowStatusline()) {
1959+
File &file = GetOutputFile();
1960+
return file.GetIsInteractive() && file.GetIsRealTerminal() &&
1961+
file.GetIsTerminalWithColors();
1962+
}
1963+
return false;
1964+
}
1965+
19231966
lldb::thread_result_t Debugger::DefaultEventHandler() {
19241967
ListenerSP listener_sp(GetListener());
19251968
ConstString broadcaster_class_target(Target::GetStaticBroadcasterClass());
@@ -1959,6 +2002,9 @@ lldb::thread_result_t Debugger::DefaultEventHandler() {
19592002
// are now listening to all required events so no events get missed
19602003
m_sync_broadcaster.BroadcastEvent(eBroadcastBitEventThreadIsListening);
19612004

2005+
if (!m_statusline && StatuslineSupported())
2006+
m_statusline.emplace(*this);
2007+
19622008
bool done = false;
19632009
while (!done) {
19642010
EventSP event_sp;
@@ -2017,8 +2063,14 @@ lldb::thread_result_t Debugger::DefaultEventHandler() {
20172063
if (m_forward_listener_sp)
20182064
m_forward_listener_sp->AddEvent(event_sp);
20192065
}
2066+
if (m_statusline)
2067+
m_statusline->Redraw();
20202068
}
20212069
}
2070+
2071+
if (m_statusline)
2072+
m_statusline.reset();
2073+
20222074
return {};
20232075
}
20242076

@@ -2081,84 +2133,39 @@ void Debugger::HandleProgressEvent(const lldb::EventSP &event_sp) {
20812133
if (!data)
20822134
return;
20832135

2084-
// Do some bookkeeping for the current event, regardless of whether we're
2085-
// going to show the progress.
2086-
const uint64_t id = data->GetID();
2087-
if (m_current_event_id) {
2088-
Log *log = GetLog(LLDBLog::Events);
2089-
if (log && log->GetVerbose()) {
2090-
StreamString log_stream;
2091-
log_stream.AsRawOstream()
2092-
<< static_cast<void *>(this) << " Debugger(" << GetID()
2093-
<< ")::HandleProgressEvent( m_current_event_id = "
2094-
<< *m_current_event_id << ", data = { ";
2095-
data->Dump(&log_stream);
2096-
log_stream << " } )";
2097-
log->PutString(log_stream.GetString());
2098-
}
2099-
if (id != *m_current_event_id)
2100-
return;
2101-
if (data->GetCompleted() == data->GetTotal())
2102-
m_current_event_id.reset();
2103-
} else {
2104-
m_current_event_id = id;
2105-
}
2106-
2107-
// Decide whether we actually are going to show the progress. This decision
2108-
// can change between iterations so check it inside the loop.
2109-
if (!GetShowProgress())
2110-
return;
2111-
2112-
// Determine whether the current output file is an interactive terminal with
2113-
// color support. We assume that if we support ANSI escape codes we support
2114-
// vt100 escape codes.
2115-
File &file = GetOutputFile();
2116-
if (!file.GetIsInteractive() || !file.GetIsTerminalWithColors())
2117-
return;
2118-
2119-
StreamSP output = GetAsyncOutputStream();
2120-
2121-
// Print over previous line, if any.
2122-
output->Printf("\r");
2136+
// Make a local copy of the incoming progress report that we'll store.
2137+
ProgressReport progress_report{data->GetID(), data->GetCompleted(),
2138+
data->GetTotal(), data->GetMessage()};
21232139

2124-
if (data->GetCompleted() == data->GetTotal()) {
2125-
// Clear the current line.
2126-
output->Printf("\x1B[2K");
2127-
output->Flush();
2128-
return;
2140+
// Do some bookkeeping regardless of whether we're going to display
2141+
// progress reports.
2142+
{
2143+
std::lock_guard<std::mutex> guard(m_progress_reports_mutex);
2144+
auto it = std::find_if(
2145+
m_progress_reports.begin(), m_progress_reports.end(),
2146+
[&](const auto &report) { return report.id == progress_report.id; });
2147+
if (it != m_progress_reports.end()) {
2148+
const bool complete = data->GetCompleted() == data->GetTotal();
2149+
if (complete)
2150+
m_progress_reports.erase(it);
2151+
else
2152+
*it = progress_report;
2153+
} else {
2154+
m_progress_reports.push_back(progress_report);
2155+
}
21292156
}
21302157

2131-
// Trim the progress message if it exceeds the window's width and print it.
2132-
std::string message = data->GetMessage();
2133-
if (data->IsFinite())
2134-
message = llvm::formatv("[{0}/{1}] {2}", data->GetCompleted(),
2135-
data->GetTotal(), message)
2136-
.str();
2137-
2138-
// Trim the progress message if it exceeds the window's width and print it.
2139-
const uint32_t term_width = GetTerminalWidth();
2140-
const uint32_t ellipsis = 3;
2141-
if (message.size() + ellipsis >= term_width)
2142-
message.resize(term_width - ellipsis);
2143-
2144-
const bool use_color = GetUseColor();
2145-
llvm::StringRef ansi_prefix = GetShowProgressAnsiPrefix();
2146-
if (!ansi_prefix.empty())
2147-
output->Printf(
2148-
"%s", ansi::FormatAnsiTerminalCodes(ansi_prefix, use_color).c_str());
2149-
2150-
output->Printf("%s...", message.c_str());
2151-
2152-
llvm::StringRef ansi_suffix = GetShowProgressAnsiSuffix();
2153-
if (!ansi_suffix.empty())
2154-
output->Printf(
2155-
"%s", ansi::FormatAnsiTerminalCodes(ansi_suffix, use_color).c_str());
2156-
2157-
// Clear until the end of the line.
2158-
output->Printf("\x1B[K\r");
2158+
// Redraw the statusline if enabled.
2159+
if (m_statusline)
2160+
m_statusline->Redraw();
2161+
}
21592162

2160-
// Flush the output.
2161-
output->Flush();
2163+
std::optional<Debugger::ProgressReport>
2164+
Debugger::GetCurrentProgressReport() const {
2165+
std::lock_guard<std::mutex> guard(m_progress_reports_mutex);
2166+
if (m_progress_reports.empty())
2167+
return std::nullopt;
2168+
return m_progress_reports.back();
21622169
}
21632170

21642171
void Debugger::HandleDiagnosticEvent(const lldb::EventSP &event_sp) {

0 commit comments

Comments
 (0)