Skip to content

Commit 42f41d9

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 470ffa2 commit 42f41d9

File tree

9 files changed

+359
-84
lines changed

9 files changed

+359
-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: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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+
#include "lldb/Core/Debugger.h"
9+
#include "llvm/ADT/SmallVector.h"
10+
#include <string>
11+
#ifndef LLDB_CORE_STATUSBAR_H
12+
#define LLDB_CORE_STATUSBAR_H
13+
14+
namespace lldb_private {
15+
class Statusline {
16+
public:
17+
Statusline(Debugger &debugger);
18+
~Statusline();
19+
20+
void Enable();
21+
void Disable();
22+
23+
void Clear();
24+
void Update();
25+
26+
void TerminalSizeChanged() { m_terminal_size_has_changed = 1; }
27+
28+
private:
29+
// Draw the statusline with the given text.
30+
void Draw(llvm::StringRef msg);
31+
32+
// Update terminal dimensions.
33+
void UpdateTerminalProperties();
34+
35+
// Set the scroll window to the given height.
36+
void SetScrollWindow(uint64_t height);
37+
38+
// Write at the given column.
39+
void AddAtPosition(uint64_t col, llvm::StringRef str);
40+
41+
// Clear the statusline (without redrawing the background).
42+
void Reset();
43+
44+
bool IsSupported() const;
45+
46+
lldb::thread_result_t StatuslineThread();
47+
48+
Debugger &m_debugger;
49+
50+
volatile std::sig_atomic_t m_terminal_size_has_changed = 1;
51+
uint64_t m_terminal_width = 0;
52+
uint64_t m_terminal_height = 0;
53+
uint64_t m_scroll_height = 0;
54+
55+
static constexpr llvm::StringLiteral k_ansi_suffix = "${ansi.normal}";
56+
};
57+
} // namespace lldb_private
58+
#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: 68 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,11 @@ 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[ePropertyStatuslineFormat].name) {
248+
// Statusline format changed. Redraw the statusline.
249+
if (m_statusline)
250+
m_statusline->Update();
246251
} else if (property_path ==
247252
g_debugger_properties[ePropertyUseSourceCache].name) {
248253
// use-source-cache changed. Wipe out the cache contents if it was
@@ -376,6 +381,8 @@ bool Debugger::SetTerminalWidth(uint64_t term_width) {
376381

377382
if (auto handler_sp = m_io_handler_stack.Top())
378383
handler_sp->TerminalSizeChanged();
384+
if (m_statusline)
385+
m_statusline->TerminalSizeChanged();
379386

380387
return success;
381388
}
@@ -392,6 +399,8 @@ bool Debugger::SetTerminalHeight(uint64_t term_height) {
392399

393400
if (auto handler_sp = m_io_handler_stack.Top())
394401
handler_sp->TerminalSizeChanged();
402+
if (m_statusline)
403+
m_statusline->TerminalSizeChanged();
395404

396405
return success;
397406
}
@@ -454,6 +463,17 @@ llvm::StringRef Debugger::GetShowProgressAnsiSuffix() const {
454463
idx, g_debugger_properties[idx].default_cstr_value);
455464
}
456465

466+
bool Debugger::GetShowStatusline() const {
467+
const uint32_t idx = ePropertyShowStatusline;
468+
return GetPropertyAtIndexAs<bool>(
469+
idx, g_debugger_properties[idx].default_uint_value != 0);
470+
}
471+
472+
const FormatEntity::Entry *Debugger::GetStatuslineFormat() const {
473+
constexpr uint32_t idx = ePropertyStatuslineFormat;
474+
return GetPropertyAtIndexAs<const FormatEntity::Entry *>(idx);
475+
}
476+
457477
bool Debugger::GetUseAutosuggestion() const {
458478
const uint32_t idx = ePropertyShowAutosuggestion;
459479
return GetPropertyAtIndexAs<bool>(
@@ -1093,12 +1113,18 @@ void Debugger::SetErrorFile(FileSP file_sp) {
10931113
}
10941114

10951115
void Debugger::SaveInputTerminalState() {
1116+
if (m_statusline)
1117+
m_statusline->Disable();
10961118
int fd = GetInputFile().GetDescriptor();
10971119
if (fd != File::kInvalidDescriptor)
10981120
m_terminal_state.Save(fd, true);
10991121
}
11001122

1101-
void Debugger::RestoreInputTerminalState() { m_terminal_state.Restore(); }
1123+
void Debugger::RestoreInputTerminalState() {
1124+
m_terminal_state.Restore();
1125+
if (m_statusline)
1126+
m_statusline->Enable();
1127+
}
11021128

11031129
ExecutionContext Debugger::GetSelectedExecutionContext() {
11041130
bool adopt_selected = true;
@@ -1958,6 +1984,12 @@ lldb::thread_result_t Debugger::DefaultEventHandler() {
19581984
// are now listening to all required events so no events get missed
19591985
m_sync_broadcaster.BroadcastEvent(eBroadcastBitEventThreadIsListening);
19601986

1987+
if (!m_statusline && GetShowStatusline())
1988+
m_statusline.emplace(*this);
1989+
1990+
if (m_statusline)
1991+
m_statusline->Enable();
1992+
19611993
bool done = false;
19621994
while (!done) {
19631995
EventSP event_sp;
@@ -2016,8 +2048,14 @@ lldb::thread_result_t Debugger::DefaultEventHandler() {
20162048
if (m_forward_listener_sp)
20172049
m_forward_listener_sp->AddEvent(event_sp);
20182050
}
2051+
if (m_statusline)
2052+
m_statusline->Update();
20192053
}
20202054
}
2055+
2056+
if (m_statusline)
2057+
m_statusline->Disable();
2058+
20212059
return {};
20222060
}
20232061

@@ -2080,84 +2118,39 @@ void Debugger::HandleProgressEvent(const lldb::EventSP &event_sp) {
20802118
if (!data)
20812119
return;
20822120

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

2120-
// Print over previous line, if any.
2121-
output->Printf("\r");
2122-
2123-
if (data->GetCompleted() == data->GetTotal()) {
2124-
// Clear the current line.
2125-
output->Printf("\x1B[2K");
2126-
output->Flush();
2127-
return;
2125+
// Do some bookkeeping regardless of whether we're going to display
2126+
// progress reports.
2127+
{
2128+
std::lock_guard<std::mutex> guard(m_progress_reports_mutex);
2129+
auto it = std::find_if(
2130+
m_progress_reports.begin(), m_progress_reports.end(),
2131+
[&](const auto &report) { return report.id == progress_report.id; });
2132+
if (it != m_progress_reports.end()) {
2133+
const bool complete = data->GetCompleted() == data->GetTotal();
2134+
if (complete)
2135+
m_progress_reports.erase(it);
2136+
else
2137+
*it = progress_report;
2138+
} else {
2139+
m_progress_reports.push_back(progress_report);
2140+
}
21282141
}
21292142

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

2159-
// Flush the output.
2160-
output->Flush();
2148+
std::optional<Debugger::ProgressReport>
2149+
Debugger::GetCurrentProgressReport() const {
2150+
std::lock_guard<std::mutex> guard(m_progress_reports_mutex);
2151+
if (m_progress_reports.empty())
2152+
return std::nullopt;
2153+
return m_progress_reports.back();
21612154
}
21622155

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

0 commit comments

Comments
 (0)