[Lldb-commits] [lldb] [lldb] Implement a statusline in LLDB (PR #121860)

via lldb-commits lldb-commits at lists.llvm.org
Fri Jan 17 17:30:41 PST 2025


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-lldb

Author: Jonas Devlieghere (JDevlieghere)

<details>
<summary>Changes</summary>

Add a statusline to command-line LLDB to display progress events and
other information related to the current state of the debugger. The
statusline is a dedicated area displayed the bottom of the screen. The
contents of the status line are configurable through a setting
consisting of LLDB’s format strings.

The statusline is configurable through the `statusline-format` setting.
The default configuration shows the target name, the current file, the
stop reason and the current progress event.

```
(lldb) settings show statusline-format
statusline-format (format-string) = "${ansi.bg.cyan}${ansi.fg.black}{${target.file.basename}}{ | ${line.file.basename}:${line.number}:${line.column}}{ | ${thread.stop-reason}}{ | {${progress.count} }${progress.message}}"
```

The statusline is enabled by default, but can be disabled with the
following setting:

```
(lldb) settings set show-statusline false
```

The statusline supersedes the current progress reporting implementation.
Consequently, the following settings no longer have any effect (but
continue to exist):

```
show-progress             -- Whether to show progress or not if the debugger's output is an interactive color-enabled terminal.
show-progress-ansi-prefix -- When displaying progress in a color-enabled terminal, use the ANSI terminal code specified in this format immediately before the progress message.
show-progress-ansi-suffix -- When displaying progress in a color-enabled terminal, use the ANSI terminal code specified in this format immediately after the progress message.
```

RFC: https://discourse.llvm.org/t/rfc-lldb-statusline/83948

---

Patch is 36.09 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/121860.diff


14 Files Affected:

- (modified) lldb/docs/use/formatting.rst (+6-2) 
- (modified) lldb/include/lldb/Core/Debugger.h (+20-1) 
- (modified) lldb/include/lldb/Core/FormatEntity.h (+4-1) 
- (added) lldb/include/lldb/Core/Statusline.h (+58) 
- (modified) lldb/include/lldb/Utility/AnsiTerminal.h (+39) 
- (modified) lldb/source/Core/CMakeLists.txt (+1) 
- (modified) lldb/source/Core/CoreProperties.td (+8) 
- (modified) lldb/source/Core/Debugger.cpp (+68-75) 
- (modified) lldb/source/Core/FormatEntity.cpp (+56-8) 
- (added) lldb/source/Core/Statusline.cpp (+161) 
- (modified) lldb/source/Host/common/Editline.cpp (+3-1) 
- (modified) lldb/test/API/terminal/TestEditline.py (+16) 
- (modified) lldb/unittests/Core/FormatEntityTest.cpp (+3) 
- (modified) lldb/unittests/Utility/AnsiTerminalTest.cpp (+15) 


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

``````````

</details>


https://github.com/llvm/llvm-project/pull/121860


More information about the lldb-commits mailing list