Skip to content

fix: handle partial reads in queryTerminal#616

Closed
MartinodF wants to merge 2 commits into
charmbracelet:v2-expfrom
MartinodF:patch-3
Closed

fix: handle partial reads in queryTerminal#616
MartinodF wants to merge 2 commits into
charmbracelet:v2-expfrom
MartinodF:patch-3

Conversation

@MartinodF

Copy link
Copy Markdown
Contributor

What is broken?

This all started with lipgloss.HasDarkBackground timing out most (80%?) of the time when running in Windows Terminal.

After ignoring the problem for a few weeks, I finally decided to figure out what was happening: queryBackgroundColor sends an OSC 11 and a CSI DA1 request one after the other and is supposed to receive back (at least) one response of each type.

The code (with a couple of debug statements) running in the VSCode integrated terminal:

Read 25 bytes: "\x1b]11;rgb:1f1f/2424/3030\x1b\\"
Received sequence: "\x1b]11;rgb:1f1f/2424/3030\x1b\\", state: 0, command: 11, data: "11;rgb:1f1f/2424/3030"
Read 7 bytes: "\x1b[?1;2c"
Received sequence: "\x1b[?1;2c", state: 0, command: 16227, data: ""

The same code in Windows Terminal:

Read 16 bytes: "\x1b]11;rgb:1a1a/1b"
Received sequence: "\x1b]11;rgb:1a1a/1b", state: 5, command: 11, data: "11;rgb:1a1a/1b"
Read 7 bytes: "1b/2626"
Received sequence: "1b/2626", state: 5, command: 11, data: "11;rgb:1a1a/1b1b/2626"
Read 2 bytes: "\x1b\\"
Received sequence: "\x1b\\", state: 0, command: 11, data: "11;rgb:1a1a/1b1b/2626"
Read 16 bytes: "\x1b[?61;4;6;7;14;2"
Received sequence: "\x1b[?61;4;6;7;14;2", state: 2, command: 16128, data: ""
Read 16 bytes: "1;22;23;24;28;32"
Received sequence: "1;22;23;24;28;32", state: 2, command: 16128, data: ""
Read 7 bytes: ";42;52c"
Received sequence: ";42;52c", state: 0, command: 16227, data: ""

As you can see, the terminal response is being received in chunks. Lipgloss assumes that somehow we will always end the read on a sequence boundary, and:

  • Resets the parser state between reads
  • Sends every partial read to filter() regardless of the current parser state.

This causes filter() to:

  • Fail to parse the color (the least of our problems)
  • Fail to recognize the DA1 response, thus never exiting the loop until the cancel reader times out

What am I changing?

The patch makes sure that we only ever send complete sequences to filter(), while maintaining the parser state in between reads so that we can successfully complete parsing a sequence even if it's split inconveniently.

If you have any better ideas on how to implement this, please feel free to propose them :)


Side note, in all terminals I use the CSI DA1 request causes the terminal to return the background and foreground colors in addition to the DA1 response. I feel like maybe there's an opportunity to simplify queryBackgroundColor, but I don't know how this behaves in other terminal emulators.

  • I have read CONTRIBUTING.md.
  • I have created a discussion that was approved by a maintainer (for new features).

Nothing guarantees that we will read a complete sequence from in, and when we don't, we should not pass an incomplete one to filter or continue while discarding those bytes.
@aymanbagabas aymanbagabas deleted the branch charmbracelet:v2-exp February 24, 2026 11:46
@aymanbagabas

Copy link
Copy Markdown
Contributor

Thank you so much for your contribution here @MartinodF!

We accidentally closed your PR after merging v2-exp. Could you please re-open your changes and, we will take a look as soon as possible.

@MartinodF

Copy link
Copy Markdown
Contributor Author

@aymanbagabas since I'm not allowed to re-open this PR, I re-submitted it as #619

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.

2 participants