MCPcopy Create free account
hub / github.com/Noumena-Network/code / handleMouseEvent

Function handleMouseEvent

src/ink/components/App.tsx:522–664  ·  view source on GitHub ↗
(app: App, m: ParsedMouse)

Source from the content-addressed store, hash-verified

520
521/** Exported for testing. Mutates app.props.selection and click/hover state. */
522export function handleMouseEvent(app: App, m: ParsedMouse): void {
523 // Allow disabling click handling while keeping wheel scroll (which goes
524 // through the keybinding system as 'wheelup'/'wheeldown', not here).
525 if (isMouseClicksDisabled()) return;
526 const sel = app.props.selection;
527 // Terminal coords are 1-indexed; screen buffer is 0-indexed
528 const col = m.col - 1;
529 const row = m.row - 1;
530 const baseButton = m.button & 0x03;
531 if (m.action === 'press') {
532 if ((m.button & 0x20) !== 0 && baseButton === 3) {
533 // Mode-1003 motion with no button held. Dispatch hover; skip the
534 // rest of this handler (no selection, no click-count side effects).
535 // Lost-release recovery: no-button motion while isDragging=true means
536 // the release happened outside the terminal window (iTerm2 doesn't
537 // capture the pointer past window bounds, so the SGR 'm' never
538 // arrives). Finish the selection here so copy-on-select fires. The
539 // FOCUS_OUT handler covers the "switched apps" case but not "released
540 // past the edge, came back" — and tmux drops focus events unless
541 // `focus-events on` is set, so this is the more reliable signal.
542 if (sel.isDragging) {
543 finishSelection(sel);
544 app.props.onSelectionChange();
545 }
546 if (col === app.lastHoverCol && row === app.lastHoverRow) return;
547 app.lastHoverCol = col;
548 app.lastHoverRow = row;
549 app.props.onHoverAt(col, row);
550 return;
551 }
552 if (baseButton !== 0) {
553 // Non-left press breaks the multi-click chain.
554 app.clickCount = 0;
555 return;
556 }
557 if ((m.button & 0x20) !== 0) {
558 // Drag motion: mode-aware extension (char/word/line). onSelectionDrag
559 // calls notifySelectionChange internally — no extra onSelectionChange.
560 app.props.onSelectionDrag(col, row);
561 return;
562 }
563 // Lost-release fallback for mode-1002-only terminals: a fresh press
564 // while isDragging=true means the previous release was dropped (cursor
565 // left the window). Finish that selection so copy-on-select fires
566 // before startSelection/onMultiClick clobbers it. Mode-1003 terminals
567 // hit the no-button-motion recovery above instead, so this is rare.
568 if (sel.isDragging) {
569 finishSelection(sel);
570 app.props.onSelectionChange();
571 }
572 // Fresh left press. Detect multi-click HERE (not on release) so the
573 // word/line highlight appears immediately and a subsequent drag can
574 // extend by word/line like native macOS. Previously detected on
575 // release, which meant (a) visible latency before the word highlights
576 // and (b) double-click+drag fell through to char-mode selection.
577 const now = Date.now();
578 const nearLast = now - app.lastClickTime < MULTI_CLICK_TIMEOUT_MS && Math.abs(col - app.lastClickCol) <= MULTI_CLICK_DISTANCE && Math.abs(row - app.lastClickRow) <= MULTI_CLICK_DISTANCE;
579 app.clickCount = nearLast ? app.clickCount + 1 : 1;

Callers 1

processKeysInBatchFunction · 0.85

Calls 6

isMouseClicksDisabledFunction · 0.85
finishSelectionFunction · 0.85
startSelectionFunction · 0.85
hasSelectionFunction · 0.85
isXtermJsFunction · 0.85
getHyperlinkAtMethod · 0.80

Tested by

no test coverage detected