Skip to content

Handle OSC 133 shell integration and the OSC 9 ConEmu extensions - #20

Merged
tomlm merged 4 commits into
tomlm:mainfrom
JohnCampionJr:osc-9-and-133
Aug 24, 2026
Merged

Handle OSC 133 shell integration and the OSC 9 ConEmu extensions#20
tomlm merged 4 commits into
tomlm:mainfrom
JohnCampionJr:osc-9-and-133

Conversation

@JohnCampionJr

Copy link
Copy Markdown
Collaborator

Both codes currently reach the unknown-sequence branch and are discarded.

OSC 133 — shell integration (FinalTerm/FTCS)

How a shell reports where its prompt ends, where a command starts, and how it exited. Terminal gains ShellIntegrationState, LastCommandExitCode, and a ShellIntegrationMarkReceived event.

OSC 133 ; A            start of prompt
OSC 133 ; B            start of command line (end of prompt)
OSC 133 ; C            start of command output
OSC 133 ; D [; exit]   end of command

ShellIntegrationState is nullable and starts null. Integration has to be configured in the shell, so a shell without it is indistinguishable from one sitting at a prompt — defaulting to PromptStart would assert the shell is idle on no evidence at all. Null means "cannot say".

The exit code is nullable for the same reason. cmd.exe cannot read the previous command's status from its prompt, so it always emits a bare D; that is normal, not malformed. Defaulting to 0 would make every cmd.exe command report success. A later bare D also clears a previous code rather than leaving it stale.

OSC 9 — ConEmu extensions

Three unrelated features multiplexed on the first parameter, not on the code, so dispatch happens there:

sequence meaning
9 ; 9 ; path working directory
9 ; 4 ; state ; pct progress (Windows Terminal renders this on the taskbar)
9 ; text desktop notification

9;9 matters more than it looks: Microsoft's own documented Windows prompts emit it rather than OSC 7, so a terminal reading only 7 loses the working directory on Windows entirely. The path arrives bare rather than as a file:// URI, and pwsh sends it quoted.

Falling through to the notification case is only correct once the sub-parameters are claimed first — otherwise every progress tick raises a toast reading "4;1;50". There's a test for exactly that.

Progress values are clamped rather than rejected (a sender that overshoots still means "as far as it goes"), and zeroed for None and Indeterminate, which carry no percentage and would otherwise leave a bar at a stale position while claiming the extent is unknown.

Testing

29 new tests in ShellIntegrationTests.cs: every mark, a full prompt→command→exit cycle, present/absent/negative exit codes, stale-code clearing, ST as well as BEL termination, unknown marks and empty payloads, quoted and empty paths, every progress state, clamping, and the notification/sub-parameter split.

Full suite: 636 passed, 0 failed.

Context

One of two independent PRs; neither is stacked on the other, and either can merge first. The companion (#19) adds a general Terminal.OscReceived passthrough event.

They do both touch the HandleOsc switch, so whichever merges second will want a trivial rebase — the changes are adjacent rather than overlapping (this one adds case arms; #19 adds a flag and a trailing raise).

🤖 Generated with Claude Code

Both codes reached the unknown-sequence branch and were discarded.

OSC 133 (FinalTerm/FTCS) is how a shell says where its prompt ends, where a
command starts, and how that command exited. Terminal exposes the last mark and
the last exit code, plus an event per mark.

ShellIntegrationState is NULLABLE and starts null. Integration has to be
configured in the shell, so a shell without it is indistinguishable from one at a
prompt; defaulting to PromptStart would assert the shell is idle on no evidence.
Null means "cannot say".

The exit code is nullable for the same reason. OSC 133;D carries one only if the
shell can supply it, and cmd.exe cannot read the previous command's status from
its prompt, so a bare D is normal rather than malformed. Defaulting to 0 would
make every cmd.exe command report success. A later bare D also CLEARS a previous
code rather than leaving it stale.

OSC 9 is three unrelated features multiplexed on the first parameter, not on the
code, so dispatch happens there:

  9 ; 9 ; path       working directory, the ConEmu convention. Microsoft's own
                     documented Windows prompts emit this rather than OSC 7, so
                     reading only 7 loses the cwd on Windows entirely. The path
                     is bare rather than a file:// URI, and pwsh sends it quoted.
  9 ; 4 ; state ; %  progress, which Windows Terminal renders on the taskbar.
  9 ; text           desktop notification.

Falling through to the notification case is only correct once the sub-parameters
are claimed first: otherwise every progress tick raises a toast reading "4;1;50".
There is a test for exactly that.

Progress values are clamped rather than rejected -- a sender that overshoots still
means "as far as it goes" -- and zeroed for None and Indeterminate, which carry no
percentage and would otherwise leave a bar sitting at a stale position while
claiming the extent is unknown.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

The implementation matches the described behavior and includes thorough test coverage, with only a minor misleading comment wording to adjust.

Pull request overview

Adds first-class handling for OSC 133 (FinalTerm/FTCS shell integration marks) and OSC 9 (ConEmu extensions) so these sequences update terminal state and raise dedicated events instead of falling through to the unknown-sequence branch.

Changes:

  • Add OSC 133 handling to track shell integration marks and last command exit code, plus a ShellIntegrationMarkReceived event.
  • Add OSC 9 ConEmu handling for working directory (9;9), progress (9;4), and desktop notifications, with progress/notification events and state stored on Terminal.
  • Add new enums (ShellIntegrationMark, ProgressState) and comprehensive tests covering the new behavior.
File summaries
File Description
src/XTerm.NET/Terminal.cs Adds terminal state and events for OSC 133 shell integration and OSC 9 progress/notification.
src/XTerm.NET/InputHandler.cs Dispatches and parses OSC 9 (ConEmu) and OSC 133 sequences and updates Terminal accordingly.
src/XTerm.NET/Events/TerminalEvents.cs Introduces event-args types for shell integration, progress, and notifications.
src/XTerm.NET/Common/ShellIntegrationMark.cs Defines the OSC 133 mark enum.
src/XTerm.NET/Common/ProgressState.cs Defines the OSC 9;4 progress state enum.
src/XTerm.NET/Common/OscCommand.cs Adds enum entries/docs for OSC 9 (ConEmu) and OSC 133 (shell integration).
src/XTerm.NET.Tests/ShellIntegrationTests.cs Adds tests covering OSC 133 marks/exit codes and OSC 9 cwd/progress/notification behavior.
Review details
  • Files reviewed: 7/7 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/XTerm.NET/InputHandler.cs Outdated
Comment on lines +703 to +704
// The sub-parameter decides everything, so an unrecognised one must not be treated as a
// notification -- OSC 9;4;... would then pop a toast reading "4;1;50" on every progress tick.
@tomlm

tomlm commented Aug 24, 2026

Copy link
Copy Markdown
Owner

ah shit, this looks like it's stacked again.

@JohnCampionJr

Copy link
Copy Markdown
Collaborator Author

ah shit, this looks like it's stacked again.

Dang it - it's not supposed to be, there are just conflicting commits. I'll fix it up.

@tomlm

tomlm commented Aug 24, 2026

Copy link
Copy Markdown
Owner

oh shit, I broke it in my merge. Argh. I'm fixing.

@JohnCampionJr

Copy link
Copy Markdown
Collaborator Author

give me a min :) Also, I have async for port.pty coming too LOL - I'm on a tear.

BUILD WAS BROKEN. The merge of main into this branch dropped the closing brace of
NotificationEventArgs and the opening line of the next doc comment, so every class
after it nested inside it and the file would not compile. tomlm#19 and tomlm#20 both inserted
at the same anchor in TerminalEvents.cs, which is what the merge had to reconcile.

COMMENT DESCRIBED BEHAVIOUR THE CODE DOES NOT HAVE. It said an unrecognised
sub-parameter "must not be treated as a notification", and the code does exactly
that on purpose -- the notification form is OSC 9 ; text, with no sub-parameter at
all, so it can only be the fallback. The real requirement is about ORDER: claimed
sub-commands have to be matched first, or OSC 9;4;1;50 pops a toast reading
"4;1;50" on every progress tick. Reworded to say that, and to say that an unclaimed
sub-parameter being a notification is the intended reading of a permissive
extension space.

A CLAIMED SUB-COMMAND WITH NO PAYLOAD is now ignored rather than shown. "OSC 9;9"
carries a sub-command and nothing else; the fallback turned it into a notification
whose entire body was "9".

A TEST FROM tomlm#19 WENT STALE, CORRECTLY. It asserted OSC 133 was unrecognised, which
was true when the passthrough event was added and is not any more -- this branch is
what implements it. That is the Recognized contract working rather than a test
rotting: a listener filling the gap stops doing so once a code lands in HandleOsc.
Moved to OSC 1337, which really is unimplemented, and added the opposite assertion
for 133 so both halves are pinned.

698 tests pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@tomlm
tomlm merged commit da1f0db into tomlm:main Aug 24, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants