[Lldb-commits] [lldb] [lldb] Implement a statusline in LLDB (PR #121860)
via lldb-commits
lldb-commits at lists.llvm.org
Fri Jan 17 17:30:41 PST 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-lldb
Author: Jonas Devlieghere (JDevlieghere)
<details>
<summary>Changes</summary>
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
---
Patch is 36.09 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/121860.diff
14 Files Affected:
- (modified) lldb/docs/use/formatting.rst (+6-2)
- (modified) lldb/include/lldb/Core/Debugger.h (+20-1)
- (modified) lldb/include/lldb/Core/FormatEntity.h (+4-1)
- (added) lldb/include/lldb/Core/Statusline.h (+58)
- (modified) lldb/include/lldb/Utility/AnsiTerminal.h (+39)
- (modified) lldb/source/Core/CMakeLists.txt (+1)
- (modified) lldb/source/Core/CoreProperties.td (+8)
- (modified) lldb/source/Core/Debugger.cpp (+68-75)
- (modified) lldb/source/Core/FormatEntity.cpp (+56-8)
- (added) lldb/source/Core/Statusline.cpp (+161)
- (modified) lldb/source/Host/common/Editline.cpp (+3-1)
- (modified) lldb/test/API/terminal/TestEditline.py (+16)
- (modified) lldb/unittests/Core/FormatEntityTest.cpp (+3)
- (modified) lldb/unittests/Utility/AnsiTerminalTest.cpp (+15)
``````````diff
diff --git a/lldb/docs/use/formatting.rst b/lldb/docs/use/formatting.rst
index 970bacfd8807a7..3b7819d29d0a27 100644
--- a/lldb/docs/use/formatting.rst
+++ b/lldb/docs/use/formatting.rst
@@ -113,11 +113,11 @@ A complete list of currently supported format string variables is listed below:
+---------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ``module.file.basename`` | The basename of the current module (shared library or executable) |
+---------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
-| ``module.file.fullpath`` | The basename of the current module (shared library or executable) |
+| ``module.file.fullpath`` | The path of the current module (shared library or executable) |
+---------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ``process.file.basename`` | The basename of the file for the process |
+---------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
-| ``process.file.fullpath`` | The fullname of the file for the process |
+| ``process.file.fullpath`` | The path of the file for the process |
+---------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ``process.id`` | The process ID native to the system on which the inferior runs. |
+---------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
@@ -141,6 +141,10 @@ A complete list of currently supported format string variables is listed below:
+---------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ``target.arch`` | The architecture of the current target |
+---------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+| ``target.file.basename`` | The basename of the current current target |
++---------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+| ``target.file.fullpath`` | The path of the current current target |
++---------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ``script.target:python_func`` | Use a Python function to generate a piece of textual output |
+---------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ``script.process:python_func`` | Use a Python function to generate a piece of textual output |
diff --git a/lldb/include/lldb/Core/Debugger.h b/lldb/include/lldb/Core/Debugger.h
index 70f4c4216221c6..a4da5fd44c17fe 100644
--- a/lldb/include/lldb/Core/Debugger.h
+++ b/lldb/include/lldb/Core/Debugger.h
@@ -19,6 +19,7 @@
#include "lldb/Core/FormatEntity.h"
#include "lldb/Core/IOHandler.h"
#include "lldb/Core/SourceManager.h"
+#include "lldb/Core/Statusline.h"
#include "lldb/Core/UserSettingsController.h"
#include "lldb/Host/HostThread.h"
#include "lldb/Host/StreamFile.h"
@@ -308,6 +309,10 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
bool SetShowProgress(bool show_progress);
+ bool GetShowStatusline() const;
+
+ const FormatEntity::Entry *GetStatuslineFormat() const;
+
llvm::StringRef GetShowProgressAnsiPrefix() const;
llvm::StringRef GetShowProgressAnsiSuffix() const;
@@ -604,6 +609,14 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
return m_source_file_cache;
}
+ struct ProgressReport {
+ uint64_t id;
+ uint64_t completed;
+ uint64_t total;
+ std::string message;
+ };
+ std::optional<ProgressReport> GetCurrentProgressReport() const;
+
protected:
friend class CommandInterpreter;
friend class REPL;
@@ -728,7 +741,7 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
IOHandlerStack m_io_handler_stack;
std::recursive_mutex m_io_handler_synchronous_mutex;
- std::optional<uint64_t> m_current_event_id;
+ std::optional<Statusline> m_statusline;
llvm::StringMap<std::weak_ptr<LogHandler>> m_stream_handlers;
std::shared_ptr<CallbackLogHandler> m_callback_handler_sp;
@@ -745,6 +758,12 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
lldb::TargetSP m_dummy_target_sp;
Diagnostics::CallbackID m_diagnostics_callback_id;
+ /// Bookkeeping for command line progress events.
+ /// @{
+ llvm::SmallVector<ProgressReport, 4> m_progress_reports;
+ mutable std::mutex m_progress_reports_mutex;
+ /// @}
+
std::mutex m_destroy_callback_mutex;
lldb::callback_token_t m_destroy_callback_next_token = 0;
struct DestroyCallbackInfo {
diff --git a/lldb/include/lldb/Core/FormatEntity.h b/lldb/include/lldb/Core/FormatEntity.h
index 36f6df4118c21f..51e9ce37e54e79 100644
--- a/lldb/include/lldb/Core/FormatEntity.h
+++ b/lldb/include/lldb/Core/FormatEntity.h
@@ -67,6 +67,7 @@ struct Entry {
ScriptThread,
ThreadInfo,
TargetArch,
+ TargetFile,
ScriptTarget,
ModuleFile,
File,
@@ -99,7 +100,9 @@ struct Entry {
LineEntryColumn,
LineEntryStartAddress,
LineEntryEndAddress,
- CurrentPCArrow
+ CurrentPCArrow,
+ ProgressCount,
+ ProgressMessage,
};
struct Definition {
diff --git a/lldb/include/lldb/Core/Statusline.h b/lldb/include/lldb/Core/Statusline.h
new file mode 100644
index 00000000000000..aeb1ae7e6846df
--- /dev/null
+++ b/lldb/include/lldb/Core/Statusline.h
@@ -0,0 +1,58 @@
+//===-- Statusline.h -----------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+#include "lldb/Core/Debugger.h"
+#include "llvm/ADT/SmallVector.h"
+#include <string>
+#ifndef LLDB_CORE_STATUSBAR_H
+#define LLDB_CORE_STATUSBAR_H
+
+namespace lldb_private {
+class Statusline {
+public:
+ Statusline(Debugger &debugger);
+ ~Statusline();
+
+ void Enable();
+ void Disable();
+
+ void Clear();
+ void Update();
+
+ void TerminalSizeChanged() { m_terminal_size_has_changed = 1; }
+
+private:
+ // Draw the statusline with the given text.
+ void Draw(llvm::StringRef msg);
+
+ // Update terminal dimensions.
+ void UpdateTerminalProperties();
+
+ // Set the scroll window to the given height.
+ void SetScrollWindow(uint64_t height);
+
+ // Write at the given column.
+ void AddAtPosition(uint64_t col, llvm::StringRef str);
+
+ // Clear the statusline (without redrawing the background).
+ void Reset();
+
+ bool IsSupported() const;
+
+ lldb::thread_result_t StatuslineThread();
+
+ Debugger &m_debugger;
+
+ volatile std::sig_atomic_t m_terminal_size_has_changed = 1;
+ uint64_t m_terminal_width = 0;
+ uint64_t m_terminal_height = 0;
+ uint64_t m_scroll_height = 0;
+
+ static constexpr llvm::StringLiteral k_ansi_suffix = "${ansi.normal}";
+};
+} // namespace lldb_private
+#endif // LLDB_CORE_STATUSBAR_H
diff --git a/lldb/include/lldb/Utility/AnsiTerminal.h b/lldb/include/lldb/Utility/AnsiTerminal.h
index 67795971d2ca89..a49865e711e108 100644
--- a/lldb/include/lldb/Utility/AnsiTerminal.h
+++ b/lldb/include/lldb/Utility/AnsiTerminal.h
@@ -73,6 +73,7 @@
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Regex.h"
#include <string>
@@ -171,7 +172,45 @@ inline std::string FormatAnsiTerminalCodes(llvm::StringRef format,
}
return fmt;
}
+
+inline std::string StripAnsiTerminalCodes(llvm::StringRef str) {
+ std::string stripped;
+ while (!str.empty()) {
+ llvm::StringRef left, right;
+
+ std::tie(left, right) = str.split(ANSI_ESC_START);
+ stripped += left;
+
+ // ANSI_ESC_START not found.
+ if (left == str && right.empty())
+ break;
+
+ auto end = llvm::StringRef::npos;
+ for (size_t i = 0; i < right.size(); i++) {
+ char c = right[i];
+ if (c == 'm' || c == 'G') {
+ end = i;
+ break;
+ }
+ if (isdigit(c) || c == ';')
+ continue;
+
+ break;
+ }
+
+ // ANSI_ESC_END not found.
+ if (end != llvm::StringRef::npos) {
+ str = right.substr(end + 1);
+ continue;
+ }
+
+ stripped += ANSI_ESC_START;
+ str = right;
+ }
+ return stripped;
}
+
+} // namespace ansi
} // namespace lldb_private
#endif
diff --git a/lldb/source/Core/CMakeLists.txt b/lldb/source/Core/CMakeLists.txt
index 6d14f7a87764e0..5d4576837dbe61 100644
--- a/lldb/source/Core/CMakeLists.txt
+++ b/lldb/source/Core/CMakeLists.txt
@@ -46,6 +46,7 @@ add_lldb_library(lldbCore
Opcode.cpp
PluginManager.cpp
Progress.cpp
+ Statusline.cpp
RichManglingContext.cpp
SearchFilter.cpp
Section.cpp
diff --git a/lldb/source/Core/CoreProperties.td b/lldb/source/Core/CoreProperties.td
index d3816c3070bbc5..0c6f93cb23e456 100644
--- a/lldb/source/Core/CoreProperties.td
+++ b/lldb/source/Core/CoreProperties.td
@@ -172,6 +172,14 @@ let Definition = "debugger" in {
Global,
DefaultStringValue<"${ansi.normal}">,
Desc<"When displaying progress in a color-enabled terminal, use the ANSI terminal code specified in this format immediately after the progress message.">;
+ def ShowStatusline: Property<"show-statusline", "Boolean">,
+ Global,
+ DefaultTrue,
+ Desc<"Whether to show a statusline at the bottom of the terminal.">;
+ def StatuslineFormat: Property<"statusline-format", "FormatEntity">,
+ Global,
+ DefaultStringValue<"${ansi.bg.blue}${ansi.fg.black}{${target.file.basename}}{ | ${line.file.basename}:${line.number}:${line.column}}{ | ${thread.stop-reason}}{ | {${progress.count} }${progress.message}}">,
+ Desc<"List of statusline format entities.">;
def UseSourceCache: Property<"use-source-cache", "Boolean">,
Global,
DefaultTrue,
diff --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp
index 6ceb209269c9e7..0735d9e4360381 100644
--- a/lldb/source/Core/Debugger.cpp
+++ b/lldb/source/Core/Debugger.cpp
@@ -243,6 +243,11 @@ Status Debugger::SetPropertyValue(const ExecutionContext *exe_ctx,
// Prompt colors changed. Ping the prompt so it can reset the ansi
// terminal codes.
SetPrompt(GetPrompt());
+ } else if (property_path ==
+ g_debugger_properties[ePropertyStatuslineFormat].name) {
+ // Statusline format changed. Redraw the statusline.
+ if (m_statusline)
+ m_statusline->Update();
} else if (property_path ==
g_debugger_properties[ePropertyUseSourceCache].name) {
// use-source-cache changed. Wipe out the cache contents if it was
@@ -376,6 +381,8 @@ bool Debugger::SetTerminalWidth(uint64_t term_width) {
if (auto handler_sp = m_io_handler_stack.Top())
handler_sp->TerminalSizeChanged();
+ if (m_statusline)
+ m_statusline->TerminalSizeChanged();
return success;
}
@@ -392,6 +399,8 @@ bool Debugger::SetTerminalHeight(uint64_t term_height) {
if (auto handler_sp = m_io_handler_stack.Top())
handler_sp->TerminalSizeChanged();
+ if (m_statusline)
+ m_statusline->TerminalSizeChanged();
return success;
}
@@ -454,6 +463,17 @@ llvm::StringRef Debugger::GetShowProgressAnsiSuffix() const {
idx, g_debugger_properties[idx].default_cstr_value);
}
+bool Debugger::GetShowStatusline() const {
+ const uint32_t idx = ePropertyShowStatusline;
+ return GetPropertyAtIndexAs<bool>(
+ idx, g_debugger_properties[idx].default_uint_value != 0);
+}
+
+const FormatEntity::Entry *Debugger::GetStatuslineFormat() const {
+ constexpr uint32_t idx = ePropertyStatuslineFormat;
+ return GetPropertyAtIndexAs<const FormatEntity::Entry *>(idx);
+}
+
bool Debugger::GetUseAutosuggestion() const {
const uint32_t idx = ePropertyShowAutosuggestion;
return GetPropertyAtIndexAs<bool>(
@@ -1093,12 +1113,18 @@ void Debugger::SetErrorFile(FileSP file_sp) {
}
void Debugger::SaveInputTerminalState() {
+ if (m_statusline)
+ m_statusline->Disable();
int fd = GetInputFile().GetDescriptor();
if (fd != File::kInvalidDescriptor)
m_terminal_state.Save(fd, true);
}
-void Debugger::RestoreInputTerminalState() { m_terminal_state.Restore(); }
+void Debugger::RestoreInputTerminalState() {
+ m_terminal_state.Restore();
+ if (m_statusline)
+ m_statusline->Enable();
+}
ExecutionContext Debugger::GetSelectedExecutionContext() {
bool adopt_selected = true;
@@ -1958,6 +1984,12 @@ lldb::thread_result_t Debugger::DefaultEventHandler() {
// are now listening to all required events so no events get missed
m_sync_broadcaster.BroadcastEvent(eBroadcastBitEventThreadIsListening);
+ if (!m_statusline && GetShowStatusline())
+ m_statusline.emplace(*this);
+
+ if (m_statusline)
+ m_statusline->Enable();
+
bool done = false;
while (!done) {
EventSP event_sp;
@@ -2016,8 +2048,14 @@ lldb::thread_result_t Debugger::DefaultEventHandler() {
if (m_forward_listener_sp)
m_forward_listener_sp->AddEvent(event_sp);
}
+ if (m_statusline)
+ m_statusline->Update();
}
}
+
+ if (m_statusline)
+ m_statusline->Disable();
+
return {};
}
@@ -2080,84 +2118,39 @@ void Debugger::HandleProgressEvent(const lldb::EventSP &event_sp) {
if (!data)
return;
- // Do some bookkeeping for the current event, regardless of whether we're
- // going to show the progress.
- const uint64_t id = data->GetID();
- if (m_current_event_id) {
- Log *log = GetLog(LLDBLog::Events);
- if (log && log->GetVerbose()) {
- StreamString log_stream;
- log_stream.AsRawOstream()
- << static_cast<void *>(this) << " Debugger(" << GetID()
- << ")::HandleProgressEvent( m_current_event_id = "
- << *m_current_event_id << ", data = { ";
- data->Dump(&log_stream);
- log_stream << " } )";
- log->PutString(log_stream.GetString());
- }
- if (id != *m_current_event_id)
- return;
- if (data->GetCompleted() == data->GetTotal())
- m_current_event_id.reset();
- } else {
- m_current_event_id = id;
- }
-
- // Decide whether we actually are going to show the progress. This decision
- // can change between iterations so check it inside the loop.
- if (!GetShowProgress())
- return;
-
- // Determine whether the current output file is an interactive terminal with
- // color support. We assume that if we support ANSI escape codes we support
- // vt100 escape codes.
- File &file = GetOutputFile();
- if (!file.GetIsInteractive() || !file.GetIsTerminalWithColors())
- return;
-
- StreamSP output = GetAsyncOutputStream();
+ // Make a local copy of the incoming pr...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/121860
More information about the lldb-commits
mailing list