Skip to content

Conversation

@d3vr
Copy link

@d3vr d3vr commented Dec 31, 2025

Drag-and-drop file paths from a file manager into terminals using OpenTUI-based applications (like OpenCode) was not working in Alacritty, while it worked correctly in Ghostty/Kitty/others.

Root Cause Analysis

When a file is dragged and dropped into a terminal, the terminal sends the file path as a bracketed paste sequence (\x1b[200~...\x1b[201~). However, different terminals emit these sequences differently:

Terminal Behavior
Ghostty Sends paste in a separate chunk - no mouse event combined
Alacritty Combines mouse event + paste in a single chunk

Alacritty's combined sequence:

\x1b[<35;37;18M\x1b[200~/home/user/file.jpg \x1b[201~
└─── Mouse ───┘└────────── Paste data ──────────┘

The bug occurred because:

  1. stdinListener() called handleMouseData(data) with the full buffer
  2. MouseParser.parseMouseEvent() found the mouse event using regex match() (not anchored)
  3. handleMouseData() returned true, signaling the entire buffer was handled
  4. The paste data following the mouse event was discarded

Solution

Added a new method parseMouseEventWithConsumed() to MouseParser that returns both the parsed event and the number of bytes consumed. This allows the caller to process any remaining data in the buffer.

Key changes:

  1. parse.mouse.ts:

    • Added MouseParseResult type: { event: RawMouseEvent, consumed: number }
    • Added parseMouseEventWithConsumed() method with ^-anchored regex
    • Refactored parseMouseEvent() to call the new method (maintains backward compatibility, since the method was exported, I erred on the safe side).
  2. renderer.ts:

    • Updated handleMouseData() to use parseMouseEventWithConsumed()
    • After parsing mouse event, remaining bytes are passed to _stdinBuffer.process()

Flow after fix:

1. stdinListener receives: [mouse event][paste data]
2. handleMouseData() calls parseMouseEventWithConsumed()
3. Returns: { event: {...}, consumed: 14 }
4. Slices remaining data: buffer.slice(14) → paste sequence
5. Passes remaining to _stdinBuffer.process() → paste event fires ✓
6. Mouse event is then handled normally

Testing

  • Added new unit test suite parse.mouse.test.ts (11 tests)
  • Updated parseAllEvents helper in integration tests to use the new method
  • Manually verified drag-and-drop works in Alacritty with OpenCode

Related

  • Affects any OpenTUI-based application running in Alacritty terminal
  • May fix similar issues with other terminals that combine escape sequences

@kommander
Copy link
Collaborator

I tried to wrap my head around this, is this an issue with the stdin buffering? It seems like https://github.com/anomalyco/opentui/blob/main/packages/core/src/lib/stdin-buffer.ts should handle that before it hits any downstream consumers.

@d3vr
Copy link
Author

d3vr commented Jan 5, 2026

Yeah my bad, in hindsight this was a quick and dirty fix, I haven't wrapped my head around the codebase and I should have opened an issue and waited for your input before submitting a PR.

So the proper fix would be to make stdinBuffer handle all input first (removing the mouse-first check in stdinListener), and then check for mouse sequences in the on("data") handler after the buffer has already split sequences correctly. That way the buffer remains the single entry point for all stdin parsing.

Should I rework the PR with this approach, or would you prefer I close it and open a fresh one?

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