Skip to content

[lldb] Support OSC escape codes for native progress#162162

Merged
JDevlieghere merged 3 commits intollvm:mainfrom
JDevlieghere:issue160369
Oct 13, 2025
Merged

[lldb] Support OSC escape codes for native progress#162162
JDevlieghere merged 3 commits intollvm:mainfrom
JDevlieghere:issue160369

Conversation

@JDevlieghere
Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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.

Comment thread lldb/include/lldb/Utility/AnsiTerminal.h
Comment thread lldb/source/Core/Debugger.cpp Outdated
@JDevlieghere JDevlieghere merged commit 578c03f into llvm:main Oct 13, 2025
10 checks passed
@JDevlieghere JDevlieghere deleted the issue160369 branch October 13, 2025 20:48
akadutta pushed a commit to akadutta/llvm-project that referenced this pull request Oct 14, 2025
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 llvm#160369
JDevlieghere added a commit to JDevlieghere/llvm-project that referenced this pull request Mar 10, 2026
In llvm#162162, I added support for OSC 9;4 graphical progress. I put it
behind the `show-progress` setting because I didn't have a reliable way
to detect whether the escape code was supported by the terminal.

Since then, more tools have added support for it, most notably Claude
Code and Homebrew. While I still don't have a good way to detect this,
there are a handful of known terminals that are easy enough to identify.

This PR toggles the default of `show-progress` to on again and puts
showing the progress behind a check for those known terminals (Windows
Terminal, ConEmu & Ghostty).

This means that if you're running in one of those, you'll get the visual
progress by default unless you set `show-progress` to off. The downside
is that if you're on an unrecognized terminal, you can't force it on any
longer by setting `show-progress` to on. I think that's a fair trade-off
as the setting wasn't really advertised and I doubt many people are
using that. As a workaround, they can set `OSC_PROGRESS` to spoof an
OSC-supporting terminal.
JDevlieghere added a commit that referenced this pull request Mar 12, 2026
…5541)

In #162162, I added support for OSC 9;4 graphical progress. I put it
behind the `show-progress` setting because I didn't have a reliable way
to detect whether the escape code was supported by the terminal.

Since then, more tools have added support for it, most notably Claude
Code and Homebrew. While I still don't have a good way to detect this,
there are a handful of known terminals that are easy enough to identify.

This PR toggles the default of `show-progress` to on again and puts
showing the progress behind a check for those known terminals (Windows
Terminal, ConEmu & Ghostty).

This means that if you're running in one of those, you'll get the visual
progress by default unless you set `show-progress` to off. The downside
is that if you're on an unrecognized terminal, you can't force it on any
longer by setting `show-progress` to on. I think that's a fair trade-off
as the setting wasn't really advertised and I doubt many people are
using that. As a workaround, they can set `OSC_PROGRESS` to spoof an
OSC-supporting terminal.
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