MCPcopy Create free account
hub / github.com/coder/ghostty-web / SelectionManager

Class SelectionManager

lib/selection-manager.ts:35–904  ·  view source on GitHub ↗

Source from the content-addressed store, hash-verified

33// ============================================================================
34
35export class SelectionManager {
36 private terminal: Terminal;
37 private renderer: CanvasRenderer;
38 private wasmTerm: GhosttyTerminal;
39 private textarea: HTMLTextAreaElement;
40
41 // Selection state - coordinates are in ABSOLUTE buffer space (viewportY + viewportRow)
42 // This ensures selection persists correctly when scrolling
43 private selectionStart: { col: number; absoluteRow: number } | null = null;
44 private selectionEnd: { col: number; absoluteRow: number } | null = null;
45 private isSelecting: boolean = false;
46 private mouseDownTarget: EventTarget | null = null; // Track where mousedown occurred
47
48 // Track rows that need redraw for clearing old selection
49 // Using a Set prevents the overwrite bug where mousemove would clobber
50 // the rows marked by clearSelection()
51 private dirtySelectionRows: Set<number> = new Set();
52
53 // Event emitter
54 private selectionChangedEmitter = new EventEmitter<void>();
55
56 // Store bound event handlers for cleanup
57 private boundMouseUpHandler: ((e: MouseEvent) => void) | null = null;
58 private boundContextMenuHandler: ((e: MouseEvent) => void) | null = null;
59 private boundClickHandler: ((e: MouseEvent) => void) | null = null;
60 private boundDocumentMouseMoveHandler: ((e: MouseEvent) => void) | null = null;
61
62 // Auto-scroll state for drag selection
63 private autoScrollInterval: ReturnType<typeof setInterval> | null = null;
64 private autoScrollDirection: number = 0; // -1 = up, 0 = none, 1 = down
65 private static readonly AUTO_SCROLL_EDGE_SIZE = 30; // pixels from edge to trigger scroll
66
67 /**
68 * Get current viewport Y position (how many lines scrolled into history)
69 */
70 private getViewportY(): number {
71 const rawViewportY =
72 typeof (this.terminal as any).getViewportY === 'function'
73 ? (this.terminal as any).getViewportY()
74 : (this.terminal as any).viewportY || 0;
75 return Math.max(0, Math.floor(rawViewportY));
76 }
77
78 /**
79 * Convert viewport row to absolute buffer row
80 * Absolute row is an index into combined buffer: scrollback (0 to len-1) + screen (len to len+rows-1)
81 */
82 private viewportRowToAbsolute(viewportRow: number): number {
83 const scrollbackLength = this.wasmTerm.getScrollbackLength();
84 const viewportY = this.getViewportY();
85 return scrollbackLength + viewportRow - viewportY;
86 }
87
88 /**
89 * Convert absolute buffer row to viewport row (may be outside visible range)
90 */
91 private absoluteRowToViewport(absoluteRow: number): number {
92 const scrollbackLength = this.wasmTerm.getScrollbackLength();

Callers

nothing calls this directly

Calls

no outgoing calls

Tested by

no test coverage detected

Used in the wild real call sites across dependent graphs

searching dependent graphs…