[Lldb-commits] [lldb] LLDB Statusline (PR #121860)
Jonas Devlieghere via lldb-commits
lldb-commits at lists.llvm.org
Fri Jan 17 17:29:30 PST 2025
https://github.com/JDevlieghere updated https://github.com/llvm/llvm-project/pull/121860
>From f35884731d0f4bb4a420d2ee802378cc0e1d24fb Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Fri, 17 Jan 2025 17:18:13 -0800
Subject: [PATCH 1/3] [lldb] Implement ${taret.file} format variable
Implements a format variable to print the basename and full path to the
current target.
---
lldb/docs/use/formatting.rst | 8 ++++++--
lldb/include/lldb/Core/FormatEntity.h | 1 +
lldb/source/Core/FormatEntity.cpp | 18 +++++++++++++++++-
lldb/unittests/Core/FormatEntityTest.cpp | 3 +++
4 files changed, 27 insertions(+), 3 deletions(-)
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/FormatEntity.h b/lldb/include/lldb/Core/FormatEntity.h
index 36f6df4118c21f..c9d5af1f31673b 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,
diff --git a/lldb/source/Core/FormatEntity.cpp b/lldb/source/Core/FormatEntity.cpp
index e13284832cf571..8355b08c887020 100644
--- a/lldb/source/Core/FormatEntity.cpp
+++ b/lldb/source/Core/FormatEntity.cpp
@@ -162,7 +162,9 @@ constexpr Definition g_thread_child_entries[] = {
Definition("completed-expression", EntryType::ThreadCompletedExpression)};
constexpr Definition g_target_child_entries[] = {
- Definition("arch", EntryType::TargetArch)};
+ Definition("arch", EntryType::TargetArch),
+ Entry::DefinitionWithChildren("file", EntryType::TargetFile,
+ g_file_child_entries)};
#define _TO_STR2(_val) #_val
#define _TO_STR(_val) _TO_STR2(_val)
@@ -322,6 +324,7 @@ const char *FormatEntity::Entry::TypeToCString(Type t) {
ENUM_TO_CSTR(ScriptThread);
ENUM_TO_CSTR(ThreadInfo);
ENUM_TO_CSTR(TargetArch);
+ ENUM_TO_CSTR(TargetFile);
ENUM_TO_CSTR(ScriptTarget);
ENUM_TO_CSTR(ModuleFile);
ENUM_TO_CSTR(File);
@@ -1469,6 +1472,19 @@ bool FormatEntity::Format(const Entry &entry, Stream &s,
}
return false;
+ case Entry::Type::TargetFile:
+ if (exe_ctx) {
+ Target *target = exe_ctx->GetTargetPtr();
+ if (target) {
+ Module *exe_module = target->GetExecutableModulePointer();
+ if (exe_module) {
+ if (DumpFile(s, exe_module->GetFileSpec(), (FileKind)entry.number))
+ return true;
+ }
+ }
+ }
+ return false;
+
case Entry::Type::ScriptTarget:
if (exe_ctx) {
Target *target = exe_ctx->GetTargetPtr();
diff --git a/lldb/unittests/Core/FormatEntityTest.cpp b/lldb/unittests/Core/FormatEntityTest.cpp
index 0a68c9340b77ae..5983c9de99ef78 100644
--- a/lldb/unittests/Core/FormatEntityTest.cpp
+++ b/lldb/unittests/Core/FormatEntityTest.cpp
@@ -148,6 +148,9 @@ constexpr llvm::StringRef lookupStrings[] = {
"${thread.return-value}",
"${thread.completed-expression}",
"${target.arch}",
+ "${target.file.basename}",
+ "${target.file.dirname}",
+ "${target.file.fullpath}",
"${var.dummy-var-to-test-wildcard}"};
TEST(FormatEntity, LookupAllEntriesInTree) {
>From 470ffa298d3998b654d3bc1ea4f733e3663ea70e Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Fri, 17 Jan 2025 16:51:21 -0800
Subject: [PATCH 2/3] [lldb] Support format string in the prompt
Implement ansi::StripAnsiTerminalCodes and fix a long standing bug where
using format strings in lldb's prompt resulted in an incorrect prompt
column width.
---
lldb/include/lldb/Utility/AnsiTerminal.h | 38 +++++++++++++++++++++
lldb/source/Host/common/Editline.cpp | 4 ++-
lldb/test/API/terminal/TestEditline.py | 16 +++++++++
lldb/unittests/Utility/AnsiTerminalTest.cpp | 15 ++++++++
4 files changed, 72 insertions(+), 1 deletion(-)
diff --git a/lldb/include/lldb/Utility/AnsiTerminal.h b/lldb/include/lldb/Utility/AnsiTerminal.h
index 67795971d2ca89..22601e873dfe9e 100644
--- a/lldb/include/lldb/Utility/AnsiTerminal.h
+++ b/lldb/include/lldb/Utility/AnsiTerminal.h
@@ -171,7 +171,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/Host/common/Editline.cpp b/lldb/source/Host/common/Editline.cpp
index 6e35b15d69651d..d31fe3af946b37 100644
--- a/lldb/source/Host/common/Editline.cpp
+++ b/lldb/source/Host/common/Editline.cpp
@@ -14,6 +14,7 @@
#include "lldb/Host/Editline.h"
#include "lldb/Host/FileSystem.h"
#include "lldb/Host/Host.h"
+#include "lldb/Utility/AnsiTerminal.h"
#include "lldb/Utility/CompletionRequest.h"
#include "lldb/Utility/FileSpec.h"
#include "lldb/Utility/LLDBAssert.h"
@@ -85,7 +86,8 @@ bool IsOnlySpaces(const EditLineStringType &content) {
}
static size_t ColumnWidth(llvm::StringRef str) {
- return llvm::sys::locale::columnWidth(str);
+ std::string stripped = ansi::StripAnsiTerminalCodes(str);
+ return llvm::sys::locale::columnWidth(stripped);
}
static int GetOperation(HistoryOperation op) {
diff --git a/lldb/test/API/terminal/TestEditline.py b/lldb/test/API/terminal/TestEditline.py
index aa7d827e599441..40f8a50e06bf6d 100644
--- a/lldb/test/API/terminal/TestEditline.py
+++ b/lldb/test/API/terminal/TestEditline.py
@@ -69,6 +69,22 @@ def test_prompt_color(self):
# Column: 1....6.8
self.child.expect(re.escape("\x1b[31m(lldb) \x1b[0m\x1b[8G"))
+ @skipIfAsan
+ @skipIfEditlineSupportMissing
+ def test_prompt_format_color(self):
+ """Test that we can change the prompt color with a format string."""
+ self.launch(use_colors=True)
+ # Clear the prefix and suffix setting to simplify the output.
+ self.child.send('settings set prompt-ansi-prefix ""\n')
+ self.child.send('settings set prompt-ansi-suffix ""\n')
+ self.child.send('settings set prompt "${ansi.fg.red}(lldb)${ansi.normal} "\n')
+ self.child.send("foo")
+ # Make sure this change is reflected immediately. Check that the color
+ # is set (31) and the cursor position (8) is correct.
+ # Prompt: (lldb) _
+ # Column: 1....6.8
+ self.child.expect(re.escape("\x1b[31m(lldb)\x1b[0m foo"))
+
@skipIfAsan
@skipIfEditlineSupportMissing
def test_prompt_no_color(self):
diff --git a/lldb/unittests/Utility/AnsiTerminalTest.cpp b/lldb/unittests/Utility/AnsiTerminalTest.cpp
index a6dbfd61061420..1ba9565c3f6af3 100644
--- a/lldb/unittests/Utility/AnsiTerminalTest.cpp
+++ b/lldb/unittests/Utility/AnsiTerminalTest.cpp
@@ -16,16 +16,21 @@ TEST(AnsiTerminal, Empty) { EXPECT_EQ("", ansi::FormatAnsiTerminalCodes("")); }
TEST(AnsiTerminal, WhiteSpace) {
EXPECT_EQ(" ", ansi::FormatAnsiTerminalCodes(" "));
+ EXPECT_EQ(" ", ansi::StripAnsiTerminalCodes(" "));
}
TEST(AnsiTerminal, AtEnd) {
EXPECT_EQ("abc\x1B[30m",
ansi::FormatAnsiTerminalCodes("abc${ansi.fg.black}"));
+
+ EXPECT_EQ("abc", ansi::StripAnsiTerminalCodes("abc\x1B[30m"));
}
TEST(AnsiTerminal, AtStart) {
EXPECT_EQ("\x1B[30mabc",
ansi::FormatAnsiTerminalCodes("${ansi.fg.black}abc"));
+
+ EXPECT_EQ("abc", ansi::StripAnsiTerminalCodes("\x1B[30mabc"));
}
TEST(AnsiTerminal, KnownPrefix) {
@@ -45,10 +50,20 @@ TEST(AnsiTerminal, Incomplete) {
TEST(AnsiTerminal, Twice) {
EXPECT_EQ("\x1B[30m\x1B[31mabc",
ansi::FormatAnsiTerminalCodes("${ansi.fg.black}${ansi.fg.red}abc"));
+
+ EXPECT_EQ("abc", ansi::StripAnsiTerminalCodes("\x1B[30m\x1B[31mabc"));
}
TEST(AnsiTerminal, Basic) {
EXPECT_EQ(
"abc\x1B[31mabc\x1B[0mabc",
ansi::FormatAnsiTerminalCodes("abc${ansi.fg.red}abc${ansi.normal}abc"));
+
+ EXPECT_EQ("abcabcabc",
+ ansi::StripAnsiTerminalCodes("abc\x1B[31mabc\x1B[0mabc"));
+}
+
+TEST(AnsiTerminal, InvalidEscapeCode) {
+ EXPECT_EQ("abc\x1B[31kabcabc",
+ ansi::StripAnsiTerminalCodes("abc\x1B[31kabc\x1B[0mabc"));
}
>From 42f41d9269860d71a77e8a60c1ca65d714312d0b Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Fri, 17 Jan 2025 17:10:36 -0800
Subject: [PATCH 3/3] [lldb] Implement a statusline in LLDB
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
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
---
lldb/include/lldb/Core/Debugger.h | 21 ++-
lldb/include/lldb/Core/FormatEntity.h | 4 +-
lldb/include/lldb/Core/Statusline.h | 58 ++++++++
lldb/include/lldb/Utility/AnsiTerminal.h | 1 +
lldb/source/Core/CMakeLists.txt | 1 +
lldb/source/Core/CoreProperties.td | 8 ++
lldb/source/Core/Debugger.cpp | 143 ++++++++++----------
lldb/source/Core/FormatEntity.cpp | 46 ++++++-
lldb/source/Core/Statusline.cpp | 161 +++++++++++++++++++++++
9 files changed, 359 insertions(+), 84 deletions(-)
create mode 100644 lldb/include/lldb/Core/Statusline.h
create mode 100644 lldb/source/Core/Statusline.cpp
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 c9d5af1f31673b..51e9ce37e54e79 100644
--- a/lldb/include/lldb/Core/FormatEntity.h
+++ b/lldb/include/lldb/Core/FormatEntity.h
@@ -100,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 22601e873dfe9e..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>
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 progress report that we'll store.
+ ProgressReport progress_report{data->GetID(), data->GetCompleted(),
+ data->GetTotal(), data->GetMessage()};
- // Print over previous line, if any.
- output->Printf("\r");
-
- if (data->GetCompleted() == data->GetTotal()) {
- // Clear the current line.
- output->Printf("\x1B[2K");
- output->Flush();
- return;
+ // Do some bookkeeping regardless of whether we're going to display
+ // progress reports.
+ {
+ std::lock_guard<std::mutex> guard(m_progress_reports_mutex);
+ auto it = std::find_if(
+ m_progress_reports.begin(), m_progress_reports.end(),
+ [&](const auto &report) { return report.id == progress_report.id; });
+ if (it != m_progress_reports.end()) {
+ const bool complete = data->GetCompleted() == data->GetTotal();
+ if (complete)
+ m_progress_reports.erase(it);
+ else
+ *it = progress_report;
+ } else {
+ m_progress_reports.push_back(progress_report);
+ }
}
- // Trim the progress message if it exceeds the window's width and print it.
- std::string message = data->GetMessage();
- if (data->IsFinite())
- message = llvm::formatv("[{0}/{1}] {2}", data->GetCompleted(),
- data->GetTotal(), message)
- .str();
-
- // Trim the progress message if it exceeds the window's width and print it.
- const uint32_t term_width = GetTerminalWidth();
- const uint32_t ellipsis = 3;
- if (message.size() + ellipsis >= term_width)
- message.resize(term_width - ellipsis);
-
- const bool use_color = GetUseColor();
- llvm::StringRef ansi_prefix = GetShowProgressAnsiPrefix();
- if (!ansi_prefix.empty())
- output->Printf(
- "%s", ansi::FormatAnsiTerminalCodes(ansi_prefix, use_color).c_str());
-
- output->Printf("%s...", message.c_str());
-
- llvm::StringRef ansi_suffix = GetShowProgressAnsiSuffix();
- if (!ansi_suffix.empty())
- output->Printf(
- "%s", ansi::FormatAnsiTerminalCodes(ansi_suffix, use_color).c_str());
-
- // Clear until the end of the line.
- output->Printf("\x1B[K\r");
+ // Redraw the statusline if enabled.
+ if (m_statusline)
+ m_statusline->Update();
+}
- // Flush the output.
- output->Flush();
+std::optional<Debugger::ProgressReport>
+Debugger::GetCurrentProgressReport() const {
+ std::lock_guard<std::mutex> guard(m_progress_reports_mutex);
+ if (m_progress_reports.empty())
+ return std::nullopt;
+ return m_progress_reports.back();
}
void Debugger::HandleDiagnosticEvent(const lldb::EventSP &event_sp) {
diff --git a/lldb/source/Core/FormatEntity.cpp b/lldb/source/Core/FormatEntity.cpp
index 8355b08c887020..ddd4d0fb275d2e 100644
--- a/lldb/source/Core/FormatEntity.cpp
+++ b/lldb/source/Core/FormatEntity.cpp
@@ -166,6 +166,10 @@ constexpr Definition g_target_child_entries[] = {
Entry::DefinitionWithChildren("file", EntryType::TargetFile,
g_file_child_entries)};
+constexpr Definition g_progress_child_entries[] = {
+ Definition("count", EntryType::ProgressCount),
+ Definition("message", EntryType::ProgressMessage)};
+
#define _TO_STR2(_val) #_val
#define _TO_STR(_val) _TO_STR2(_val)
@@ -259,7 +263,10 @@ constexpr Definition g_top_level_entries[] = {
Entry::DefinitionWithChildren("target", EntryType::Invalid,
g_target_child_entries),
Entry::DefinitionWithChildren("var", EntryType::Variable,
- g_var_child_entries, true)};
+ g_var_child_entries, true),
+ Entry::DefinitionWithChildren("progress", EntryType::Invalid,
+ g_progress_child_entries),
+};
constexpr Definition g_root = Entry::DefinitionWithChildren(
"<root>", EntryType::Root, g_top_level_entries);
@@ -358,6 +365,8 @@ const char *FormatEntity::Entry::TypeToCString(Type t) {
ENUM_TO_CSTR(LineEntryStartAddress);
ENUM_TO_CSTR(LineEntryEndAddress);
ENUM_TO_CSTR(CurrentPCArrow);
+ ENUM_TO_CSTR(ProgressCount);
+ ENUM_TO_CSTR(ProgressMessage);
}
return "???";
}
@@ -1198,13 +1207,13 @@ bool FormatEntity::Format(const Entry &entry, Stream &s,
// FormatEntity::Entry::Definition encoding
return false;
case Entry::Type::EscapeCode:
- if (exe_ctx) {
- if (Target *target = exe_ctx->GetTargetPtr()) {
- Debugger &debugger = target->GetDebugger();
- if (debugger.GetUseColor()) {
- s.PutCString(entry.string);
- }
+ if (Target *target = Target::GetTargetFromContexts(exe_ctx, sc)) {
+ Debugger &debugger = target->GetDebugger();
+ if (debugger.GetUseColor()) {
+ s.PutCString(entry.string);
}
+ } else {
+ assert(false);
}
// Always return true, so colors being disabled is transparent.
return true;
@@ -1914,7 +1923,30 @@ bool FormatEntity::Format(const Entry &entry, Stream &s,
return true;
}
return false;
+
+ case Entry::Type::ProgressCount:
+ if (Target *target = Target::GetTargetFromContexts(exe_ctx, sc)) {
+ Debugger &debugger = target->GetDebugger();
+ if (auto progress = debugger.GetCurrentProgressReport()) {
+ if (progress->total != UINT64_MAX) {
+ s.Printf("[%llu/%llu]", progress->completed, progress->total);
+ return true;
+ }
+ }
+ }
+ return false;
+
+ case Entry::Type::ProgressMessage:
+ if (Target *target = Target::GetTargetFromContexts(exe_ctx, sc)) {
+ Debugger &debugger = target->GetDebugger();
+ if (auto progress = debugger.GetCurrentProgressReport()) {
+ s.PutCString(progress->message);
+ return true;
+ }
+ }
+ return false;
}
+
return false;
}
diff --git a/lldb/source/Core/Statusline.cpp b/lldb/source/Core/Statusline.cpp
new file mode 100644
index 00000000000000..7f90837c9cc3f3
--- /dev/null
+++ b/lldb/source/Core/Statusline.cpp
@@ -0,0 +1,161 @@
+//===-- Statusline.cpp ---------------------------------------------------===//
+//
+// 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/Statusline.h"
+#include "lldb/Core/Debugger.h"
+#include "lldb/Core/FormatEntity.h"
+#include "lldb/Host/ThreadLauncher.h"
+#include "lldb/Interpreter/CommandInterpreter.h"
+#include "lldb/Symbol/SymbolContext.h"
+#include "lldb/Target/StackFrame.h"
+#include "lldb/Utility/AnsiTerminal.h"
+#include "lldb/Utility/LLDBLog.h"
+#include "lldb/Utility/Log.h"
+#include "lldb/Utility/StreamString.h"
+#include "llvm/Support/Locale.h"
+
+#include <sys/ioctl.h>
+#include <termios.h>
+
+#define ESCAPE "\x1b"
+#define ANSI_SAVE_CURSOR ESCAPE "7"
+#define ANSI_RESTORE_CURSOR ESCAPE "8"
+#define ANSI_CLEAR_BELOW ESCAPE "[J"
+#define ANSI_CLEAR_LINE "\r\x1B[2K"
+#define ANSI_SET_SCROLL_ROWS ESCAPE "[0;%ur"
+#define ANSI_TO_START_OF_ROW ESCAPE "[%u;0f"
+#define ANSI_UP_ROWS ESCAPE "[%dA"
+#define ANSI_DOWN_ROWS ESCAPE "[%dB"
+#define ANSI_FORWARD_COLS ESCAPE "\033[%dC"
+#define ANSI_BACKWARD_COLS ESCAPE "\033[%dD"
+
+using namespace lldb;
+using namespace lldb_private;
+
+static size_t ColumnWidth(llvm::StringRef str) {
+ std::string stripped = ansi::StripAnsiTerminalCodes(str);
+ return llvm::sys::locale::columnWidth(stripped);
+}
+
+Statusline::Statusline(Debugger &debugger) : m_debugger(debugger) {}
+
+Statusline::~Statusline() { Disable(); }
+
+bool Statusline::IsSupported() const {
+ File &file = m_debugger.GetOutputFile();
+ return file.GetIsInteractive() && file.GetIsTerminalWithColors();
+}
+
+void Statusline::Enable() {
+ if (!IsSupported())
+ return;
+
+ UpdateTerminalProperties();
+
+ // Reduce the scroll window to make space for the status bar below.
+ SetScrollWindow(m_terminal_height - 1);
+
+ // Draw the statusline.
+ Update();
+}
+
+void Statusline::Disable() {
+ UpdateTerminalProperties();
+ // Clear the previous status bar if any.
+ Clear();
+ // Extend the scroll window to cover the status bar.
+ SetScrollWindow(m_terminal_height);
+}
+
+void Statusline::Draw(llvm::StringRef str) {
+ UpdateTerminalProperties();
+
+ const size_t ellipsis = 3;
+ const size_t column_width = ColumnWidth(str);
+
+ if (column_width + ellipsis >= m_terminal_width)
+ str = str.substr(0, m_terminal_width - ellipsis);
+
+ StreamFile &out = m_debugger.GetOutputStream();
+ out << ANSI_SAVE_CURSOR;
+ out.Printf(ANSI_TO_START_OF_ROW, static_cast<unsigned>(m_terminal_height));
+ out << ANSI_CLEAR_LINE;
+ out << str;
+ out << std::string(m_terminal_width - column_width, ' ');
+ out << ansi::FormatAnsiTerminalCodes(k_ansi_suffix);
+ out << ANSI_RESTORE_CURSOR;
+}
+
+void Statusline::Reset() {
+ StreamFile &out = m_debugger.GetOutputStream();
+ out << ANSI_SAVE_CURSOR;
+ out.Printf(ANSI_TO_START_OF_ROW, static_cast<unsigned>(m_terminal_height));
+ out << ANSI_CLEAR_LINE;
+ out << ANSI_RESTORE_CURSOR;
+}
+
+void Statusline::Clear() { Draw(""); }
+
+void Statusline::UpdateTerminalProperties() {
+ if (m_terminal_size_has_changed == 0)
+ return;
+
+ // Clear the previous statusline.
+ Reset();
+
+ // Purposely ignore the terminal settings. If the setting doesn't match
+ // reality and we draw the status bar over existing text, we have no way to
+ // recover.
+ struct winsize window_size;
+ if ((isatty(STDIN_FILENO) != 0) &&
+ ::ioctl(STDIN_FILENO, TIOCGWINSZ, &window_size) == 0) {
+ m_terminal_width = window_size.ws_col;
+ m_terminal_height = window_size.ws_row;
+ }
+
+ // Set the scroll window based on the new terminal height.
+ SetScrollWindow(m_terminal_height - 1);
+
+ // Clear the flag.
+ m_terminal_size_has_changed = 0;
+}
+
+void Statusline::SetScrollWindow(uint64_t height) {
+ StreamFile &out = m_debugger.GetOutputStream();
+ out << '\n';
+ out << ANSI_SAVE_CURSOR;
+ out.Printf(ANSI_SET_SCROLL_ROWS, static_cast<unsigned>(height));
+ out << ANSI_RESTORE_CURSOR;
+ out.Printf(ANSI_UP_ROWS, 1);
+ out << ANSI_CLEAR_BELOW;
+ out.Flush();
+
+ m_scroll_height = height;
+}
+
+void Statusline::Update() {
+ StreamString stream;
+
+ ExecutionContext exe_ctx =
+ m_debugger.GetCommandInterpreter().GetExecutionContext();
+
+ // For colors and progress events, the format entity needs access to the
+ // debugger, which requires a target in the execution context.
+ if (!exe_ctx.HasTargetScope())
+ exe_ctx.SetTargetPtr(&m_debugger.GetSelectedOrDummyTarget());
+
+ SymbolContext symbol_ctx;
+ if (auto frame_sp = exe_ctx.GetFrameSP())
+ symbol_ctx = frame_sp->GetSymbolContext(eSymbolContextEverything);
+
+ if (auto *format = m_debugger.GetStatuslineFormat())
+ FormatEntity::Format(*format, stream, &symbol_ctx, &exe_ctx, nullptr,
+ nullptr, false, false);
+
+ Draw(stream.GetString());
+}
More information about the lldb-commits
mailing list