[Lldb-commits] [lldb] LLDB Statusline (PR #121860)

Jonas Devlieghere via lldb-commits lldb-commits at lists.llvm.org
Mon Jan 13 09:58:58 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 91306183fbfee9e2d231180c5d7820452bd2a330 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       | 107 +++++-----------
 lldb/source/Core/Statusline.cpp     | 192 ++++++++++++++++++++++++++++
 6 files changed, 305 insertions(+), 79 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..61bb37ae842f99 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>(
@@ -1958,6 +1973,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 +2037,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 +2107,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