({
scrollRef,
isActive,
onScroll,
isModal = false
}: Props)
| 357 | * the bottom also re-enables sticky so new content follows naturally. |
| 358 | */ |
| 359 | export function ScrollKeybindingHandler({ |
| 360 | scrollRef, |
| 361 | isActive, |
| 362 | onScroll, |
| 363 | isModal = false |
| 364 | }: Props): React.ReactNode { |
| 365 | const selection = useSelection(); |
| 366 | const { |
| 367 | addNotification |
| 368 | } = useNotifications(); |
| 369 | // Lazy-inited on first wheel event so the XTVERSION probe (fired at |
| 370 | // raw-mode-enable time) has resolved by then — initializing in useRef() |
| 371 | // would read getWheelBase() before the probe reply arrives over SSH. |
| 372 | const wheelAccel = useRef<WheelAccelState | null>(null); |
| 373 | function showCopiedToast(text: string): void { |
| 374 | // getClipboardPath reads env synchronously — predicts what setClipboard |
| 375 | // did (native pbcopy / tmux load-buffer / raw OSC 52) so we can tell |
| 376 | // the user whether paste will Just Work or needs prefix+]. |
| 377 | const path = getClipboardPath(); |
| 378 | const n = text.length; |
| 379 | let msg: string; |
| 380 | switch (path) { |
| 381 | case 'native': |
| 382 | msg = `copied ${n} chars to clipboard`; |
| 383 | break; |
| 384 | case 'tmux-buffer': |
| 385 | msg = `copied ${n} chars to tmux buffer · paste with prefix + ]`; |
| 386 | break; |
| 387 | case 'osc52': |
| 388 | msg = `sent ${n} chars via OSC 52 · check terminal clipboard settings if paste fails`; |
| 389 | break; |
| 390 | } |
| 391 | addNotification({ |
| 392 | key: 'selection-copied', |
| 393 | text: msg, |
| 394 | color: 'suggestion', |
| 395 | priority: 'immediate', |
| 396 | timeoutMs: path === 'native' ? 2000 : 4000 |
| 397 | }); |
| 398 | } |
| 399 | function copyAndToast(): void { |
| 400 | const text_0 = selection.copySelection(); |
| 401 | if (text_0) showCopiedToast(text_0); |
| 402 | } |
| 403 | |
| 404 | // Translate selection to track a keyboard page jump. Selection coords are |
| 405 | // screen-buffer-local; a scrollTo that moves content by N rows must also |
| 406 | // shift anchor+focus by N so the highlight stays on the same text (native |
| 407 | // terminal behavior: selection moves with content, clips at viewport |
| 408 | // edges). Rows that scroll out of the viewport are captured into |
| 409 | // scrolledOffAbove/Below before the scroll so getSelectedText still |
| 410 | // returns the full text. Wheel scroll (scroll:lineUp/Down via scrollBy) |
| 411 | // still clears — its async pendingScrollDelta drain means the actual |
| 412 | // delta isn't known synchronously (follow-up). |
| 413 | function translateSelectionForJump(s: ScrollBoxHandle, delta: number): void { |
| 414 | const sel = selection.getState(); |
| 415 | if (!sel?.anchor || !sel.focus) return; |
| 416 | const top = s.getViewportTop(); |
nothing calls this directly
no test coverage detected