Skip to content

Conversation

JDevlieghere
Copy link
Member

This PR adds support for emitting the OSC 9;4 sequences to show a GUI native progress bar.

There's a limited number of terminal emulators that support this, so for now this requires explicit opt-in through a setting. I'm reusing the existing show-progress setting, which became a NOOP with the introduction of the statusline. The option now defaults to off.

Implements #160369

This PR adds support for emitting the ConEmu OSC 9;4 sequences to show a
GUI native progress bar.

There's a limited number of terminal emulators that support this, so for
now this requires explicit opt-in through a setting. I'm reusing the
existing `show-progress` setting, which became a NOOP with the
introduction of the statusline. The option now defaults to off.

Implements llvm#160369
@llvmbot
Copy link
Member

llvmbot commented Oct 6, 2025

@llvm/pr-subscribers-lldb

Author: Jonas Devlieghere (JDevlieghere)

Changes

This PR adds support for emitting the OSC 9;4 sequences to show a GUI native progress bar.

There's a limited number of terminal emulators that support this, so for now this requires explicit opt-in through a setting. I'm reusing the existing show-progress setting, which became a NOOP with the introduction of the statusline. The option now defaults to off.

Implements #160369


Full diff: https://github.com/llvm/llvm-project/pull/162162.diff

4 Files Affected:

  • (modified) lldb/include/lldb/Core/Debugger.h (+1)
  • (modified) lldb/include/lldb/Utility/AnsiTerminal.h (+9)
  • (modified) lldb/source/Core/CoreProperties.td (+6-4)
  • (modified) lldb/source/Core/Debugger.cpp (+39-10)
diff --git a/lldb/include/lldb/Core/Debugger.h b/lldb/include/lldb/Core/Debugger.h
index 06136ed40471d..78f1fa6757f9d 100644
--- a/lldb/include/lldb/Core/Debugger.h
+++ b/lldb/include/lldb/Core/Debugger.h
@@ -682,6 +682,7 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
   lldb::LockableStreamFileSP GetErrorStreamSP() { return m_error_stream_sp; }
   /// @}
 
+  bool IsInteractiveColorTTY();
   bool StatuslineSupported();
 
   void PushIOHandler(const lldb::IOHandlerSP &reader_sp,
diff --git a/lldb/include/lldb/Utility/AnsiTerminal.h b/lldb/include/lldb/Utility/AnsiTerminal.h
index 7db184ad67225..350c1fb145300 100644
--- a/lldb/include/lldb/Utility/AnsiTerminal.h
+++ b/lldb/include/lldb/Utility/AnsiTerminal.h
@@ -72,6 +72,15 @@
 
 #define ANSI_ESC_START_LEN 2
 
+// OSC (Operating System Commands)
+#define OSC_ESCAPE_START "\033"
+#define OSC_ESCAPE_END "\x07"
+
+#define OSC_PROGRESS_REMOVE OSC_ESCAPE_START "]9;4;0;0" OSC_ESCAPE_END
+#define OSC_PROGRESS_SHOW OSC_ESCAPE_START "]9;4;1;%u" OSC_ESCAPE_END
+#define OSC_PROGRESS_ERROR OSC_ESCAPE_START "]9;4;2;%u" OSC_ESCAPE_END
+#define OSC_PROGRESS_INDETERMINATE OSC_ESCAPE_START "]9;4;3;%u" OSC_ESCAPE_END
+
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/StringRef.h"
diff --git a/lldb/source/Core/CoreProperties.td b/lldb/source/Core/CoreProperties.td
index fda34a8ad2630..1be911c291703 100644
--- a/lldb/source/Core/CoreProperties.td
+++ b/lldb/source/Core/CoreProperties.td
@@ -162,10 +162,12 @@ let Definition = "debugger" in {
     Global,
     DefaultTrue,
     Desc<"Whether to use Ansi color codes or not.">;
-  def ShowProgress: Property<"show-progress", "Boolean">,
-    Global,
-    DefaultTrue,
-    Desc<"Whether to show progress or not if the debugger's output is an interactive color-enabled terminal.">;
+  def ShowProgress
+      : Property<"show-progress", "Boolean">,
+        Global,
+        DefaultFalse,
+        Desc<"Whether to show progress using Operating System Command (OSC) "
+             "Sequences in supporting terminal emulators.">;
   def ShowProgressAnsiPrefix: Property<"show-progress-ansi-prefix", "String">,
     Global,
     DefaultStringValue<"${ansi.faint}">,
diff --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp
index 568cd9d3d03b6..13fd3a705128e 100644
--- a/lldb/source/Core/Debugger.cpp
+++ b/lldb/source/Core/Debugger.cpp
@@ -2066,19 +2066,23 @@ void Debugger::CancelForwardEvents(const ListenerSP &listener_sp) {
   m_forward_listener_sp.reset();
 }
 
+bool Debugger::IsInteractiveColorTTY() {
+  if (lldb::LockableStreamFileSP stream_sp = GetOutputStreamSP()) {
+    File &file = stream_sp->GetUnlockedFile();
+    return file.GetIsInteractive() && file.GetIsRealTerminal() &&
+           file.GetIsTerminalWithColors();
+  }
+  return false;
+}
+
 bool Debugger::StatuslineSupported() {
 // We have trouble with the contol codes on Windows, see
 // https://github.com/llvm/llvm-project/issues/134846.
 #ifndef _WIN32
-  if (GetShowStatusline()) {
-    if (lldb::LockableStreamFileSP stream_sp = GetOutputStreamSP()) {
-      File &file = stream_sp->GetUnlockedFile();
-      return file.GetIsInteractive() && file.GetIsRealTerminal() &&
-             file.GetIsTerminalWithColors();
-    }
-  }
-#endif
+  return GetShowStatusline() && IsInteractiveColorTTY();
+#else
   return false;
+#endif
 }
 
 static bool RequiresFollowChildWorkaround(const Process &process) {
@@ -2271,10 +2275,11 @@ void Debugger::HandleProgressEvent(const lldb::EventSP &event_sp) {
   ProgressReport progress_report{data->GetID(), data->GetCompleted(),
                                  data->GetTotal(), data->GetMessage()};
 
-  // Do some bookkeeping regardless of whether we're going to display
-  // progress reports.
   {
     std::lock_guard<std::mutex> guard(m_progress_reports_mutex);
+
+    // Do some bookkeeping regardless of whether we're going to display
+    // progress reports.
     auto it = llvm::find_if(m_progress_reports, [&](const auto &report) {
       return report.id == progress_report.id;
     });
@@ -2287,6 +2292,30 @@ void Debugger::HandleProgressEvent(const lldb::EventSP &event_sp) {
     } else {
       m_progress_reports.push_back(progress_report);
     }
+
+    // Show progress using Operating System Command (OSC) sequences.
+    if (GetShowProgress() && IsInteractiveColorTTY()) {
+      if (lldb::LockableStreamFileSP stream_sp = GetOutputStreamSP()) {
+
+        // Clear progress if this was the last progress event.
+        if (m_progress_reports.empty()) {
+          stream_sp->Lock() << OSC_PROGRESS_REMOVE;
+          return;
+        }
+
+        const ProgressReport &report = m_progress_reports.back();
+
+        // Show indeterminate progress.
+        if (report.total == UINT64_MAX) {
+          stream_sp->Lock() << OSC_PROGRESS_INDETERMINATE;
+          return;
+        }
+
+        // Compute and show the progress value (0-100).
+        const unsigned value = (report.completed / report.total) * 100;
+        stream_sp->Lock().Printf(OSC_PROGRESS_SHOW, value);
+      }
+    }
   }
 }
 

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.

Needs a release note.

DefaultFalse,
Desc<"Whether to show progress using Operating System Command (OSC) "
"Sequences in supporting terminal emulators.">;
def ShowProgressAnsiPrefix: Property<"show-progress-ansi-prefix", "String">,
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should/should have release noted that this and the next setting now do nothing.

Copy link
Member Author

Choose a reason for hiding this comment

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

You're right. I've retroactively included in the new release note.

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.

4 participants