Skip to content

[lldb] Implement a statusline in LLDB #121860

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Mar 26, 2025
Merged

Conversation

JDevlieghere
Copy link
Member

@JDevlieghere JDevlieghere commented Jan 7, 2025

[lldb] Implement a statusline in LLDB

Add a statusline to command-line LLDB to display information about the
current state of the debugger. The statusline is a dedicated area
displayed at the bottom of the screen. The information displayed is
configurable through a setting consisting of LLDB’s format strings.

Enablement

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

(lldb) settings set show-statusline false

Configuration

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

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

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

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.

Format Strings

LLDB's format strings are documented in the LLDB documentation and on
the website: https://lldb.llvm.org/use/formatting.html#format-strings.
The current implementation is relatively limited but various
improvements have been discussed in the RFC.

One such improvement is being to display a string when a format string
is empty. Right now, when launching LLDB without a target, the
statusline will be empty, which is expected, but looks rather odd.

RFC

The full RFC can be found on Discourse:
https://discourse.llvm.org/t/rfc-lldb-statusline/83948

@JDevlieghere JDevlieghere force-pushed the statusline branch 4 times, most recently from c485d7d to d2fdaec Compare January 17, 2025 23:10
@JDevlieghere JDevlieghere changed the title LLDB Statusline [lldb] Implement a statusline in LLDB Jan 18, 2025
@JDevlieghere JDevlieghere marked this pull request as ready for review January 18, 2025 01:29
@llvmbot llvmbot added the lldb label Jan 18, 2025
@llvmbot
Copy link
Member

llvmbot commented Jan 18, 2025

@llvm/pr-subscribers-lldb

Author: Jonas Devlieghere (JDevlieghere)

Changes

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

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

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

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

(lldb) settings set show-statusline false

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

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

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


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

14 Files Affected:

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

@JDevlieghere
Copy link
Member Author

Moving this out of Draft as I think I've addressed all the RFC feedback and I'd like to get some input on the code. I've broken out the changes that can stand on their own into separate PRs.

The only outstanding blocker that I'm aware of is the use of an ansi escape code in Editline that that clears everything on the screen (including the statusline) but that can (and should) be fixed in a separate PR anyway.

@JDevlieghere JDevlieghere force-pushed the statusline branch 3 times, most recently from ecd2f91 to b0cf540 Compare January 22, 2025 18:59
Copy link
Collaborator

@labath labath left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this looks pretty good. The signal-safety of the code is very dubious (it was dubious even before that, but now it's even more), but I don't think that there's anything we can do about it from here. I think the only way we can have a real signal-safety guarantee is if we have a signal-catcher thread in the lldb driver, which catches the signals very safely (for example, using our MainLoop class) and then notifies lldb outside of the signal context.

Definitely needs some tests though (I thought you had some -- what happened to those?)

@JDevlieghere
Copy link
Member Author

Definitely needs some tests though (I thought you had some -- what happened to those?)

Yup, I'm planning on adding dedicated PExpect tests. The ones you remember are probably the ones from dependencies that have landed in separate PRs.

@JDevlieghere
Copy link
Member Author

Rebased & fixed the existing tests.

@bulbazord
Copy link
Member

Rebased & fixed the existing tests.

Looks like lldb/test/API/functionalities/progress_reporting/TestTrimmedProgressReporting.py was deleted entirely. Was this intentional?

Copy link

github-actions bot commented Feb 7, 2025

✅ With the latest revision this PR passed the Python code formatter.

@JDevlieghere
Copy link
Member Author

JDevlieghere commented Feb 10, 2025

Looks like lldb/test/API/functionalities/progress_reporting/TestTrimmedProgressReporting.py was deleted entirely. Was this intentional?

Yes, because this replaces the old in-line progress printing. The statusline still trims messages that don't fit and progress isn't special in that regard. I can add a test to the statusline for that, but because PExpect is line-oriented it's going to be pretty heavy-weight (i.e. duplicate the existing test in a smaller window).

edit: it was easier than expected and caught a bug!


# Enable the statusline and check that we can see the target, the
# location and the stop reason.
self.child.send("set set show-statusline true\n")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use self.expect for this? I'm not sure what's the order of things being printed. If the status line is printed before the prompt, then you can put it into the substrs argument of the expect call. Otherwise, you can keep this expect line after it.

Comment on lines 44 to 45
self.child.send("set set show-statusline false\n")
self.child.send("set set show-statusline true\n")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And the same here.

we can test.
"""
self.build()
self.launch(timeout=3)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove short timeout

def test(self):
"""Basic test for the statusline.

PExpect was designed for line-oriented output so we're limited in what
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should still be able to check a fair number of things. This isn't like the curses GUI, where there's the curses library sitting between our code and the terminal. All of the escape codes we print are fully within our control.

For example, it should be possible to check that the right escape sequence for enabling/disabling the status line is printed in response to changing the setting value.

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
@JDevlieghere
Copy link
Member Author

@DavidSpickett I ended up rewriting the strip-and-pad logic as there were too many unhandled edge cases for my liking. The new algorithm isn't particularly efficient, but given that most of the time things should fit, and it scales with how much text doesn't fix on the screen, I'm hoping that that's a fair trade-off. Also in the end it's "just" string manipulation. I've added a unit test to show the expected behavior. I did drop the ellipsis as they complicate things more than they're worth.

JDevlieghere added a commit to JDevlieghere/llvm-project that referenced this pull request Mar 12, 2025
This PR implements a unicode and ANSI escape code aware function to trim
and pad strings. This is a break-out from llvm#121860.
JDevlieghere added a commit to JDevlieghere/llvm-project that referenced this pull request Mar 12, 2025
This PR implements a unicode and ANSI escape code aware function to trim
and pad strings. This is a break-out from llvm#121860.
JDevlieghere added a commit that referenced this pull request Mar 12, 2025
…0878)

This PR implements a unicode and ANSI escape code aware function to trim
and pad strings. This is a break-out from #121860.
@JDevlieghere
Copy link
Member Author

@DavidSpickett does this LGTY?

frederik-h pushed a commit to frederik-h/llvm-project that referenced this pull request Mar 18, 2025
…m#130878)

This PR implements a unicode and ANSI escape code aware function to trim
and pad strings. This is a break-out from llvm#121860.
Copy link
Collaborator

@DavidSpickett DavidSpickett left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Could you append to the PR description a list of known issues e.g. the redraw on tab complete?

It's discussed here in comments already but it's good to have a record of what state we are at and what you have promised to address (not that I doubt your sincerity there).

And maybe some brave soul daily driving a main build will know what to expect if they see it in the commit message.

@JDevlieghere
Copy link
Member Author

Could you append to the PR description a list of known issues e.g. the redraw on tab complete?

It's discussed here in comments already but it's good to have a record of what state we are at and what you have promised to address (not that I doubt your sincerity there).

And maybe some brave soul daily driving a main build will know what to expect if they see it in the commit message.

Things have changed since I wrote the description so I'll update it to reflect that. As far as I know there are no known issues (that I've encountered). The tab complete issue is (should be?) fixed with the current state of this PR. Are you still experiencing that behavior?

@DavidSpickett
Copy link
Collaborator

The tab complete issue is (should be?) fixed with the current state of this PR.

Cool! I did not check it again locally. If you're reasonably sure you've fixed it then I trust you there :)

Go ahead and land this and I'll try my best to break it again at some point.

@JDevlieghere JDevlieghere merged commit 9c18edc into llvm:main Mar 26, 2025
10 checks passed
@JDevlieghere JDevlieghere deleted the statusline branch March 26, 2025 21:48
@DavidSpickett
Copy link
Collaborator

@JDevlieghere if I start lldb with no arguments:

david.spickett@tcwg-jade-03-dev:~/build-llvm-aarch64$ ./bin/lldb
<< cursor is here at the start of the line, but no (lldb) was printed
< the status bar>

Does not happen if I have a program file, but this is also strange:

$ ./bin/lldb /tmp/test.o
(lldb) target create "/tmp/test.o"
Current executable set to '/tmp/test.o' (aarch64).
(lldb) << the cursor is actually at the *start* of this line, instead of after the (lldb)
test.o << status bar

Typing overwrites the (lldb):

$ ./bin/lldb /tmp/test.o
(lldb) target create "/tmp/test.o"
Current executable set to '/tmp/test.o' (aarch64).
ddddb)
test.o << status bar

AArch64 Ubuntu Linux, in case it matters.

JDevlieghere added a commit to JDevlieghere/llvm-project that referenced this pull request Mar 27, 2025
Add a release note for the statusline: llvm#121860
JDevlieghere added a commit that referenced this pull request Mar 28, 2025
Add a release note for the LLDB statusline: #121860
JDevlieghere added a commit to swiftlang/llvm-project that referenced this pull request Mar 29, 2025
…m#130878)

This PR implements a unicode and ANSI escape code aware function to trim
and pad strings. This is a break-out from llvm#121860.

(cherry picked from commit 78c9fa3)
JDevlieghere added a commit to swiftlang/llvm-project that referenced this pull request Mar 29, 2025
Add a statusline to command-line LLDB to display information about the
current state of the debugger. The statusline is a dedicated area
displayed at the bottom of the screen. The information displayed is
configurable through a setting consisting of LLDB’s format strings.

Enablement
----------

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

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

Configuration
-------------

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

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

The statusline supersedes the current progress reporting implementation.
Consequently, the following settings no longer have any effect (but
continue to exist to not break anyone's `.lldbinit`):

```
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.
```

Format Strings
--------------

LLDB's format strings are documented in the LLDB documentation and on
the website: https://lldb.llvm.org/use/formatting.html#format-strings.
The current implementation is relatively limited but various
improvements have been discussed in the RFC.

One such improvement is being to display a string when a format string
is empty. Right now, when launching LLDB without a target, the
statusline will be empty, which is expected, but looks rather odd.

RFC
---

The full RFC can be found on Discourse:
https://discourse.llvm.org/t/rfc-lldb-statusline/83948

(cherry picked from commit 9c18edc)
@DavidSpickett
Copy link
Collaborator

if I start lldb with no arguments:

#134064

Does not happen if I have a program file, but this is also strange:

This part appears to have been fixed.

@labath
Copy link
Collaborator

labath commented Apr 25, 2025

I'm not sure what the deal, but I just noticed I don't see the "Manually Indexing DWARF" messages even though I'm certain that's what lldb is doing. It's possible they are being shadowed by another progress event or something...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants