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

Jonas Devlieghere via lldb-commits lldb-commits at lists.llvm.org
Fri Jan 17 15:10:39 PST 2025


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

>From d2fdaec2cd06127d2b07e9e2a8ed68400cfd1a5d Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Mon, 13 Jan 2025 10:58:31 -0800
Subject: [PATCH] [lldb] Implement statusline
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         |  17 +-
 lldb/include/lldb/Core/Statusline.h           |  58 +++++++
 lldb/include/lldb/Utility/AnsiTerminal.h      |  27 +++
 lldb/source/Core/CMakeLists.txt               |   1 +
 lldb/source/Core/CoreProperties.td            |   8 +
 lldb/source/Core/Debugger.cpp                 | 145 ++++++++--------
 lldb/source/Core/FormatEntity.cpp             |  97 ++++++++---
 lldb/source/Core/IOHandlerCursesGUI.cpp       |  11 +-
 lldb/source/Core/Statusline.cpp               | 157 ++++++++++++++++++
 lldb/source/DataFormatters/TypeSummary.cpp    |   4 +-
 .../Plugins/Language/CPlusPlus/LibCxx.cpp     |   3 +-
 lldb/source/Target/StackFrame.cpp             |   2 +-
 lldb/source/Target/Thread.cpp                 |   3 +-
 lldb/unittests/Core/FormatEntityTest.cpp      |   3 +
 15 files changed, 443 insertions(+), 114 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 36f6df4118c21f..c40594a10040ef 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 {
@@ -207,17 +210,19 @@ struct Entry {
 
 bool Format(const Entry &entry, Stream &s, const SymbolContext *sc,
             const ExecutionContext *exe_ctx, const Address *addr,
-            ValueObject *valobj, bool function_changed, bool initial_function);
+            const Debugger *debugger, ValueObject *valobj,
+            bool function_changed, bool initial_function);
 
 bool FormatStringRef(const llvm::StringRef &format, Stream &s,
                      const SymbolContext *sc, const ExecutionContext *exe_ctx,
-                     const Address *addr, ValueObject *valobj,
-                     bool function_changed, bool initial_function);
+                     const Address *addr, const Debugger *debugger,
+                     ValueObject *valobj, bool function_changed,
+                     bool initial_function);
 
 bool FormatCString(const char *format, Stream &s, const SymbolContext *sc,
                    const ExecutionContext *exe_ctx, const Address *addr,
-                   ValueObject *valobj, bool function_changed,
-                   bool initial_function);
+                   const Debugger *debugger, ValueObject *valobj,
+                   bool function_changed, bool initial_function);
 
 Status Parse(const llvm::StringRef &format, Entry &entry);
 
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..cfd10968a5e230 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>
 
@@ -172,6 +173,32 @@ 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 (right.empty())
+      break;
+
+    std::tie(left, right) = right.split(ANSI_ESC_END);
+
+    // ANSI_ESC_END not found.
+    if (right.empty()) {
+      stripped += ANSI_ESC_START;
+      stripped += left;
+    }
+
+    str = right;
+  }
+  return stripped;
+}
+
 } // 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..9612ca14db8dd9 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;
@@ -1453,7 +1479,7 @@ bool Debugger::FormatDisassemblerAddress(const FormatEntity::Entry *format,
       (prev_sc->function == nullptr && prev_sc->symbol == nullptr)) {
     initial_function = true;
   }
-  return FormatEntity::Format(*format, s, sc, exe_ctx, addr, nullptr,
+  return FormatEntity::Format(*format, s, sc, exe_ctx, addr, nullptr, nullptr,
                               function_changed, initial_function);
 }
 
@@ -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 e13284832cf571..0767032186c083 100644
--- a/lldb/source/Core/FormatEntity.cpp
+++ b/lldb/source/Core/FormatEntity.cpp
@@ -162,7 +162,13 @@ 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)};
+
+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)
@@ -257,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);
@@ -322,6 +331,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);
@@ -355,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 "???";
 }
@@ -993,8 +1005,9 @@ static bool DumpValue(Stream &s, const SymbolContext *sc,
         success &= item->DumpPrintableRepresentation(s, val_obj_display,
                                                      custom_format);
       } else {
-        success &= FormatEntity::FormatStringRef(
-            special_directions, s, sc, exe_ctx, nullptr, item, false, false);
+        success &=
+            FormatEntity::FormatStringRef(special_directions, s, sc, exe_ctx,
+                                          nullptr, nullptr, item, false, false);
       }
 
       if (--max_num_children == 0) {
@@ -1151,14 +1164,15 @@ static void FormatInlinedBlock(Stream &out_stream, Block *block) {
 bool FormatEntity::FormatStringRef(const llvm::StringRef &format_str, Stream &s,
                                    const SymbolContext *sc,
                                    const ExecutionContext *exe_ctx,
-                                   const Address *addr, ValueObject *valobj,
-                                   bool function_changed,
+                                   const Address *addr,
+                                   const Debugger *debugger,
+                                   ValueObject *valobj, bool function_changed,
                                    bool initial_function) {
   if (!format_str.empty()) {
     FormatEntity::Entry root;
     Status error = FormatEntity::Parse(format_str, root);
     if (error.Success()) {
-      return FormatEntity::Format(root, s, sc, exe_ctx, addr, valobj,
+      return FormatEntity::Format(root, s, sc, exe_ctx, addr, debugger, valobj,
                                   function_changed, initial_function);
     }
   }
@@ -1168,14 +1182,15 @@ bool FormatEntity::FormatStringRef(const llvm::StringRef &format_str, Stream &s,
 bool FormatEntity::FormatCString(const char *format, Stream &s,
                                  const SymbolContext *sc,
                                  const ExecutionContext *exe_ctx,
-                                 const Address *addr, ValueObject *valobj,
-                                 bool function_changed, bool initial_function) {
+                                 const Address *addr, const Debugger *debugger,
+                                 ValueObject *valobj, bool function_changed,
+                                 bool initial_function) {
   if (format && format[0]) {
     FormatEntity::Entry root;
     llvm::StringRef format_str(format);
     Status error = FormatEntity::Parse(format_str, root);
     if (error.Success()) {
-      return FormatEntity::Format(root, s, sc, exe_ctx, addr, valobj,
+      return FormatEntity::Format(root, s, sc, exe_ctx, addr, debugger, valobj,
                                   function_changed, initial_function);
     }
   }
@@ -1185,8 +1200,17 @@ bool FormatEntity::FormatCString(const char *format, Stream &s,
 bool FormatEntity::Format(const Entry &entry, Stream &s,
                           const SymbolContext *sc,
                           const ExecutionContext *exe_ctx, const Address *addr,
-                          ValueObject *valobj, bool function_changed,
-                          bool initial_function) {
+                          const Debugger *debugger, ValueObject *valobj,
+                          bool function_changed, bool initial_function) {
+  auto CalculateDebugger = [&]() -> const Debugger * {
+    if (debugger)
+      return debugger;
+    if (exe_ctx)
+      if (Target *target = exe_ctx->GetTargetPtr())
+        return &target->GetDebugger();
+    return nullptr;
+  };
+
   switch (entry.type) {
   case Entry::Type::Invalid:
   case Entry::Type::ParentNumber: // Only used for
@@ -1195,12 +1219,9 @@ 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 (const Debugger *debugger_ptr = CalculateDebugger()) {
+      if (debugger_ptr->GetUseColor()) {
+        s.PutCString(entry.string);
       }
     }
     // Always return true, so colors being disabled is transparent.
@@ -1208,8 +1229,8 @@ bool FormatEntity::Format(const Entry &entry, Stream &s,
 
   case Entry::Type::Root:
     for (const auto &child : entry.children) {
-      if (!Format(child, s, sc, exe_ctx, addr, valobj, function_changed,
-                  initial_function)) {
+      if (!Format(child, s, sc, exe_ctx, addr, debugger, valobj,
+                  function_changed, initial_function)) {
         return false; // If any item of root fails, then the formatting fails
       }
     }
@@ -1223,7 +1244,7 @@ bool FormatEntity::Format(const Entry &entry, Stream &s,
     StreamString scope_stream;
     bool success = false;
     for (const auto &child : entry.children) {
-      success = Format(child, scope_stream, sc, exe_ctx, addr, valobj,
+      success = Format(child, scope_stream, sc, exe_ctx, addr, debugger, valobj,
                        function_changed, initial_function);
       if (!success)
         break;
@@ -1469,6 +1490,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();
@@ -1898,7 +1932,28 @@ bool FormatEntity::Format(const Entry &entry, Stream &s,
       return true;
     }
     return false;
+
+  case Entry::Type::ProgressCount:
+    if (const Debugger *debugger_ptr = CalculateDebugger()) {
+      if (auto progress = debugger_ptr->GetCurrentProgressReport()) {
+        if (progress->total != UINT64_MAX) {
+          s.Printf("[%llu/%llu]", progress->completed, progress->total);
+          return true;
+        }
+      }
+    }
+    return false;
+
+  case Entry::Type::ProgressMessage:
+    if (const Debugger *debugger_ptr = CalculateDebugger()) {
+      if (auto progress = debugger_ptr->GetCurrentProgressReport()) {
+        s.PutCString(progress->message);
+        return true;
+      }
+    }
+    return false;
   }
+
   return false;
 }
 
diff --git a/lldb/source/Core/IOHandlerCursesGUI.cpp b/lldb/source/Core/IOHandlerCursesGUI.cpp
index 456ce7d16e102d..0e98912fe33d54 100644
--- a/lldb/source/Core/IOHandlerCursesGUI.cpp
+++ b/lldb/source/Core/IOHandlerCursesGUI.cpp
@@ -5059,7 +5059,7 @@ class FrameTreeDelegate : public TreeDelegate {
             frame_sp->GetSymbolContext(eSymbolContextEverything);
         ExecutionContext exe_ctx(frame_sp);
         if (FormatEntity::Format(m_format, strm, &sc, &exe_ctx, nullptr,
-                                 nullptr, false, false)) {
+                                 nullptr, nullptr, false, false)) {
           int right_pad = 1;
           window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
         }
@@ -5117,7 +5117,7 @@ class ThreadTreeDelegate : public TreeDelegate {
       StreamString strm;
       ExecutionContext exe_ctx(thread_sp);
       if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr,
-                               nullptr, false, false)) {
+                               nullptr, nullptr, false, false)) {
         int right_pad = 1;
         window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
       }
@@ -5216,7 +5216,7 @@ class ThreadsTreeDelegate : public TreeDelegate {
       StreamString strm;
       ExecutionContext exe_ctx(process_sp);
       if (FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr,
-                               nullptr, false, false)) {
+                               nullptr, nullptr, false, false)) {
         int right_pad = 1;
         window.PutCStringTruncated(right_pad, strm.GetString().str().c_str());
       }
@@ -6747,8 +6747,9 @@ class StatusBarWindowDelegate : public WindowDelegate {
 
       if (StateIsStoppedState(state, true)) {
         StreamString strm;
-        if (thread && FormatEntity::Format(m_format, strm, nullptr, &exe_ctx,
-                                           nullptr, nullptr, false, false)) {
+        if (thread &&
+            FormatEntity::Format(m_format, strm, nullptr, &exe_ctx, nullptr,
+                                 nullptr, nullptr, false, false)) {
           window.MoveCursor(40, 0);
           window.PutCStringTruncated(1, strm.GetString().str().c_str());
         }
diff --git a/lldb/source/Core/Statusline.cpp b/lldb/source/Core/Statusline.cpp
new file mode 100644
index 00000000000000..37035b83cf84df
--- /dev/null
+++ b/lldb/source/Core/Statusline.cpp
@@ -0,0 +1,157 @@
+//===-- 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 = 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);
+
+  printf("size = %llu, width = %llu\n", str.size(), column_width);
+
+  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();
+  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,
+                         &m_debugger, nullptr, false, false);
+
+  Draw(stream.GetString());
+}
diff --git a/lldb/source/DataFormatters/TypeSummary.cpp b/lldb/source/DataFormatters/TypeSummary.cpp
index 2c863b364538f3..d9644ca85f2120 100644
--- a/lldb/source/DataFormatters/TypeSummary.cpp
+++ b/lldb/source/DataFormatters/TypeSummary.cpp
@@ -102,8 +102,8 @@ bool StringSummaryFormat::FormatObject(ValueObject *valobj, std::string &retval,
     return true;
   } else {
     if (FormatEntity::Format(m_format, s, &sc, &exe_ctx,
-                             &sc.line_entry.range.GetBaseAddress(), valobj,
-                             false, false)) {
+                             &sc.line_entry.range.GetBaseAddress(), nullptr,
+                             valobj, false, false)) {
       retval.assign(std::string(s.GetString()));
       return true;
     } else {
diff --git a/lldb/source/Plugins/Language/CPlusPlus/LibCxx.cpp b/lldb/source/Plugins/Language/CPlusPlus/LibCxx.cpp
index 6d0ccdbbe4a71d..238d8a8740ab48 100644
--- a/lldb/source/Plugins/Language/CPlusPlus/LibCxx.cpp
+++ b/lldb/source/Plugins/Language/CPlusPlus/LibCxx.cpp
@@ -437,7 +437,8 @@ bool lldb_private::formatters::LibcxxContainerSummaryProvider(
     stream.Printf("0x%016" PRIx64 " ", value);
   }
   return FormatEntity::FormatStringRef("size=${svar%#}", stream, nullptr,
-                                       nullptr, nullptr, &valobj, false, false);
+                                       nullptr, nullptr, nullptr, &valobj,
+                                       false, false);
 }
 
 /// The field layout in a libc++ string (cap, side, data or data, size, cap).
diff --git a/lldb/source/Target/StackFrame.cpp b/lldb/source/Target/StackFrame.cpp
index 4d068638f42b66..ad4ba5c191a4e0 100644
--- a/lldb/source/Target/StackFrame.cpp
+++ b/lldb/source/Target/StackFrame.cpp
@@ -1880,7 +1880,7 @@ bool StackFrame::DumpUsingFormat(Stream &strm,
   s.PutCString(frame_marker);
 
   if (format && FormatEntity::Format(*format, s, &m_sc, &exe_ctx, nullptr,
-                                     nullptr, false, false)) {
+                                     nullptr, nullptr, false, false)) {
     strm.PutCString(s.GetString());
     return true;
   }
diff --git a/lldb/source/Target/Thread.cpp b/lldb/source/Target/Thread.cpp
index b5261310970611..ee0815f34b8e10 100644
--- a/lldb/source/Target/Thread.cpp
+++ b/lldb/source/Target/Thread.cpp
@@ -1648,7 +1648,8 @@ bool Thread::DumpUsingFormat(Stream &strm, uint32_t frame_idx,
   }
 
   return FormatEntity::Format(*format, strm, frame_sp ? &frame_sc : nullptr,
-                              &exe_ctx, nullptr, nullptr, false, false);
+                              &exe_ctx, nullptr, nullptr, nullptr, false,
+                              false);
 }
 
 void Thread::DumpUsingSettingsFormat(Stream &strm, uint32_t frame_idx,
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) {



More information about the lldb-commits mailing list