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

>From 13524447f9af1c8d1923e9ef8cc3693a1c53253a Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jo...@devlieghere.com>
Date: Fri, 17 Jan 2025 17:10:36 -0800
Subject: [PATCH 1/5] [lldb] Implement a statusline in LLDB
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

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

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

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

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

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

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

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

RFC: https://discourse.llvm.org/t/rfc-lldb-statusline/83948
---
 lldb/include/lldb/Core/Debugger.h             |  24 ++-
 lldb/include/lldb/Core/FormatEntity.h         |   4 +-
 lldb/include/lldb/Core/Statusline.h           |  58 ++++++
 .../Python/lldbsuite/test/lldbtest.py         |   2 +
 lldb/source/Core/CMakeLists.txt               |   1 +
 lldb/source/Core/CoreProperties.td            |   8 +
 lldb/source/Core/Debugger.cpp                 | 159 ++++++++--------
 lldb/source/Core/FormatEntity.cpp             |  44 ++++-
 lldb/source/Core/Statusline.cpp               | 171 ++++++++++++++++++
 .../TestTrimmedProgressReporting.py           |  51 ------
 .../API/functionalities/statusline/Makefile   |   3 +
 .../statusline/TestStatusline.py              |  57 ++++++
 .../API/functionalities/statusline/main.c     |  11 ++
 13 files changed, 458 insertions(+), 135 deletions(-)
 create mode 100644 lldb/include/lldb/Core/Statusline.h
 create mode 100644 lldb/source/Core/Statusline.cpp
 delete mode 100644 
lldb/test/API/functionalities/progress_reporting/TestTrimmedProgressReporting.py
 create mode 100644 lldb/test/API/functionalities/statusline/Makefile
 create mode 100644 lldb/test/API/functionalities/statusline/TestStatusline.py
 create mode 100644 lldb/test/API/functionalities/statusline/main.c

diff --git a/lldb/include/lldb/Core/Debugger.h 
b/lldb/include/lldb/Core/Debugger.h
index 6ebc6147800e1..9e2100662c6de 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"
@@ -303,6 +304,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;
@@ -599,11 +604,20 @@ 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;
   friend class Progress;
   friend class ProgressManager;
+  friend class Statusline;
 
   /// Report progress events.
   ///
@@ -656,6 +670,8 @@ class Debugger : public 
std::enable_shared_from_this<Debugger>,
   lldb::LockableStreamFileSP GetErrorStreamSP() { return m_error_stream_sp; }
   /// @}
 
+  bool StatuslineSupported();
+
   void PushIOHandler(const lldb::IOHandlerSP &reader_sp,
                      bool cancel_top_handler = true);
 
@@ -732,7 +748,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;
@@ -749,6 +765,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 c9d5af1f31673..51e9ce37e54e7 100644
--- a/lldb/include/lldb/Core/FormatEntity.h
+++ b/lldb/include/lldb/Core/FormatEntity.h
@@ -100,7 +100,9 @@ struct Entry {
     LineEntryColumn,
     LineEntryStartAddress,
     LineEntryEndAddress,
-    CurrentPCArrow
+    CurrentPCArrow,
+    ProgressCount,
+    ProgressMessage,
   };
 
   struct Definition {
diff --git a/lldb/include/lldb/Core/Statusline.h 
b/lldb/include/lldb/Core/Statusline.h
new file mode 100644
index 0000000000000..21bd58d933b9e
--- /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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_CORE_STATUSLINE_H
+#define LLDB_CORE_STATUSLINE_H
+
+#include "lldb/lldb-forward.h"
+#include "llvm/ADT/StringRef.h"
+#include <csignal>
+#include <string>
+
+namespace lldb_private {
+class Statusline {
+public:
+  Statusline(Debugger &debugger);
+  ~Statusline();
+
+  /// Reduce the scroll window and draw the statusline.
+  void Enable();
+
+  /// Hide the statusline and extend the scroll window.
+  void Disable();
+
+  /// Redraw the statusline. If update is false, this will redraw the last
+  /// string.
+  void Redraw(bool update = true);
+
+  /// Inform the statusline that the terminal dimensions have changed.
+  void TerminalSizeChanged();
+
+private:
+  /// Draw the statusline with the given text.
+  void Draw(std::string msg);
+
+  /// Update terminal dimensions.
+  void UpdateTerminalProperties();
+
+  /// Set the scroll window to the given height.
+  void SetScrollWindow(uint64_t height);
+
+  /// Clear the statusline (without redrawing the background).
+  void Reset();
+
+  Debugger &m_debugger;
+  std::string m_last_str;
+
+  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;
+};
+} // namespace lldb_private
+#endif // LLDB_CORE_STATUSLINE_H
diff --git a/lldb/packages/Python/lldbsuite/test/lldbtest.py 
b/lldb/packages/Python/lldbsuite/test/lldbtest.py
index 81b286340560d..056ae1a0e62af 100644
--- a/lldb/packages/Python/lldbsuite/test/lldbtest.py
+++ b/lldb/packages/Python/lldbsuite/test/lldbtest.py
@@ -765,6 +765,8 @@ def setUpCommands(cls):
             # Disable fix-its by default so that incorrect expressions in 
tests don't
             # pass just because Clang thinks it has a fix-it.
             "settings set target.auto-apply-fixits false",
+            # Disable the statusline in PExpect tests.
+            "settings set show-statusline false",
             # Testsuite runs in parallel and the host can have also other load.
             "settings set plugin.process.gdb-remote.packet-timeout 60",
             'settings set symbols.clang-modules-cache-path "{}"'.format(
diff --git a/lldb/source/Core/CMakeLists.txt b/lldb/source/Core/CMakeLists.txt
index 82fb5f42f9f4b..70509ca4398c5 100644
--- a/lldb/source/Core/CMakeLists.txt
+++ b/lldb/source/Core/CMakeLists.txt
@@ -50,6 +50,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 d3816c3070bbc..0c6f93cb23e45 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 585138535203d..38e0b1505edec 100644
--- a/lldb/source/Core/Debugger.cpp
+++ b/lldb/source/Core/Debugger.cpp
@@ -243,6 +243,19 @@ 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[ePropertyShowStatusline].name) {
+      // Statusline setting changed. If we have a statusline instance, update 
it
+      // now. Otherwise it will get created in the default event handler.
+      if (StatuslineSupported())
+        m_statusline.emplace(*this);
+      else
+        m_statusline.reset();
+    } else if (property_path ==
+               g_debugger_properties[ePropertyStatuslineFormat].name) {
+      // Statusline format changed. Redraw the statusline.
+      if (m_statusline)
+        m_statusline->Redraw();
     } else if (property_path ==
                g_debugger_properties[ePropertyUseSourceCache].name) {
       // use-source-cache changed. Wipe out the cache contents if it was
@@ -375,6 +388,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;
 }
@@ -391,6 +406,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;
 }
@@ -453,6 +470,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>(
@@ -1096,12 +1124,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;
@@ -1925,6 +1959,17 @@ void Debugger::CancelForwardEvents(const ListenerSP 
&listener_sp) {
   m_forward_listener_sp.reset();
 }
 
+bool Debugger::StatuslineSupported() {
+  if (GetShowStatusline()) {
+    if (lldb::LockableStreamFileSP stream_sp = GetOutputStreamSP()) {
+      File &file = stream_sp->GetUnlockedFile();
+      return file.GetIsInteractive() && file.GetIsRealTerminal() &&
+             file.GetIsTerminalWithColors();
+    }
+  }
+  return false;
+}
+
 lldb::thread_result_t Debugger::DefaultEventHandler() {
   ListenerSP listener_sp(GetListener());
   ConstString broadcaster_class_target(Target::GetStaticBroadcasterClass());
@@ -1964,6 +2009,9 @@ 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 && StatuslineSupported())
+    m_statusline.emplace(*this);
+
   bool done = false;
   while (!done) {
     EventSP event_sp;
@@ -2018,8 +2066,14 @@ lldb::thread_result_t Debugger::DefaultEventHandler() {
         if (m_forward_listener_sp)
           m_forward_listener_sp->AddEvent(event_sp);
       }
+      if (m_statusline)
+        m_statusline->Redraw();
     }
   }
+
+  if (m_statusline)
+    m_statusline.reset();
+
   return {};
 }
 
@@ -2082,84 +2136,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.
-  FileSP file_sp = GetOutputFileSP();
-  if (!file_sp->GetIsInteractive() || !file_sp->GetIsTerminalWithColors())
-    return;
-
-  StreamUP 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->Redraw();
+}
 
-  // 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 7fe22994d7f7e..8a32195769357 100644
--- a/lldb/source/Core/FormatEntity.cpp
+++ b/lldb/source/Core/FormatEntity.cpp
@@ -166,6 +166,10 @@ constexpr Definition g_target_child_entries[] = {
     Entry::DefinitionWithChildren("file", EntryType::TargetFile,
                                   g_file_child_entries)};
 
+constexpr Definition g_progress_child_entries[] = {
+    Definition("count", EntryType::ProgressCount),
+    Definition("message", EntryType::ProgressMessage)};
+
 #define _TO_STR2(_val) #_val
 #define _TO_STR(_val) _TO_STR2(_val)
 
@@ -259,7 +263,10 @@ constexpr Definition g_top_level_entries[] = {
     Entry::DefinitionWithChildren("target", EntryType::Invalid,
                                   g_target_child_entries),
     Entry::DefinitionWithChildren("var", EntryType::Variable,
-                                  g_var_child_entries, true)};
+                                  g_var_child_entries, true),
+    Entry::DefinitionWithChildren("progress", EntryType::Invalid,
+                                  g_progress_child_entries),
+};
 
 constexpr Definition g_root = Entry::DefinitionWithChildren(
     "<root>", EntryType::Root, g_top_level_entries);
@@ -358,6 +365,8 @@ const char *FormatEntity::Entry::TypeToCString(Type t) {
     ENUM_TO_CSTR(LineEntryStartAddress);
     ENUM_TO_CSTR(LineEntryEndAddress);
     ENUM_TO_CSTR(CurrentPCArrow);
+    ENUM_TO_CSTR(ProgressCount);
+    ENUM_TO_CSTR(ProgressMessage);
   }
   return "???";
 }
@@ -1198,12 +1207,10 @@ bool FormatEntity::Format(const Entry &entry, Stream &s,
                                   // FormatEntity::Entry::Definition encoding
     return false;
   case Entry::Type::EscapeCode:
-    if (exe_ctx) {
-      if (Target *target = exe_ctx->GetTargetPtr()) {
-        Debugger &debugger = target->GetDebugger();
-        if (debugger.GetUseColor()) {
-          s.PutCString(entry.string);
-        }
+    if (Target *target = Target::GetTargetFromContexts(exe_ctx, sc)) {
+      Debugger &debugger = target->GetDebugger();
+      if (debugger.GetUseColor()) {
+        s.PutCString(entry.string);
       }
     }
     // Always return true, so colors being disabled is transparent.
@@ -1912,7 +1919,30 @@ bool FormatEntity::Format(const Entry &entry, Stream &s,
       return true;
     }
     return false;
+
+  case Entry::Type::ProgressCount:
+    if (Target *target = Target::GetTargetFromContexts(exe_ctx, sc)) {
+      Debugger &debugger = target->GetDebugger();
+      if (auto progress = debugger.GetCurrentProgressReport()) {
+        if (progress->total != UINT64_MAX) {
+          s.Format("[{0}/{1}]", progress->completed, progress->total);
+          return true;
+        }
+      }
+    }
+    return false;
+
+  case Entry::Type::ProgressMessage:
+    if (Target *target = Target::GetTargetFromContexts(exe_ctx, sc)) {
+      Debugger &debugger = target->GetDebugger();
+      if (auto progress = debugger.GetCurrentProgressReport()) {
+        s.PutCString(progress->message);
+        return true;
+      }
+    }
+    return false;
   }
+
   return false;
 }
 
diff --git a/lldb/source/Core/Statusline.cpp b/lldb/source/Core/Statusline.cpp
new file mode 100644
index 0000000000000..a6c3e778e7179
--- /dev/null
+++ b/lldb/source/Core/Statusline.cpp
@@ -0,0 +1,171 @@
+//===-- 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/StreamFile.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"
+
+#define ESCAPE "\x1b"
+#define ANSI_NORMAL ESCAPE "[0m"
+#define ANSI_SAVE_CURSOR ESCAPE "7"
+#define ANSI_RESTORE_CURSOR ESCAPE "8"
+#define ANSI_CLEAR_BELOW ESCAPE "[J"
+#define ANSI_CLEAR_LINE "\r\x1B[2K"
+#define ANSI_SET_SCROLL_ROWS ESCAPE "[0;%ur"
+#define ANSI_TO_START_OF_ROW ESCAPE "[%u;0f"
+#define ANSI_UP_ROWS ESCAPE "[%dA"
+#define ANSI_DOWN_ROWS ESCAPE "[%dB"
+#define ANSI_FORWARD_COLS ESCAPE "\033[%dC"
+#define ANSI_BACKWARD_COLS ESCAPE "\033[%dD"
+
+using namespace lldb;
+using namespace lldb_private;
+
+static size_t ColumnWidth(llvm::StringRef str) {
+  std::string stripped = ansi::StripAnsiTerminalCodes(str);
+  return llvm::sys::locale::columnWidth(stripped);
+}
+
+Statusline::Statusline(Debugger &debugger) : m_debugger(debugger) { Enable(); }
+
+Statusline::~Statusline() { Disable(); }
+
+void Statusline::TerminalSizeChanged() {
+  m_terminal_size_has_changed = 1;
+
+  // This definitely isn't signal safe, but the best we can do, until we
+  // have proper signal-catching thread.
+  Redraw(/*update=*/false);
+}
+
+void Statusline::Enable() {
+  UpdateTerminalProperties();
+
+  // Reduce the scroll window to make space for the status bar below.
+  SetScrollWindow(m_terminal_height - 1);
+
+  // Draw the statusline.
+  Redraw();
+}
+
+void Statusline::Disable() {
+  UpdateTerminalProperties();
+
+  // Extend the scroll window to cover the status bar.
+  SetScrollWindow(m_terminal_height);
+}
+
+void Statusline::Draw(std::string str) {
+  static constexpr const size_t g_ellipsis = 3;
+
+  UpdateTerminalProperties();
+
+  m_last_str = str;
+
+  size_t column_width = ColumnWidth(str);
+
+  if (column_width + g_ellipsis >= m_terminal_width) {
+    // FIXME: If there are hidden characters (e.g. UTF-8, ANSI escape
+    // characters), this will strip the string more than necessary. Ideally we
+    // want to strip until column_width == m_terminal_width.
+    str = str.substr(0, m_terminal_width);
+    str.replace(m_terminal_width - g_ellipsis, g_ellipsis, "...");
+    column_width = ColumnWidth(str);
+  }
+
+  if (lldb::LockableStreamFileSP stream_sp = m_debugger.GetOutputStreamSP()) {
+    LockedStreamFile locked_stream = stream_sp->Lock();
+    locked_stream << ANSI_SAVE_CURSOR;
+    locked_stream.Printf(ANSI_TO_START_OF_ROW,
+                         static_cast<unsigned>(m_terminal_height));
+    locked_stream << ANSI_CLEAR_LINE;
+    locked_stream << str;
+    if (column_width < m_terminal_width)
+      locked_stream << std::string(m_terminal_width - column_width, ' ');
+    locked_stream << ANSI_NORMAL;
+    locked_stream << ANSI_RESTORE_CURSOR;
+  }
+}
+
+void Statusline::Reset() {
+  if (lldb::LockableStreamFileSP stream_sp = m_debugger.GetOutputStreamSP()) {
+    LockedStreamFile locked_stream = stream_sp->Lock();
+    locked_stream << ANSI_SAVE_CURSOR;
+    locked_stream.Printf(ANSI_TO_START_OF_ROW,
+                         static_cast<unsigned>(m_terminal_height));
+    locked_stream << ANSI_CLEAR_LINE;
+    locked_stream << ANSI_RESTORE_CURSOR;
+  }
+}
+
+void Statusline::UpdateTerminalProperties() {
+  if (m_terminal_size_has_changed == 0)
+    return;
+
+  // Clear the previous statusline using the previous dimensions.
+  Reset();
+
+  m_terminal_width = m_debugger.GetTerminalWidth();
+  m_terminal_height = m_debugger.GetTerminalHeight();
+
+  // 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) {
+  if (lldb::LockableStreamFileSP stream_sp = m_debugger.GetOutputStreamSP()) {
+    LockedStreamFile locked_stream = stream_sp->Lock();
+    locked_stream << '\n';
+    locked_stream << ANSI_SAVE_CURSOR;
+    locked_stream.Printf(ANSI_SET_SCROLL_ROWS, static_cast<unsigned>(height));
+    locked_stream << ANSI_RESTORE_CURSOR;
+    locked_stream.Printf(ANSI_UP_ROWS, 1);
+    locked_stream << ANSI_CLEAR_BELOW;
+  }
+
+  m_scroll_height = height;
+}
+
+void Statusline::Redraw(bool update) {
+  if (!update) {
+    Draw(m_last_str);
+    return;
+  }
+
+  StreamString stream;
+  ExecutionContext exe_ctx =
+      m_debugger.GetCommandInterpreter().GetExecutionContext();
+
+  // For colors and progress events, the format entity needs access to the
+  // debugger, which requires a target in the execution context.
+  if (!exe_ctx.HasTargetScope())
+    exe_ctx.SetTargetPtr(&m_debugger.GetSelectedOrDummyTarget());
+
+  SymbolContext symbol_ctx;
+  if (auto frame_sp = exe_ctx.GetFrameSP())
+    symbol_ctx = frame_sp->GetSymbolContext(eSymbolContextEverything);
+
+  if (auto *format = m_debugger.GetStatuslineFormat())
+    FormatEntity::Format(*format, stream, &symbol_ctx, &exe_ctx, nullptr,
+                         nullptr, false, false);
+
+  Draw(std::string(stream.GetString()));
+}
diff --git 
a/lldb/test/API/functionalities/progress_reporting/TestTrimmedProgressReporting.py
 
b/lldb/test/API/functionalities/progress_reporting/TestTrimmedProgressReporting.py
deleted file mode 100644
index 3cf7b9d210089..0000000000000
--- 
a/lldb/test/API/functionalities/progress_reporting/TestTrimmedProgressReporting.py
+++ /dev/null
@@ -1,51 +0,0 @@
-"""
-Test trimming long progress report in tiny terminal windows
-"""
-
-import os
-import tempfile
-import re
-
-import lldb
-from lldbsuite.test.decorators import *
-from lldbsuite.test.lldbtest import *
-from lldbsuite.test.lldbpexpect import PExpectTest
-
-
-class TestTrimmedProgressReporting(PExpectTest):
-    def do_test(self, term_width, pattern_list):
-        self.build()
-        # Start with a small window
-        self.launch(use_colors=True)
-        self.expect("set set show-progress true")
-        self.expect(
-            "set show show-progress", substrs=["show-progress (boolean) = 
true"]
-        )
-        self.expect("set set term-width " + str(term_width))
-        self.expect(
-            "set show term-width",
-            substrs=["term-width (unsigned) = " + str(term_width)],
-        )
-
-        self.child.send("file " + self.getBuildArtifact("a.out") + "\n")
-        self.child.expect(pattern_list)
-
-    # PExpect uses many timeouts internally and doesn't play well
-    # under ASAN on a loaded machine..
-    @skipIfAsan
-    @skipIfEditlineSupportMissing
-    def test_trimmed_progress_message(self):
-        self.do_test(19, ["Locating e...", "Parsing sy..."])
-
-    # PExpect uses many timeouts internally and doesn't play well
-    # under ASAN on a loaded machine..
-    @skipIfAsan
-    @skipIfEditlineSupportMissing
-    def test_long_progress_message(self):
-        self.do_test(
-            80,
-            [
-                "Locating external symbol file",
-                "Parsing symbol table",
-            ],
-        )
diff --git a/lldb/test/API/functionalities/statusline/Makefile 
b/lldb/test/API/functionalities/statusline/Makefile
new file mode 100644
index 0000000000000..10495940055b6
--- /dev/null
+++ b/lldb/test/API/functionalities/statusline/Makefile
@@ -0,0 +1,3 @@
+C_SOURCES := main.c
+
+include Makefile.rules
diff --git a/lldb/test/API/functionalities/statusline/TestStatusline.py 
b/lldb/test/API/functionalities/statusline/TestStatusline.py
new file mode 100644
index 0000000000000..ab2a6826ba5d3
--- /dev/null
+++ b/lldb/test/API/functionalities/statusline/TestStatusline.py
@@ -0,0 +1,57 @@
+import lldb
+import re
+
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test.lldbpexpect import PExpectTest
+
+
+class TestStatusline(PExpectTest):
+    def do_setup(self):
+        # Create a target and run to a breakpoint.
+        exe = self.getBuildArtifact("a.out")
+        self.expect(
+            "target create {}".format(exe), substrs=["Current executable set 
to"]
+        )
+        self.expect('breakpoint set -p "Break here"', substrs=["Breakpoint 1"])
+        self.expect("run", substrs=["stop reason"])
+
+    # PExpect uses many timeouts internally and doesn't play well
+    # under ASAN on a loaded machine..
+    @skipIfAsan
+    def test(self):
+        """Basic test for the statusline."""
+        self.build()
+        self.launch()
+        self.do_setup()
+
+        # Change the terminal dimensions.
+        terminal_height = 10
+        terminal_width = 60
+        self.child.setwinsize(terminal_height, terminal_width)
+
+        # Enable the statusline and check for the control character and that we
+        # can see the target, the location and the stop reason.
+        self.expect(
+            "set set show-statusline true",
+            [
+                "\x1b[0;{}r".format(terminal_height - 1),
+                "a.out | main.c:4:15 | breakpoint 1.1                        ",
+            ],
+        )
+
+        # Change the terminal dimensions and make sure it's reflected 
immediately.
+        self.child.setwinsize(terminal_height, 20)
+        self.child.expect(re.escape("a.out | main.c:4:..."))
+        self.child.setwinsize(terminal_height, terminal_width)
+
+        # Change the format.
+        self.expect(
+            'set set statusline-format "target = {${target.file.basename}}"',
+            ["target = a.out"],
+        )
+
+        # Hide the statusline and check or the control character.
+        self.expect(
+            "set set show-statusline false", 
["\x1b[0;{}r".format(terminal_height)]
+        )
diff --git a/lldb/test/API/functionalities/statusline/main.c 
b/lldb/test/API/functionalities/statusline/main.c
new file mode 100644
index 0000000000000..762cd38be8a2a
--- /dev/null
+++ b/lldb/test/API/functionalities/statusline/main.c
@@ -0,0 +1,11 @@
+int bar(int b) { return b * b; }
+
+int foo(int f) {
+  int b = bar(f); // Break here
+  return b;
+}
+
+int main() {
+  int f = foo(42);
+  return f;
+}

>From 56784922d635ff66d8bb723b7d55a21d8db56534 Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jo...@devlieghere.com>
Date: Mon, 10 Mar 2025 11:52:14 -0700
Subject: [PATCH 2/5] Redraw the statusline when editline has taken control of
 the screen

---
 lldb/include/lldb/Core/Debugger.h    |  3 +++
 lldb/include/lldb/Core/IOHandler.h   |  2 ++
 lldb/include/lldb/Host/Editline.h    |  8 ++++++++
 lldb/source/Core/Debugger.cpp        | 15 ++++++++-------
 lldb/source/Core/IOHandler.cpp       |  6 ++++++
 lldb/source/Host/common/Editline.cpp |  3 +++
 6 files changed, 30 insertions(+), 7 deletions(-)

diff --git a/lldb/include/lldb/Core/Debugger.h 
b/lldb/include/lldb/Core/Debugger.h
index 9e2100662c6de..c79a75ab61564 100644
--- a/lldb/include/lldb/Core/Debugger.h
+++ b/lldb/include/lldb/Core/Debugger.h
@@ -417,6 +417,9 @@ class Debugger : public 
std::enable_shared_from_this<Debugger>,
   /// Decrement the "interrupt requested" counter.
   void CancelInterruptRequest();
 
+  /// Redraw the statusline if enabled.
+  void RedrawStatusline(bool update = true);
+
   /// This is the correct way to query the state of Interruption.
   /// If you are on the RunCommandInterpreter thread, it will check the
   /// command interpreter state, and if it is on another thread it will
diff --git a/lldb/include/lldb/Core/IOHandler.h 
b/lldb/include/lldb/Core/IOHandler.h
index fc0c676883b4a..794d229bc1337 100644
--- a/lldb/include/lldb/Core/IOHandler.h
+++ b/lldb/include/lldb/Core/IOHandler.h
@@ -406,6 +406,8 @@ class IOHandlerEditline : public IOHandler {
   std::optional<std::string> SuggestionCallback(llvm::StringRef line);
 
   void AutoCompleteCallback(CompletionRequest &request);
+
+  void RedrawCallback();
 #endif
 
 protected:
diff --git a/lldb/include/lldb/Host/Editline.h 
b/lldb/include/lldb/Host/Editline.h
index 8964d37be8823..705ec9c49f7c7 100644
--- a/lldb/include/lldb/Host/Editline.h
+++ b/lldb/include/lldb/Host/Editline.h
@@ -102,6 +102,8 @@ using SuggestionCallbackType =
 
 using CompleteCallbackType = llvm::unique_function<void(CompletionRequest &)>;
 
+using RedrawCallbackType = llvm::unique_function<void()>;
+
 /// Status used to decide when and how to start editing another line in
 /// multi-line sessions.
 enum class EditorStatus {
@@ -194,6 +196,11 @@ class Editline {
     m_suggestion_callback = std::move(callback);
   }
 
+  /// Register a callback for redrawing the statusline.
+  void SetRedrawCallback(RedrawCallbackType callback) {
+    m_redraw_callback = std::move(callback);
+  }
+
   /// Register a callback for the tab key
   void SetAutoCompleteCallback(CompleteCallbackType callback) {
     m_completion_callback = std::move(callback);
@@ -409,6 +416,7 @@ class Editline {
 
   CompleteCallbackType m_completion_callback;
   SuggestionCallbackType m_suggestion_callback;
+  RedrawCallbackType m_redraw_callback;
 
   bool m_color;
   std::string m_prompt_ansi_prefix;
diff --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp
index a8d8343b030f7..f3e98b3ab2d39 100644
--- a/lldb/source/Core/Debugger.cpp
+++ b/lldb/source/Core/Debugger.cpp
@@ -258,8 +258,7 @@ Status Debugger::SetPropertyValue(const ExecutionContext 
*exe_ctx,
     } else if (property_path ==
                g_debugger_properties[ePropertyStatuslineFormat].name) {
       // Statusline format changed. Redraw the statusline.
-      if (m_statusline)
-        m_statusline->Redraw();
+      RedrawStatusline();
     } else if (property_path ==
                g_debugger_properties[ePropertyUseSourceCache].name) {
       // use-source-cache changed. Wipe out the cache contents if it was
@@ -1155,6 +1154,11 @@ void Debugger::RestoreInputTerminalState() {
     m_statusline->Enable();
 }
 
+void Debugger::RedrawStatusline(bool update) {
+  if (m_statusline)
+    m_statusline->Redraw(update);
+}
+
 ExecutionContext Debugger::GetSelectedExecutionContext() {
   bool adopt_selected = true;
   ExecutionContextRef exe_ctx_ref(GetSelectedTarget().get(), adopt_selected);
@@ -2084,8 +2088,7 @@ lldb::thread_result_t Debugger::DefaultEventHandler() {
         if (m_forward_listener_sp)
           m_forward_listener_sp->AddEvent(event_sp);
       }
-      if (m_statusline)
-        m_statusline->Redraw();
+      RedrawStatusline();
     }
   }
 
@@ -2176,9 +2179,7 @@ void Debugger::HandleProgressEvent(const lldb::EventSP 
&event_sp) {
     }
   }
 
-  // Redraw the statusline if enabled.
-  if (m_statusline)
-    m_statusline->Redraw();
+  RedrawStatusline();
 }
 
 std::optional<Debugger::ProgressReport>
diff --git a/lldb/source/Core/IOHandler.cpp b/lldb/source/Core/IOHandler.cpp
index 98d14758f1987..d336cb0592d5b 100644
--- a/lldb/source/Core/IOHandler.cpp
+++ b/lldb/source/Core/IOHandler.cpp
@@ -258,6 +258,7 @@ IOHandlerEditline::IOHandlerEditline(
     m_editline_up->SetAutoCompleteCallback([this](CompletionRequest &request) {
       this->AutoCompleteCallback(request);
     });
+    m_editline_up->SetRedrawCallback([this]() { this->RedrawCallback(); });
 
     if (debugger.GetUseAutosuggestion()) {
       m_editline_up->SetSuggestionCallback([this](llvm::StringRef line) {
@@ -439,6 +440,11 @@ IOHandlerEditline::SuggestionCallback(llvm::StringRef 
line) {
 void IOHandlerEditline::AutoCompleteCallback(CompletionRequest &request) {
   m_delegate.IOHandlerComplete(*this, request);
 }
+
+void IOHandlerEditline::RedrawCallback() {
+  m_debugger.RedrawStatusline(/*update=*/false);
+}
+
 #endif
 
 const char *IOHandlerEditline::GetPrompt() {
diff --git a/lldb/source/Host/common/Editline.cpp 
b/lldb/source/Host/common/Editline.cpp
index 5f7a8b0190a1d..29abaf7c65f28 100644
--- a/lldb/source/Host/common/Editline.cpp
+++ b/lldb/source/Host/common/Editline.cpp
@@ -567,6 +567,9 @@ int Editline::GetCharacter(EditLineGetCharType *c) {
     m_needs_prompt_repaint = false;
   }
 
+  if (m_redraw_callback)
+    m_redraw_callback();
+
   if (m_multiline_enabled) {
     // Detect when the number of rows used for this input line changes due to
     // an edit

>From 0b22fb34b894c8baa185a1f44731b451d095b73e Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jo...@devlieghere.com>
Date: Mon, 10 Mar 2025 13:17:00 -0700
Subject: [PATCH 3/5] Address David's feedback

---
 .../Python/lldbsuite/test/lldbtest.py         |  5 +-
 lldb/source/Core/CoreProperties.td            |  2 +-
 lldb/source/Core/FormatEntity.cpp             |  6 +--
 lldb/source/Core/Statusline.cpp               | 50 ++++++++++---------
 .../statusline/TestStatusline.py              |  4 +-
 .../API/functionalities/statusline/main.c     |  4 +-
 6 files changed, 36 insertions(+), 35 deletions(-)

diff --git a/lldb/packages/Python/lldbsuite/test/lldbtest.py 
b/lldb/packages/Python/lldbsuite/test/lldbtest.py
index dc265d510964d..0d60025556049 100644
--- a/lldb/packages/Python/lldbsuite/test/lldbtest.py
+++ b/lldb/packages/Python/lldbsuite/test/lldbtest.py
@@ -765,14 +765,15 @@ def setUpCommands(cls):
             # Disable fix-its by default so that incorrect expressions in 
tests don't
             # pass just because Clang thinks it has a fix-it.
             "settings set target.auto-apply-fixits false",
-            # Disable the statusline in PExpect tests.
-            "settings set show-statusline false",
             # Testsuite runs in parallel and the host can have also other load.
             "settings set plugin.process.gdb-remote.packet-timeout 60",
             'settings set symbols.clang-modules-cache-path "{}"'.format(
                 configuration.lldb_module_cache_dir
             ),
+            # Disable colors by default.
             "settings set use-color false",
+            # Disable the statusline by default.
+            "settings set show-statusline false",
         ]
 
         # Set any user-overridden settings.
diff --git a/lldb/source/Core/CoreProperties.td 
b/lldb/source/Core/CoreProperties.td
index 0c6f93cb23e45..01a04f9e79095 100644
--- a/lldb/source/Core/CoreProperties.td
+++ b/lldb/source/Core/CoreProperties.td
@@ -179,7 +179,7 @@ let Definition = "debugger" in {
   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.">;
+    Desc<"The default statusline format string.">;
   def UseSourceCache: Property<"use-source-cache", "Boolean">,
     Global,
     DefaultTrue,
diff --git a/lldb/source/Core/FormatEntity.cpp 
b/lldb/source/Core/FormatEntity.cpp
index 8a32195769357..04dea7efde54d 100644
--- a/lldb/source/Core/FormatEntity.cpp
+++ b/lldb/source/Core/FormatEntity.cpp
@@ -1922,8 +1922,7 @@ bool FormatEntity::Format(const Entry &entry, Stream &s,
 
   case Entry::Type::ProgressCount:
     if (Target *target = Target::GetTargetFromContexts(exe_ctx, sc)) {
-      Debugger &debugger = target->GetDebugger();
-      if (auto progress = debugger.GetCurrentProgressReport()) {
+      if (auto progress = target->GetDebugger().GetCurrentProgressReport()) {
         if (progress->total != UINT64_MAX) {
           s.Format("[{0}/{1}]", progress->completed, progress->total);
           return true;
@@ -1934,8 +1933,7 @@ bool FormatEntity::Format(const Entry &entry, Stream &s,
 
   case Entry::Type::ProgressMessage:
     if (Target *target = Target::GetTargetFromContexts(exe_ctx, sc)) {
-      Debugger &debugger = target->GetDebugger();
-      if (auto progress = debugger.GetCurrentProgressReport()) {
+      if (auto progress = target->GetDebugger().GetCurrentProgressReport()) {
         s.PutCString(progress->message);
         return true;
       }
diff --git a/lldb/source/Core/Statusline.cpp b/lldb/source/Core/Statusline.cpp
index a6c3e778e7179..21e4780fbfc24 100644
--- a/lldb/source/Core/Statusline.cpp
+++ b/lldb/source/Core/Statusline.cpp
@@ -71,7 +71,11 @@ void Statusline::Disable() {
 }
 
 void Statusline::Draw(std::string str) {
-  static constexpr const size_t g_ellipsis = 3;
+  lldb::LockableStreamFileSP stream_sp = m_debugger.GetOutputStreamSP();
+  if (!stream_sp)
+    return;
+
+  static constexpr const size_t g_ellipsis_len = 3;
 
   UpdateTerminalProperties();
 
@@ -79,38 +83,38 @@ void Statusline::Draw(std::string str) {
 
   size_t column_width = ColumnWidth(str);
 
-  if (column_width + g_ellipsis >= m_terminal_width) {
+  if (column_width + g_ellipsis_len >= m_terminal_width) {
     // FIXME: If there are hidden characters (e.g. UTF-8, ANSI escape
     // characters), this will strip the string more than necessary. Ideally we
     // want to strip until column_width == m_terminal_width.
     str = str.substr(0, m_terminal_width);
-    str.replace(m_terminal_width - g_ellipsis, g_ellipsis, "...");
+    str.replace(m_terminal_width - g_ellipsis_len, g_ellipsis_len, "...");
     column_width = ColumnWidth(str);
   }
 
-  if (lldb::LockableStreamFileSP stream_sp = m_debugger.GetOutputStreamSP()) {
-    LockedStreamFile locked_stream = stream_sp->Lock();
-    locked_stream << ANSI_SAVE_CURSOR;
-    locked_stream.Printf(ANSI_TO_START_OF_ROW,
-                         static_cast<unsigned>(m_terminal_height));
-    locked_stream << ANSI_CLEAR_LINE;
-    locked_stream << str;
-    if (column_width < m_terminal_width)
-      locked_stream << std::string(m_terminal_width - column_width, ' ');
-    locked_stream << ANSI_NORMAL;
-    locked_stream << ANSI_RESTORE_CURSOR;
-  }
+  LockedStreamFile locked_stream = stream_sp->Lock();
+  locked_stream << ANSI_SAVE_CURSOR;
+  locked_stream.Printf(ANSI_TO_START_OF_ROW,
+                       static_cast<unsigned>(m_terminal_height));
+  locked_stream << ANSI_CLEAR_LINE;
+  locked_stream << str;
+  if (column_width < m_terminal_width)
+    locked_stream << std::string(m_terminal_width - column_width, ' ');
+  locked_stream << ANSI_NORMAL;
+  locked_stream << ANSI_RESTORE_CURSOR;
 }
 
 void Statusline::Reset() {
-  if (lldb::LockableStreamFileSP stream_sp = m_debugger.GetOutputStreamSP()) {
-    LockedStreamFile locked_stream = stream_sp->Lock();
-    locked_stream << ANSI_SAVE_CURSOR;
-    locked_stream.Printf(ANSI_TO_START_OF_ROW,
-                         static_cast<unsigned>(m_terminal_height));
-    locked_stream << ANSI_CLEAR_LINE;
-    locked_stream << ANSI_RESTORE_CURSOR;
-  }
+  lldb::LockableStreamFileSP stream_sp = m_debugger.GetOutputStreamSP();
+  if (!stream_sp)
+    return;
+
+  LockedStreamFile locked_stream = stream_sp->Lock();
+  locked_stream << ANSI_SAVE_CURSOR;
+  locked_stream.Printf(ANSI_TO_START_OF_ROW,
+                       static_cast<unsigned>(m_terminal_height));
+  locked_stream << ANSI_CLEAR_LINE;
+  locked_stream << ANSI_RESTORE_CURSOR;
 }
 
 void Statusline::UpdateTerminalProperties() {
diff --git a/lldb/test/API/functionalities/statusline/TestStatusline.py 
b/lldb/test/API/functionalities/statusline/TestStatusline.py
index ab2a6826ba5d3..5d37d53d80b87 100644
--- a/lldb/test/API/functionalities/statusline/TestStatusline.py
+++ b/lldb/test/API/functionalities/statusline/TestStatusline.py
@@ -36,13 +36,13 @@ def test(self):
             "set set show-statusline true",
             [
                 "\x1b[0;{}r".format(terminal_height - 1),
-                "a.out | main.c:4:15 | breakpoint 1.1                        ",
+                "a.out | main.c:2:11 | breakpoint 1.1                        ",
             ],
         )
 
         # Change the terminal dimensions and make sure it's reflected 
immediately.
         self.child.setwinsize(terminal_height, 20)
-        self.child.expect(re.escape("a.out | main.c:4:..."))
+        self.child.expect(re.escape("a.out | main.c:2:..."))
         self.child.setwinsize(terminal_height, terminal_width)
 
         # Change the format.
diff --git a/lldb/test/API/functionalities/statusline/main.c 
b/lldb/test/API/functionalities/statusline/main.c
index 762cd38be8a2a..7182181ba2492 100644
--- a/lldb/test/API/functionalities/statusline/main.c
+++ b/lldb/test/API/functionalities/statusline/main.c
@@ -1,7 +1,5 @@
-int bar(int b) { return b * b; }
-
 int foo(int f) {
-  int b = bar(f); // Break here
+  int b = f * f; // Break here
   return b;
 }
 

>From 74417cc0daac2a14583f68894959c8caa48e009c Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jo...@devlieghere.com>
Date: Mon, 10 Mar 2025 15:05:07 -0700
Subject: [PATCH 4/5] [lldb] Rewrite stripping and padding and add a unit test

---
 lldb/include/lldb/Core/Statusline.h           |  5 ++
 lldb/source/Core/Statusline.cpp               | 53 ++++++++++++++-----
 .../statusline/TestStatusline.py              |  4 +-
 lldb/unittests/Core/CMakeLists.txt            |  1 +
 lldb/unittests/Core/StatuslineTest.cpp        | 42 +++++++++++++++
 5 files changed, 89 insertions(+), 16 deletions(-)
 create mode 100644 lldb/unittests/Core/StatuslineTest.cpp

diff --git a/lldb/include/lldb/Core/Statusline.h 
b/lldb/include/lldb/Core/Statusline.h
index 21bd58d933b9e..1f61173c6bff8 100644
--- a/lldb/include/lldb/Core/Statusline.h
+++ b/lldb/include/lldb/Core/Statusline.h
@@ -12,6 +12,7 @@
 #include "lldb/lldb-forward.h"
 #include "llvm/ADT/StringRef.h"
 #include <csignal>
+#include <cstdint>
 #include <string>
 
 namespace lldb_private {
@@ -33,6 +34,10 @@ class Statusline {
   /// Inform the statusline that the terminal dimensions have changed.
   void TerminalSizeChanged();
 
+protected:
+  /// Pad and trim the given string to fit to the given width.
+  static std::string TrimAndPad(std::string str, size_t width);
+
 private:
   /// Draw the statusline with the given text.
   void Draw(std::string msg);
diff --git a/lldb/source/Core/Statusline.cpp b/lldb/source/Core/Statusline.cpp
index 21e4780fbfc24..f19a387958b4b 100644
--- a/lldb/source/Core/Statusline.cpp
+++ b/lldb/source/Core/Statusline.cpp
@@ -18,7 +18,10 @@
 #include "lldb/Utility/LLDBLog.h"
 #include "lldb/Utility/Log.h"
 #include "lldb/Utility/StreamString.h"
+#include "llvm/ADT/StringRef.h"
 #include "llvm/Support/Locale.h"
+#include <algorithm>
+#include <cstdint>
 
 #define ESCAPE "\x1b"
 #define ANSI_NORMAL ESCAPE "[0m"
@@ -70,27 +73,51 @@ void Statusline::Disable() {
   SetScrollWindow(m_terminal_height);
 }
 
+std::string Statusline::TrimAndPad(std::string str, size_t max_width) {
+  size_t column_width = ColumnWidth(str);
+
+  // Trim the string.
+  if (column_width > max_width) {
+    size_t min_width_idx = max_width;
+    size_t min_width = column_width;
+
+    // Use a StringRef for more efficient slicing in the loop below.
+    llvm::StringRef str_ref = str;
+
+    // Keep extending the string to find the minimum column width to make sure
+    // we include as many ANSI escape characters or Unicode code units as
+    // possible. This is far from the most efficient way to do this, but it's
+    // means our stripping code doesn't need to be ANSI and Unicode aware and
+    // should be relatively cold code path.
+    for (size_t i = column_width; i < str.length(); ++i) {
+      size_t stripped_width = ColumnWidth(str_ref.take_front(i));
+      if (stripped_width <= column_width) {
+        min_width = stripped_width;
+        min_width_idx = i;
+      }
+    }
+
+    str = str.substr(0, min_width_idx);
+    column_width = min_width;
+  }
+
+  // Pad the string.
+  if (column_width < max_width)
+    str.append(max_width - column_width, ' ');
+
+  return str;
+}
+
 void Statusline::Draw(std::string str) {
   lldb::LockableStreamFileSP stream_sp = m_debugger.GetOutputStreamSP();
   if (!stream_sp)
     return;
 
-  static constexpr const size_t g_ellipsis_len = 3;
-
   UpdateTerminalProperties();
 
   m_last_str = str;
 
-  size_t column_width = ColumnWidth(str);
-
-  if (column_width + g_ellipsis_len >= m_terminal_width) {
-    // FIXME: If there are hidden characters (e.g. UTF-8, ANSI escape
-    // characters), this will strip the string more than necessary. Ideally we
-    // want to strip until column_width == m_terminal_width.
-    str = str.substr(0, m_terminal_width);
-    str.replace(m_terminal_width - g_ellipsis_len, g_ellipsis_len, "...");
-    column_width = ColumnWidth(str);
-  }
+  str = TrimAndPad(str, m_terminal_width);
 
   LockedStreamFile locked_stream = stream_sp->Lock();
   locked_stream << ANSI_SAVE_CURSOR;
@@ -98,8 +125,6 @@ void Statusline::Draw(std::string str) {
                        static_cast<unsigned>(m_terminal_height));
   locked_stream << ANSI_CLEAR_LINE;
   locked_stream << str;
-  if (column_width < m_terminal_width)
-    locked_stream << std::string(m_terminal_width - column_width, ' ');
   locked_stream << ANSI_NORMAL;
   locked_stream << ANSI_RESTORE_CURSOR;
 }
diff --git a/lldb/test/API/functionalities/statusline/TestStatusline.py 
b/lldb/test/API/functionalities/statusline/TestStatusline.py
index 5d37d53d80b87..a58dc5470ed6d 100644
--- a/lldb/test/API/functionalities/statusline/TestStatusline.py
+++ b/lldb/test/API/functionalities/statusline/TestStatusline.py
@@ -41,8 +41,8 @@ def test(self):
         )
 
         # Change the terminal dimensions and make sure it's reflected 
immediately.
-        self.child.setwinsize(terminal_height, 20)
-        self.child.expect(re.escape("a.out | main.c:2:..."))
+        self.child.setwinsize(terminal_height, 25)
+        self.child.expect(re.escape("a.out | main.c:2:11 | bre"))
         self.child.setwinsize(terminal_height, terminal_width)
 
         # Change the format.
diff --git a/lldb/unittests/Core/CMakeLists.txt 
b/lldb/unittests/Core/CMakeLists.txt
index 60265f794b5e8..579d8304e1b32 100644
--- a/lldb/unittests/Core/CMakeLists.txt
+++ b/lldb/unittests/Core/CMakeLists.txt
@@ -11,6 +11,7 @@ add_lldb_unittest(LLDBCoreTests
   RichManglingContextTest.cpp
   SourceLocationSpecTest.cpp
   SourceManagerTest.cpp
+  StatuslineTest.cpp
   TelemetryTest.cpp
   UniqueCStringMapTest.cpp
 
diff --git a/lldb/unittests/Core/StatuslineTest.cpp 
b/lldb/unittests/Core/StatuslineTest.cpp
new file mode 100644
index 0000000000000..38f612c882f25
--- /dev/null
+++ b/lldb/unittests/Core/StatuslineTest.cpp
@@ -0,0 +1,42 @@
+//===-- StatuslineTest.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 "gtest/gtest.h"
+
+using namespace lldb_private;
+
+class TestStatusline : public Statusline {
+public:
+  using Statusline::TrimAndPad;
+};
+
+TEST(StatuslineTest, TestTrimAndPad) {
+  // Test basic ASCII.
+  EXPECT_EQ("     ", TestStatusline::TrimAndPad("", 5));
+  EXPECT_EQ("foo  ", TestStatusline::TrimAndPad("foo", 5));
+  EXPECT_EQ("fooba", TestStatusline::TrimAndPad("fooba", 5));
+  EXPECT_EQ("fooba", TestStatusline::TrimAndPad("foobar", 5));
+
+  // Simple test that ANSI escape codes don't contribute to the visible width.
+  EXPECT_EQ("\x1B[30m     ", TestStatusline::TrimAndPad("\x1B[30m", 5));
+  EXPECT_EQ("\x1B[30mfoo  ", TestStatusline::TrimAndPad("\x1B[30mfoo", 5));
+  EXPECT_EQ("\x1B[30mfooba", TestStatusline::TrimAndPad("\x1B[30mfooba", 5));
+  EXPECT_EQ("\x1B[30mfooba", TestStatusline::TrimAndPad("\x1B[30mfoobar", 5));
+
+  // Test that we include as many escape codes as we can.
+  EXPECT_EQ("fooba\x1B[30m", TestStatusline::TrimAndPad("fooba\x1B[30m", 5));
+  EXPECT_EQ("fooba\x1B[30m\x1B[34m",
+            TestStatusline::TrimAndPad("fooba\x1B[30m\x1B[34m", 5));
+  EXPECT_EQ("fooba\x1B[30m\x1B[34m",
+            TestStatusline::TrimAndPad("fooba\x1B[30m\x1B[34mr", 5));
+
+  // Test Unicode.
+  EXPECT_EQ("❤️    ", TestStatusline::TrimAndPad("❤️", 5));
+  EXPECT_EQ("    ❤️", TestStatusline::TrimAndPad("    ❤️", 5));
+}

>From 9e3648018eb4e9e86848fe725846c1be1834abbe Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jo...@devlieghere.com>
Date: Mon, 10 Mar 2025 16:53:20 -0700
Subject: [PATCH 5/5] Fix spurious newline bug

---
 lldb/include/lldb/Core/Statusline.h | 10 +++++---
 lldb/source/Core/Statusline.cpp     | 37 +++++++++++++++++++----------
 2 files changed, 31 insertions(+), 16 deletions(-)

diff --git a/lldb/include/lldb/Core/Statusline.h 
b/lldb/include/lldb/Core/Statusline.h
index 1f61173c6bff8..c1449f0f69081 100644
--- a/lldb/include/lldb/Core/Statusline.h
+++ b/lldb/include/lldb/Core/Statusline.h
@@ -45,8 +45,13 @@ class Statusline {
   /// Update terminal dimensions.
   void UpdateTerminalProperties();
 
-  /// Set the scroll window to the given height.
-  void SetScrollWindow(uint64_t height);
+  enum ScrollWindowMode {
+    ScrollWindowExtend,
+    ScrollWindowShrink,
+  };
+
+  /// Set the scroll window for the given mode.
+  void UpdateScrollWindow(ScrollWindowMode mode);
 
   /// Clear the statusline (without redrawing the background).
   void Reset();
@@ -57,7 +62,6 @@ class Statusline {
   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;
 };
 } // namespace lldb_private
 #endif // LLDB_CORE_STATUSLINE_H
diff --git a/lldb/source/Core/Statusline.cpp b/lldb/source/Core/Statusline.cpp
index f19a387958b4b..f535c02b1dfe7 100644
--- a/lldb/source/Core/Statusline.cpp
+++ b/lldb/source/Core/Statusline.cpp
@@ -60,7 +60,7 @@ void Statusline::Enable() {
   UpdateTerminalProperties();
 
   // Reduce the scroll window to make space for the status bar below.
-  SetScrollWindow(m_terminal_height - 1);
+  UpdateScrollWindow(ScrollWindowShrink);
 
   // Draw the statusline.
   Redraw();
@@ -70,7 +70,7 @@ void Statusline::Disable() {
   UpdateTerminalProperties();
 
   // Extend the scroll window to cover the status bar.
-  SetScrollWindow(m_terminal_height);
+  UpdateScrollWindow(ScrollWindowExtend);
 }
 
 std::string Statusline::TrimAndPad(std::string str, size_t max_width) {
@@ -153,24 +153,35 @@ void Statusline::UpdateTerminalProperties() {
   m_terminal_height = m_debugger.GetTerminalHeight();
 
   // Set the scroll window based on the new terminal height.
-  SetScrollWindow(m_terminal_height - 1);
+  UpdateScrollWindow(ScrollWindowShrink);
 
   // Clear the flag.
   m_terminal_size_has_changed = 0;
 }
 
-void Statusline::SetScrollWindow(uint64_t height) {
-  if (lldb::LockableStreamFileSP stream_sp = m_debugger.GetOutputStreamSP()) {
-    LockedStreamFile locked_stream = stream_sp->Lock();
-    locked_stream << '\n';
-    locked_stream << ANSI_SAVE_CURSOR;
-    locked_stream.Printf(ANSI_SET_SCROLL_ROWS, static_cast<unsigned>(height));
-    locked_stream << ANSI_RESTORE_CURSOR;
-    locked_stream.Printf(ANSI_UP_ROWS, 1);
+void Statusline::UpdateScrollWindow(ScrollWindowMode mode) {
+  lldb::LockableStreamFileSP stream_sp = m_debugger.GetOutputStreamSP();
+  if (!stream_sp)
+    return;
+
+  const unsigned scroll_height =
+      (mode == ScrollWindowExtend) ? m_terminal_height : m_terminal_height - 1;
+
+  LockedStreamFile locked_stream = stream_sp->Lock();
+  locked_stream << ANSI_SAVE_CURSOR;
+  locked_stream.Printf(ANSI_SET_SCROLL_ROWS, scroll_height);
+  locked_stream << ANSI_RESTORE_CURSOR;
+  switch (mode) {
+  case ScrollWindowExtend:
+    // Clear the screen below to hide the old statusline.
     locked_stream << ANSI_CLEAR_BELOW;
+    break;
+  case ScrollWindowShrink:
+    // Move everything on the screen up.
+    locked_stream.Printf(ANSI_UP_ROWS, 1);
+    locked_stream << '\n';
+    break;
   }
-
-  m_scroll_height = height;
 }
 
 void Statusline::Redraw(bool update) {

_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to