MCPcopy
hub / github.com/codeaashu/claude-code / handleMouseEvent

Function handleMouseEvent

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

Source from the content-addressed store, hash-verified

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

Callers 1

processKeysInBatchFunction · 0.85

Calls 7

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

Tested by

no test coverage detected