[Lldb-commits] [lldb] LLDB Statusline (PR #121860)
Jonas Devlieghere via lldb-commits
lldb-commits at lists.llvm.org
Mon Jan 13 10:38:59 PST 2025
https://github.com/JDevlieghere updated https://github.com/llvm/llvm-project/pull/121860
>From e82cc29033102dfed2a145e7235be8743c3bbdbe Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Fri, 3 Jan 2025 18:13:16 -0800
Subject: [PATCH 1/2] Add OptionValueFormatEntityList
---
lldb/include/lldb/Interpreter/OptionValue.h | 13 +-
.../Interpreter/OptionValueFormatEntityList.h | 62 +++++
lldb/include/lldb/Interpreter/OptionValues.h | 1 +
lldb/include/lldb/lldb-forward.h | 1 +
lldb/source/Interpreter/CMakeLists.txt | 1 +
lldb/source/Interpreter/OptionValue.cpp | 24 ++
.../OptionValueFormatEntityList.cpp | 223 ++++++++++++++++++
lldb/source/Interpreter/Property.cpp | 9 +
8 files changed, 331 insertions(+), 3 deletions(-)
create mode 100644 lldb/include/lldb/Interpreter/OptionValueFormatEntityList.h
create mode 100644 lldb/source/Interpreter/OptionValueFormatEntityList.cpp
diff --git a/lldb/include/lldb/Interpreter/OptionValue.h b/lldb/include/lldb/Interpreter/OptionValue.h
index d19c8b8fab6222..73944c133d3b4e 100644
--- a/lldb/include/lldb/Interpreter/OptionValue.h
+++ b/lldb/include/lldb/Interpreter/OptionValue.h
@@ -51,7 +51,8 @@ class OptionValue {
eTypeString,
eTypeUInt64,
eTypeUUID,
- eTypeFormatEntity
+ eTypeFormatEntity,
+ eTypeFormatEntityList,
};
enum {
@@ -72,7 +73,7 @@ class OptionValue {
virtual ~OptionValue() = default;
OptionValue(const OptionValue &other);
-
+
OptionValue& operator=(const OptionValue &other);
// Subclasses should override these functions
@@ -249,6 +250,9 @@ class OptionValue {
OptionValueFormatEntity *GetAsFormatEntity();
const OptionValueFormatEntity *GetAsFormatEntity() const;
+ OptionValueFormatEntityList *GetAsFormatEntityList();
+ const OptionValueFormatEntityList *GetAsFormatEntityList() const;
+
bool AppendFileSpecValue(FileSpec file_spec);
bool OptionWasSet() const { return m_value_was_set; }
@@ -286,6 +290,8 @@ class OptionValue {
return GetFileSpecValue();
if constexpr (std::is_same_v<T, FileSpecList>)
return GetFileSpecListValue();
+ if constexpr (std::is_same_v<T, std::vector<FormatEntity::Entry>>)
+ return GetFormatEntityList();
if constexpr (std::is_same_v<T, lldb::LanguageType>)
return GetLanguageValue();
if constexpr (std::is_same_v<T, llvm::StringRef>)
@@ -387,8 +393,9 @@ class OptionValue {
bool SetUUIDValue(const UUID &uuid);
const FormatEntity::Entry *GetFormatEntity() const;
+ std::vector<FormatEntity::Entry> GetFormatEntityList() const;
const RegularExpression *GetRegexValue() const;
-
+
mutable std::mutex m_mutex;
};
diff --git a/lldb/include/lldb/Interpreter/OptionValueFormatEntityList.h b/lldb/include/lldb/Interpreter/OptionValueFormatEntityList.h
new file mode 100644
index 00000000000000..4270bf26b54c9f
--- /dev/null
+++ b/lldb/include/lldb/Interpreter/OptionValueFormatEntityList.h
@@ -0,0 +1,62 @@
+//===-- OptionValueFormatEntityList.h --------------------------------*-
+// C++-*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_INTERPRETER_OPTIONVALUEFORMATENTITYLIST_H
+#define LLDB_INTERPRETER_OPTIONVALUEFORMATENTITYLIST_H
+
+#include "lldb/Core/FormatEntity.h"
+#include "lldb/Interpreter/OptionValue.h"
+
+namespace lldb_private {
+
+class OptionValueFormatEntityList
+ : public Cloneable<OptionValueFormatEntityList, OptionValue> {
+public:
+ OptionValueFormatEntityList();
+
+ OptionValueFormatEntityList(const OptionValueFormatEntityList &other)
+ : Cloneable(other), m_current_entries(other.m_current_entries),
+ m_current_formats(other.m_current_formats) {}
+
+ ~OptionValueFormatEntityList() override = default;
+
+ // Virtual subclass pure virtual overrides
+
+ OptionValue::Type GetType() const override { return eTypeFormatEntityList; }
+
+ void DumpValue(const ExecutionContext *exe_ctx, Stream &strm,
+ uint32_t dump_mask) override;
+
+ Status
+ SetValueFromString(llvm::StringRef value,
+ VarSetOperationType op = eVarSetOperationAssign) override;
+
+ void Clear() override;
+
+ std::vector<FormatEntity::Entry> GetCurrentValue() const {
+ std::lock_guard<std::recursive_mutex> lock(m_mutex);
+ return m_current_entries;
+ }
+
+protected:
+ llvm::Error Append(llvm::StringRef str);
+ llvm::Error Insert(size_t idx, llvm::StringRef str);
+ llvm::Error Replace(size_t idx, llvm::StringRef str);
+ llvm::Error Remove(size_t idx);
+
+ lldb::OptionValueSP Clone() const override;
+
+ std::vector<FormatEntity::Entry> m_current_entries;
+ std::vector<std::string> m_current_formats;
+ mutable std::recursive_mutex m_mutex;
+};
+
+} // namespace lldb_private
+
+#endif // LLDB_INTERPRETER_OPTIONVALUEFORMATENTITYLIST_H
diff --git a/lldb/include/lldb/Interpreter/OptionValues.h b/lldb/include/lldb/Interpreter/OptionValues.h
index 6efc9e1ad064ce..399119e290401b 100644
--- a/lldb/include/lldb/Interpreter/OptionValues.h
+++ b/lldb/include/lldb/Interpreter/OptionValues.h
@@ -22,6 +22,7 @@
#include "lldb/Interpreter/OptionValueFileSpecList.h"
#include "lldb/Interpreter/OptionValueFormat.h"
#include "lldb/Interpreter/OptionValueFormatEntity.h"
+#include "lldb/Interpreter/OptionValueFormatEntityList.h"
#include "lldb/Interpreter/OptionValueLanguage.h"
#include "lldb/Interpreter/OptionValuePathMappings.h"
#include "lldb/Interpreter/OptionValueProperties.h"
diff --git a/lldb/include/lldb/lldb-forward.h b/lldb/include/lldb/lldb-forward.h
index d09edeeccaff1a..5831d62f5e9338 100644
--- a/lldb/include/lldb/lldb-forward.h
+++ b/lldb/include/lldb/lldb-forward.h
@@ -150,6 +150,7 @@ class OptionValueFileSpec;
class OptionValueFileSpecList;
class OptionValueFormat;
class OptionValueFormatEntity;
+class OptionValueFormatEntityList;
class OptionValueLanguage;
class OptionValuePathMappings;
class OptionValueProperties;
diff --git a/lldb/source/Interpreter/CMakeLists.txt b/lldb/source/Interpreter/CMakeLists.txt
index 642263a8bda7fa..4fca862de9cfcc 100644
--- a/lldb/source/Interpreter/CMakeLists.txt
+++ b/lldb/source/Interpreter/CMakeLists.txt
@@ -41,6 +41,7 @@ add_lldb_library(lldbInterpreter NO_PLUGIN_DEPENDENCIES
OptionValueFileSpecList.cpp
OptionValueFormat.cpp
OptionValueFormatEntity.cpp
+ OptionValueFormatEntityList.cpp
OptionValueLanguage.cpp
OptionValuePathMappings.cpp
OptionValueProperties.cpp
diff --git a/lldb/source/Interpreter/OptionValue.cpp b/lldb/source/Interpreter/OptionValue.cpp
index b95f4fec339499..d7c1996b24a0cc 100644
--- a/lldb/source/Interpreter/OptionValue.cpp
+++ b/lldb/source/Interpreter/OptionValue.cpp
@@ -184,6 +184,18 @@ const OptionValueFormatEntity *OptionValue::GetAsFormatEntity() const {
return nullptr;
}
+OptionValueFormatEntityList *OptionValue::GetAsFormatEntityList() {
+ if (GetType() == OptionValue::eTypeFormatEntityList)
+ return static_cast<OptionValueFormatEntityList *>(this);
+ return nullptr;
+}
+
+const OptionValueFormatEntityList *OptionValue::GetAsFormatEntityList() const {
+ if (GetType() == OptionValue::eTypeFormatEntityList)
+ return static_cast<const OptionValueFormatEntityList *>(this);
+ return nullptr;
+}
+
OptionValuePathMappings *OptionValue::GetAsPathMappings() {
if (GetType() == OptionValue::eTypePathMap)
return static_cast<OptionValuePathMappings *>(this);
@@ -380,6 +392,13 @@ bool OptionValue::SetLanguageValue(lldb::LanguageType new_language) {
return false;
}
+std::vector<FormatEntity::Entry> OptionValue::GetFormatEntityList() const {
+ std::lock_guard<std::mutex> lock(m_mutex);
+ if (const OptionValueFormatEntityList *option_value = GetAsFormatEntityList())
+ return option_value->GetCurrentValue();
+ return {};
+}
+
const FormatEntity::Entry *OptionValue::GetFormatEntity() const {
std::lock_guard<std::mutex> lock(m_mutex);
if (const OptionValueFormatEntity *option_value = GetAsFormatEntity())
@@ -502,6 +521,8 @@ const char *OptionValue::GetBuiltinTypeAsCString(Type t) {
return "format";
case eTypeFormatEntity:
return "format-string";
+ case eTypeFormatEntityList:
+ return "format-string-list";
case eTypeLanguage:
return "language";
case eTypePathMap:
@@ -546,6 +567,9 @@ lldb::OptionValueSP OptionValue::CreateValueFromCStringForTypeMask(
case 1u << eTypeFormatEntity:
value_sp = std::make_shared<OptionValueFormatEntity>(nullptr);
break;
+ case 1u << eTypeFormatEntityList:
+ value_sp = std::make_shared<OptionValueFormatEntityList>();
+ break;
case 1u << eTypeLanguage:
value_sp = std::make_shared<OptionValueLanguage>(eLanguageTypeUnknown);
break;
diff --git a/lldb/source/Interpreter/OptionValueFormatEntityList.cpp b/lldb/source/Interpreter/OptionValueFormatEntityList.cpp
new file mode 100644
index 00000000000000..6f7d46fa68618c
--- /dev/null
+++ b/lldb/source/Interpreter/OptionValueFormatEntityList.cpp
@@ -0,0 +1,223 @@
+//===-- OptionValueFormatEntityList.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/Interpreter/OptionValueFormatEntityList.h"
+
+#include "lldb/Core/Module.h"
+#include "lldb/Interpreter/CommandInterpreter.h"
+#include "lldb/Utility/Stream.h"
+#include "lldb/Utility/StringList.h"
+using namespace lldb;
+using namespace lldb_private;
+
+OptionValueFormatEntityList::OptionValueFormatEntityList() {}
+
+void OptionValueFormatEntityList::Clear() {
+ m_current_entries.clear();
+ m_current_formats.clear();
+ m_value_was_set = false;
+}
+
+static void EscapeBackticks(llvm::StringRef str, std::string &dst) {
+ dst.clear();
+ dst.reserve(str.size());
+
+ for (size_t i = 0, e = str.size(); i != e; ++i) {
+ char c = str[i];
+ if (c == '`') {
+ if (i == 0 || str[i - 1] != '\\')
+ dst += '\\';
+ }
+ dst += c;
+ }
+}
+
+void OptionValueFormatEntityList::DumpValue(const ExecutionContext *exe_ctx,
+ Stream &strm, uint32_t dump_mask) {
+ if (dump_mask & eDumpOptionType)
+ strm.Printf("(%s)", GetTypeAsCString());
+ if (dump_mask & eDumpOptionValue) {
+ const bool one_line = dump_mask & eDumpOptionCommand;
+ const size_t size = m_current_formats.size();
+ if (dump_mask & eDumpOptionType)
+ strm.Printf(" =%s", (size > 0 && !one_line) ? "\n" : "");
+ if (!one_line)
+ strm.IndentMore();
+ for (uint32_t i = 0; i < size; ++i) {
+ if (!one_line) {
+ strm.Indent();
+ strm.Printf("[%u]: ", i);
+ }
+ strm << m_current_formats[i];
+ if (one_line)
+ strm << ' ';
+ }
+ if (!one_line)
+ strm.IndentLess();
+ }
+}
+
+static llvm::Expected<FormatEntity::Entry> Parse(llvm::StringRef str) {
+ FormatEntity::Entry entry;
+ Status error = FormatEntity::Parse(str, entry);
+ if (error.Fail())
+ return error.ToError();
+ return entry;
+}
+
+llvm::Error OptionValueFormatEntityList::Append(llvm::StringRef str) {
+ auto maybe_entry = Parse(str);
+ if (!maybe_entry)
+ return maybe_entry.takeError();
+
+ m_current_entries.emplace_back(*maybe_entry);
+ m_current_formats.emplace_back(str);
+
+ return llvm::Error::success();
+}
+
+llvm::Error OptionValueFormatEntityList::Insert(size_t idx,
+ llvm::StringRef str) {
+ if (idx >= m_current_formats.size())
+ return llvm::createStringError(
+ "invalid file list index %s, index must be 0 through %u", idx,
+ m_current_formats.size());
+
+ auto maybe_entry = Parse(str);
+ if (!maybe_entry)
+ return maybe_entry.takeError();
+
+ m_current_entries.insert(m_current_entries.begin() + idx, *maybe_entry);
+ m_current_formats.insert(m_current_formats.begin() + idx, std::string(str));
+
+ return llvm::Error::success();
+ return llvm::Error::success();
+}
+
+llvm::Error OptionValueFormatEntityList::Replace(size_t idx,
+ llvm::StringRef str) {
+ if (idx >= m_current_formats.size())
+ return llvm::createStringError(
+ "invalid file list index %s, index must be 0 through %u", idx,
+ m_current_formats.size());
+
+ auto maybe_entry = Parse(str);
+ if (!maybe_entry)
+ return maybe_entry.takeError();
+
+ m_current_entries[idx] = *maybe_entry;
+ m_current_formats[idx] = str;
+
+ return llvm::Error::success();
+}
+
+llvm::Error OptionValueFormatEntityList::Remove(size_t idx) {
+ if (idx >= m_current_formats.size())
+ return llvm::createStringError(
+ "invalid fromat entry list index %s, index must be 0 through %u", idx,
+ m_current_formats.size());
+
+ m_current_formats.erase(m_current_formats.begin() + idx);
+ m_current_entries.erase(m_current_entries.begin() + idx);
+
+ return llvm::Error::success();
+}
+
+Status
+OptionValueFormatEntityList::SetValueFromString(llvm::StringRef value_str,
+ VarSetOperationType op) {
+ std::lock_guard<std::recursive_mutex> lock(m_mutex);
+
+ Args args(value_str.str());
+ const size_t argc = args.GetArgumentCount();
+
+ switch (op) {
+ case eVarSetOperationClear:
+ Clear();
+ NotifyValueChanged();
+ break;
+
+ case eVarSetOperationReplace:
+
+ case eVarSetOperationAssign:
+ Clear();
+ // Fall through to append case
+ [[fallthrough]];
+ case eVarSetOperationAppend:
+ if (argc > 0) {
+ m_value_was_set = true;
+ for (size_t i = 0; i < argc; ++i) {
+ if (llvm::Error err = Append(args.GetArgumentAtIndex(i)))
+ return Status::FromError(std::move(err));
+ }
+ NotifyValueChanged();
+ } else {
+ return Status::FromErrorString(
+ "assign operation takes at least one file path argument");
+ }
+ break;
+ case eVarSetOperationInsertBefore:
+ case eVarSetOperationInsertAfter:
+ if (argc > 1) {
+ uint32_t idx;
+ if (!llvm::to_integer(args.GetArgumentAtIndex(0), idx))
+ return Status::FromErrorStringWithFormat("invalid index %s",
+ args.GetArgumentAtIndex(0));
+ if (op == eVarSetOperationInsertAfter)
+ ++idx;
+ for (size_t i = 1; i < argc; ++i, ++idx) {
+ if (llvm::Error err = Append(args.GetArgumentAtIndex(i)))
+ return Status::FromError(std::move(err));
+ }
+ NotifyValueChanged();
+ } else {
+ return Status::FromErrorString(
+ "insert operation takes an array index followed by "
+ "one or more values");
+ }
+ break;
+ case eVarSetOperationRemove:
+ if (argc > 0) {
+ std::vector<int> indexes;
+ for (size_t i = 0; i < argc; ++i) {
+ int idx;
+ if (!llvm::to_integer(args.GetArgumentAtIndex(i), idx))
+ return Status::FromErrorStringWithFormat(
+ "invalid array index '%s', aborting remove operation",
+ args.GetArgumentAtIndex(i));
+ indexes.push_back(idx);
+ }
+
+ if (indexes.empty())
+ return Status::FromErrorString(
+ "remove operation takes one or more array index");
+
+ // Sort and then erase in reverse so indexes are always valid.
+ llvm::sort(indexes);
+ for (auto i : llvm::reverse(indexes)) {
+ if (auto err = Remove(i))
+ return Status::FromError(std::move(err));
+ }
+ } else {
+ return Status::FromErrorString(
+ "remove operation takes one or more array index");
+ }
+ break;
+ case eVarSetOperationInvalid:
+ return OptionValue::SetValueFromString(value_str, op);
+ break;
+ }
+
+ return {};
+}
+
+OptionValueSP OptionValueFormatEntityList::Clone() const {
+ std::lock_guard<std::recursive_mutex> lock(m_mutex);
+ return Cloneable::Clone();
+}
diff --git a/lldb/source/Interpreter/Property.cpp b/lldb/source/Interpreter/Property.cpp
index 56e45363be89a1..331735f167385a 100644
--- a/lldb/source/Interpreter/Property.cpp
+++ b/lldb/source/Interpreter/Property.cpp
@@ -161,6 +161,15 @@ Property::Property(const PropertyDefinition &definition)
definition.default_cstr_value);
break;
+ case OptionValue::eTypeFormatEntityList:
+ // "definition.default_uint_value" is not used for a
+ // OptionValue::eTypeFormatEntityList
+ m_value_sp = std::make_shared<OptionValueFormatEntityList>();
+ if (definition.default_cstr_value) {
+ m_value_sp->SetValueFromString(definition.default_cstr_value);
+ }
+ break;
+
case OptionValue::eTypePathMap:
// "definition.default_uint_value" tells us if notifications should occur
// for path mappings
>From 2e210de4f46db713c74cb9ccfeb02d25d6e3e836 Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Mon, 13 Jan 2025 09:58:30 -0800
Subject: [PATCH 2/2] Add statusline
---
lldb/include/lldb/Core/Debugger.h | 7 +-
lldb/include/lldb/Core/Statusline.h | 69 ++++++++++
lldb/source/Core/CMakeLists.txt | 1 +
lldb/source/Core/CoreProperties.td | 8 ++
lldb/source/Core/Debugger.cpp | 115 ++++++-----------
lldb/source/Core/Statusline.cpp | 192 ++++++++++++++++++++++++++++
6 files changed, 312 insertions(+), 80 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..f6905d27551597 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;
+
+ std::vector<FormatEntity::Entry> GetStatuslineFormat() const;
+
llvm::StringRef GetShowProgressAnsiPrefix() const;
llvm::StringRef GetShowProgressAnsiSuffix() const;
@@ -728,7 +733,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;
diff --git a/lldb/include/lldb/Core/Statusline.h b/lldb/include/lldb/Core/Statusline.h
new file mode 100644
index 00000000000000..3e731f1d2d751e
--- /dev/null
+++ b/lldb/include/lldb/Core/Statusline.h
@@ -0,0 +1,69 @@
+//===-- 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 ReportProgress(const ProgressEventData &data);
+
+ 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;
+
+ struct ProgressReport {
+ uint64_t id;
+ std::string message;
+ };
+ llvm::SmallVector<ProgressReport, 8> m_progress_reports;
+ std::optional<ProgressReport> m_progress_report;
+
+ 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;
+
+ std::string m_ansi_prefix = "${ansi.bg.cyan}${ansi.fg.black}";
+ std::string m_ansi_suffix = "${ansi.normal}";
+ llvm::SmallVector<FormatEntity::Entry> m_components;
+};
+} // namespace lldb_private
+#endif // LLDB_CORE_STATUSBAR_H
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..37b0ec231d4d48 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", "FormatEntityList">,
+ Global,
+ DefaultStringValue<"${module.file.basename} ${line.file.basename}:${line.number}:${line.column} ${thread.stop-reason}">,
+ 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..e14962c064a059 100644
--- a/lldb/source/Core/Debugger.cpp
+++ b/lldb/source/Core/Debugger.cpp
@@ -376,6 +376,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 +394,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 +458,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);
+}
+
+std::vector<FormatEntity::Entry> Debugger::GetStatuslineFormat() const {
+ const uint32_t idx = ePropertyStatuslineFormat;
+ return GetPropertyAtIndexAs<std::vector<FormatEntity::Entry>>(idx, {});
+}
+
bool Debugger::GetUseAutosuggestion() const {
const uint32_t idx = ePropertyShowAutosuggestion;
return GetPropertyAtIndexAs<bool>(
@@ -1093,12 +1108,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 +1979,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 +2043,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 +2113,8 @@ 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();
-
- // 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;
- }
-
- // 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");
-
- // Flush the output.
- output->Flush();
+ if (m_statusline)
+ m_statusline->ReportProgress(*data);
}
void Debugger::HandleDiagnosticEvent(const lldb::EventSP &event_sp) {
diff --git a/lldb/source/Core/Statusline.cpp b/lldb/source/Core/Statusline.cpp
new file mode 100644
index 00000000000000..6aa41156dee8b7
--- /dev/null
+++ b/lldb/source/Core/Statusline.cpp
@@ -0,0 +1,192 @@
+//===-- 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 <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;
+
+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 uint32_t ellipsis = 3;
+ if (str.size() + 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 << ansi::FormatAnsiTerminalCodes(m_ansi_prefix);
+ out << str;
+ out << std::string(m_terminal_width - str.size(), ' ');
+ out << ansi::FormatAnsiTerminalCodes(m_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();
+ SymbolContext symbol_ctx;
+ if (auto frame_sp = exe_ctx.GetFrameSP())
+ symbol_ctx = frame_sp->GetSymbolContext(eSymbolContextEverything);
+
+ // Add the user-configured components.
+ bool add_separator = false;
+ for (const FormatEntity::Entry &entry : m_debugger.GetStatuslineFormat()) {
+ if (add_separator)
+ stream << " | ";
+ add_separator = FormatEntity::Format(entry, stream, &symbol_ctx, &exe_ctx,
+ nullptr, nullptr, false, false);
+ }
+
+ // Add progress reports at the end, if enabled.
+ if (m_debugger.GetShowProgress()) {
+ if (m_progress_reports.empty()) {
+ m_progress_report.reset();
+ } else {
+ m_progress_report.emplace(m_progress_reports.back());
+ }
+ if (m_progress_report) {
+ if (add_separator)
+ stream << " | ";
+ stream << m_progress_report->message;
+ }
+ }
+
+ Draw(stream.GetString());
+}
+
+void Statusline::ReportProgress(const ProgressEventData &data) {
+ // Make a local copy of the incoming progress report, which might get modified
+ // below.
+ ProgressReport progress_report{
+ data.GetID(), data.IsFinite()
+ ? llvm::formatv("[{0}/{1}] {2}", data.GetCompleted(),
+ data.GetTotal(), data.GetMessage())
+ .str()
+ : data.GetMessage()};
+
+ // Do some bookkeeping regardless of whether we're going to display
+ // progress reports.
+ 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);
+ }
+}
More information about the lldb-commits
mailing list