Skip to content

Commit b0cf540

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 195a1fc commit b0cf540

File tree

9 files changed

+373
-84
lines changed

9 files changed

+373
-84
lines changed

lldb/include/lldb/Core/Debugger.h

Lines changed: 20 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;
@@ -728,7 +741,7 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
728741
IOHandlerStack m_io_handler_stack;
729742
std::recursive_mutex m_io_handler_synchronous_mutex;
730743

731-
std::optional<uint64_t> m_current_event_id;
744+
std::optional<Statusline> m_statusline;
732745

733746
llvm::StringMap<std::weak_ptr<LogHandler>> m_stream_handlers;
734747
std::shared_ptr<CallbackLogHandler> m_callback_handler_sp;
@@ -745,6 +758,12 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
745758
lldb::TargetSP m_dummy_target_sp;
746759
Diagnostics::CallbackID m_diagnostics_callback_id;
747760

761+
/// Bookkeeping for command line progress events.
762+
/// @{
763+
llvm::SmallVector<ProgressReport, 4> m_progress_reports;
764+
mutable std::mutex m_progress_reports_mutex;
765+
/// @}
766+
748767
std::mutex m_destroy_callback_mutex;
749768
lldb::callback_token_t m_destroy_callback_next_token = 0;
750769
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: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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_STATUSBAR_H
10+
#define LLDB_CORE_STATUSBAR_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+
/// Return true if the output is an interactive terminal with colors.
50+
bool IsSupported() const;
51+
52+
Debugger &m_debugger;
53+
std::string m_last_str;
54+
55+
volatile std::sig_atomic_t m_terminal_size_has_changed = 1;
56+
uint64_t m_terminal_width = 0;
57+
uint64_t m_terminal_height = 0;
58+
uint64_t m_scroll_height = 0;
59+
60+
static constexpr llvm::StringLiteral k_ansi_suffix = "${ansi.normal}";
61+
};
62+
} // namespace lldb_private
63+
#endif // LLDB_CORE_STATUSBAR_H

lldb/include/lldb/Utility/AnsiTerminal.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
#include "llvm/ADT/ArrayRef.h"
7474
#include "llvm/ADT/STLExtras.h"
7575
#include "llvm/ADT/StringRef.h"
76+
#include "llvm/Support/Regex.h"
7677

7778
#include <string>
7879

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: 73 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 (GetShowStatusline())
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;
@@ -1959,6 +1993,9 @@ lldb::thread_result_t Debugger::DefaultEventHandler() {
19591993
// are now listening to all required events so no events get missed
19601994
m_sync_broadcaster.BroadcastEvent(eBroadcastBitEventThreadIsListening);
19611995

1996+
if (!m_statusline && GetShowStatusline())
1997+
m_statusline.emplace(*this);
1998+
19621999
bool done = false;
19632000
while (!done) {
19642001
EventSP event_sp;
@@ -2017,8 +2054,14 @@ lldb::thread_result_t Debugger::DefaultEventHandler() {
20172054
if (m_forward_listener_sp)
20182055
m_forward_listener_sp->AddEvent(event_sp);
20192056
}
2057+
if (m_statusline)
2058+
m_statusline->Redraw();
20202059
}
20212060
}
2061+
2062+
if (m_statusline)
2063+
m_statusline.reset();
2064+
20222065
return {};
20232066
}
20242067

@@ -2081,84 +2124,39 @@ void Debugger::HandleProgressEvent(const lldb::EventSP &event_sp) {
20812124
if (!data)
20822125
return;
20832126

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-
}
2127+
// Make a local copy of the incoming progress report that we'll store.
2128+
ProgressReport progress_report{data->GetID(), data->GetCompleted(),
2129+
data->GetTotal(), data->GetMessage()};
21062130

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");
2123-
2124-
if (data->GetCompleted() == data->GetTotal()) {
2125-
// Clear the current line.
2126-
output->Printf("\x1B[2K");
2127-
output->Flush();
2128-
return;
2131+
// Do some bookkeeping regardless of whether we're going to display
2132+
// progress reports.
2133+
{
2134+
std::lock_guard<std::mutex> guard(m_progress_reports_mutex);
2135+
auto it = std::find_if(
2136+
m_progress_reports.begin(), m_progress_reports.end(),
2137+
[&](const auto &report) { return report.id == progress_report.id; });
2138+
if (it != m_progress_reports.end()) {
2139+
const bool complete = data->GetCompleted() == data->GetTotal();
2140+
if (complete)
2141+
m_progress_reports.erase(it);
2142+
else
2143+
*it = progress_report;
2144+
} else {
2145+
m_progress_reports.push_back(progress_report);
2146+
}
21292147
}
21302148

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");
2149+
// Redraw the statusline if enabled.
2150+
if (m_statusline)
2151+
m_statusline->Redraw();
2152+
}
21592153

2160-
// Flush the output.
2161-
output->Flush();
2154+
std::optional<Debugger::ProgressReport>
2155+
Debugger::GetCurrentProgressReport() const {
2156+
std::lock_guard<std::mutex> guard(m_progress_reports_mutex);
2157+
if (m_progress_reports.empty())
2158+
return std::nullopt;
2159+
return m_progress_reports.back();
21622160
}
21632161

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

0 commit comments

Comments
 (0)