@code-yeongyu/senpi-tui 2026.9.12-3 → 2026.9.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -53
- package/dist/alt-screen-search.d.ts +1 -21
- package/dist/alt-screen-search.d.ts.map +1 -1
- package/dist/alt-screen-search.js +42 -199
- package/dist/alt-screen-search.js.map +1 -1
- package/dist/components/box.d.ts +1 -3
- package/dist/components/box.d.ts.map +1 -1
- package/dist/components/box.js +0 -28
- package/dist/components/box.js.map +1 -1
- package/dist/components/editor.d.ts +1 -6
- package/dist/components/editor.d.ts.map +1 -1
- package/dist/components/editor.js +18 -98
- package/dist/components/editor.js.map +1 -1
- package/dist/components/input.d.ts +1 -12
- package/dist/components/input.d.ts.map +1 -1
- package/dist/components/input.js +6 -40
- package/dist/components/input.js.map +1 -1
- package/dist/components/latex.d.ts.map +1 -1
- package/dist/components/latex.js +0 -7
- package/dist/components/latex.js.map +1 -1
- package/dist/components/loader.d.ts +0 -3
- package/dist/components/loader.d.ts.map +1 -1
- package/dist/components/loader.js +8 -20
- package/dist/components/loader.js.map +1 -1
- package/dist/components/scroll-view.d.ts +3 -6
- package/dist/components/scroll-view.d.ts.map +1 -1
- package/dist/components/scroll-view.js +1 -6
- package/dist/components/scroll-view.js.map +1 -1
- package/dist/components/select-list.d.ts +1 -4
- package/dist/components/select-list.d.ts.map +1 -1
- package/dist/components/select-list.js +2 -48
- package/dist/components/select-list.js.map +1 -1
- package/dist/components/settings-list.d.ts +1 -5
- package/dist/components/settings-list.d.ts.map +1 -1
- package/dist/components/settings-list.js +5 -54
- package/dist/components/settings-list.js.map +1 -1
- package/dist/index.d.ts +1 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -3
- package/dist/index.js.map +1 -1
- package/dist/layout.d.ts +1 -3
- package/dist/layout.d.ts.map +1 -1
- package/dist/layout.js +9 -32
- package/dist/layout.js.map +1 -1
- package/dist/native-modifiers.d.ts +1 -2
- package/dist/native-modifiers.d.ts.map +1 -1
- package/dist/native-modifiers.js +44 -3
- package/dist/native-modifiers.js.map +1 -1
- package/dist/terminal.d.ts.map +1 -1
- package/dist/terminal.js +20 -2
- package/dist/terminal.js.map +1 -1
- package/dist/tui-alt-screen.d.ts +1 -31
- package/dist/tui-alt-screen.d.ts.map +1 -1
- package/dist/tui-alt-screen.js +41 -335
- package/dist/tui-alt-screen.js.map +1 -1
- package/dist/tui.d.ts +5 -83
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +4 -118
- package/dist/tui.js.map +1 -1
- package/dist/utils.d.ts +0 -2
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +0 -9
- package/dist/utils.js.map +1 -1
- package/native/darwin/README.md +6 -8
- package/native/darwin/build.sh +5 -12
- package/native/darwin/prebuilds/darwin-arm64/{darwin-platform.node → darwin-modifiers.node} +0 -0
- package/native/darwin/prebuilds/darwin-x64/{darwin-platform.node → darwin-modifiers.node} +0 -0
- package/native/darwin/src/darwin-modifiers.c +70 -0
- package/native/win32/README.md +7 -18
- package/native/win32/build.mjs +4 -7
- package/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
- package/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
- package/native/win32/src/win32-console-mode.c +119 -0
- package/package.json +3 -10
- package/dist/components/mouse-region.d.ts +0 -12
- package/dist/components/mouse-region.d.ts.map +0 -1
- package/dist/components/mouse-region.js +0 -19
- package/dist/components/mouse-region.js.map +0 -1
- package/dist/native-platform.d.ts +0 -18
- package/dist/native-platform.d.ts.map +0 -1
- package/dist/native-platform.js +0 -42
- package/dist/native-platform.js.map +0 -1
- package/native/clipboard.h +0 -170
- package/native/darwin/src/darwin-platform.m +0 -93
- package/native/linux/README.md +0 -23
- package/native/linux/build.sh +0 -65
- package/native/linux/prebuilds/linux-arm64/linux-platform-x11.node +0 -0
- package/native/linux/prebuilds/linux-x64/linux-platform-x11.node +0 -0
- package/native/linux/src/clipboard-worker.h +0 -70
- package/native/linux/src/linux-platform-x11.c +0 -274
- package/native/napi.h +0 -89
- package/native/win32/prebuilds/win32-arm64/win32-platform.node +0 -0
- package/native/win32/prebuilds/win32-x64/win32-platform.node +0 -0
- package/native/win32/src/win32-platform.c +0 -241
package/dist/tui-alt-screen.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
var _a;
|
|
2
|
-
import { AltScreenSearchComponent,
|
|
2
|
+
import { AltScreenSearchComponent, findAltScreenSearchMatches, getAltScreenSearchMatchKey, } from "./alt-screen-search.js";
|
|
3
3
|
import { AltScreenFlashContainer } from "./components/alt-screen-flash.js";
|
|
4
4
|
import { ScrollView } from "./components/scroll-view.js";
|
|
5
5
|
import { getKeybindings } from "./keybindings.js";
|
|
6
6
|
import { isKeyRelease } from "./keys.js";
|
|
7
|
-
import {
|
|
8
|
-
import { getLayoutNode } from "./layout-node.js";
|
|
7
|
+
import { getScrollbarGeometry, getScrollViewBox, getScrollViewsAt, renderLayoutFrame, } from "./layout.js";
|
|
9
8
|
import { deleteAllKittyImages, deleteAllKittyPlacements, deleteKittyImage, getCapabilities, getKittyImagePlacement, isImageLine, setCapabilities, } from "./terminal-image.js";
|
|
10
|
-
import {
|
|
11
|
-
import { extractAnsiCode, getGraphemeCellRange, getOsc8LinkAtColumn, getWordSegmenter, sliceByColumn, stripTerminalSequences,
|
|
9
|
+
import { CURSOR_MARKER, compositeTuiLine, TuiBase, VIEWPORT_TUI, } from "./tui.js";
|
|
10
|
+
import { extractAnsiCode, getGraphemeCellRange, getOsc8LinkAtColumn, getWordSegmenter, sliceByColumn, stripTerminalSequences, visibleWidth, } from "./utils.js";
|
|
12
11
|
const ENTER_ALT_SCREEN = "\x1b[?1049h";
|
|
13
12
|
const EXIT_ALT_SCREEN = "\x1b[?1049l";
|
|
14
13
|
const DISABLE_AUTOWRAP = "\x1b[?7l";
|
|
@@ -23,7 +22,6 @@ const END_SYNCHRONIZED_OUTPUT = "\x1b[?2026l";
|
|
|
23
22
|
const OSC133_ZONE_PREFIX = /^(?:\x1b\]133;[ABC](?:\x07|\x1b\\))+/;
|
|
24
23
|
const OSC133_PROMPT_START = /^\x1b\]133;A(?:\x07|\x1b\\)/;
|
|
25
24
|
const PAGE_SCROLL_OVERLAP = 4;
|
|
26
|
-
const ALT_WHEEL_SCROLL_MULTIPLIER = 5;
|
|
27
25
|
const MAX_CACHED_OFFSCREEN_KITTY_IMAGES = 16;
|
|
28
26
|
const MAX_CACHED_OFFSCREEN_KITTY_TRANSMISSION_BYTES = 32 * 1024 * 1024;
|
|
29
27
|
const MAX_CACHED_OFFSCREEN_KITTY_DECODED_BYTES = 64 * 1024 * 1024;
|
|
@@ -50,10 +48,8 @@ export class TuiAltScreen extends TuiBase {
|
|
|
50
48
|
this.selectionAutoScrollDirection = 0;
|
|
51
49
|
this.selectionPressActive = false;
|
|
52
50
|
this.selectionDragged = false;
|
|
53
|
-
this.mousePressMoved = false;
|
|
54
51
|
this.implicitDocument = {
|
|
55
52
|
render: (width) => super.render(width),
|
|
56
|
-
handleMouse: (event) => super.handleMouse(event),
|
|
57
53
|
invalidate: () => {
|
|
58
54
|
for (const child of this.children)
|
|
59
55
|
child.invalidate();
|
|
@@ -65,8 +61,6 @@ export class TuiAltScreen extends TuiBase {
|
|
|
65
61
|
this.mouseEnabled = options.mouse ?? true;
|
|
66
62
|
this.searchMatchStyle = options.searchMatchStyle ?? ((text) => `\x1b[4m${text}\x1b[24m`);
|
|
67
63
|
this.searchCurrentMatchStyle = options.searchCurrentMatchStyle ?? ((text) => `\x1b[1;7m${text}\x1b[22;27m`);
|
|
68
|
-
this.searchNavigationButtonStyle = options.searchNavigationButtonStyle ?? ((text) => text);
|
|
69
|
-
this.scrollToEndIndicator = options.scrollToEndIndicator;
|
|
70
64
|
this.openUrl = options.openUrl;
|
|
71
65
|
this.onRightClickPaste = options.onRightClickPaste;
|
|
72
66
|
this.copyOnSelect = options.copyOnSelect ?? true;
|
|
@@ -135,8 +129,6 @@ export class TuiAltScreen extends TuiBase {
|
|
|
135
129
|
this.lastClick = undefined;
|
|
136
130
|
this.pressedUrl = undefined;
|
|
137
131
|
this.selectionDragged = false;
|
|
138
|
-
this.clearComponentMouseGesture();
|
|
139
|
-
this.lastComponentClick = undefined;
|
|
140
132
|
this.resetRenderState();
|
|
141
133
|
const term = process.env.TERM?.toLowerCase() ?? "";
|
|
142
134
|
// Multiplexers can lag when every pointer movement is forwarded. Button-motion
|
|
@@ -156,7 +148,6 @@ export class TuiAltScreen extends TuiBase {
|
|
|
156
148
|
this.selectionPressActive = false;
|
|
157
149
|
this.stopScrollbarHover();
|
|
158
150
|
this.stopScrollbarDrag();
|
|
159
|
-
this.clearComponentMouseGesture();
|
|
160
151
|
this.flashes.dispose();
|
|
161
152
|
if (!this.altScreenActive)
|
|
162
153
|
return;
|
|
@@ -271,15 +262,14 @@ export class TuiAltScreen extends TuiBase {
|
|
|
271
262
|
return;
|
|
272
263
|
}
|
|
273
264
|
}
|
|
274
|
-
|
|
265
|
+
openSearch() {
|
|
275
266
|
if (this.activeSearch) {
|
|
276
|
-
this.
|
|
267
|
+
this.activeSearch.overlay?.focus();
|
|
277
268
|
return;
|
|
278
269
|
}
|
|
279
|
-
const component = new AltScreenSearchComponent((query) => this.updateSearchQuery(query)
|
|
270
|
+
const component = new AltScreenSearchComponent((query) => this.updateSearchQuery(query));
|
|
280
271
|
const search = {
|
|
281
272
|
component,
|
|
282
|
-
index: new AltScreenSearchIndex(),
|
|
283
273
|
query: "",
|
|
284
274
|
matches: [],
|
|
285
275
|
selectedIndex: -1,
|
|
@@ -290,7 +280,7 @@ export class TuiAltScreen extends TuiBase {
|
|
|
290
280
|
search.overlay = this.showOverlay(component, {
|
|
291
281
|
anchor: "top-right",
|
|
292
282
|
width: "40%",
|
|
293
|
-
minWidth:
|
|
283
|
+
minWidth: 24,
|
|
294
284
|
margin: 1,
|
|
295
285
|
});
|
|
296
286
|
}
|
|
@@ -320,29 +310,6 @@ export class TuiAltScreen extends TuiBase {
|
|
|
320
310
|
search.selectionMode = direction < 0 ? "previous" : "next";
|
|
321
311
|
this.requestRender();
|
|
322
312
|
}
|
|
323
|
-
getSearchNavigationDirectionAt(x, y) {
|
|
324
|
-
const search = this.activeSearch;
|
|
325
|
-
const bounds = search?.overlay?.getBounds();
|
|
326
|
-
if (!search || !bounds)
|
|
327
|
-
return undefined;
|
|
328
|
-
if (x < bounds.col || x >= bounds.col + bounds.width || y < bounds.row || y >= bounds.row + bounds.height) {
|
|
329
|
-
return undefined;
|
|
330
|
-
}
|
|
331
|
-
return search.component.getNavigationDirectionAt(y - bounds.row, x - bounds.col);
|
|
332
|
-
}
|
|
333
|
-
handleSearchMouseEvent(event) {
|
|
334
|
-
const search = this.activeSearch;
|
|
335
|
-
if (!search)
|
|
336
|
-
return false;
|
|
337
|
-
const direction = this.getSearchNavigationDirectionAt(event.x, event.y);
|
|
338
|
-
if (search.component.setHoveredNavigationDirection(direction))
|
|
339
|
-
this.requestRender();
|
|
340
|
-
if (direction === undefined || event.release || (event.button & 32) !== 0 || (event.button & 3) !== 0) {
|
|
341
|
-
return false;
|
|
342
|
-
}
|
|
343
|
-
this.navigateSearch(direction);
|
|
344
|
-
return true;
|
|
345
|
-
}
|
|
346
313
|
refreshSearch(layout) {
|
|
347
314
|
const search = this.activeSearch;
|
|
348
315
|
if (!search)
|
|
@@ -359,29 +326,16 @@ export class TuiAltScreen extends TuiBase {
|
|
|
359
326
|
return false;
|
|
360
327
|
}
|
|
361
328
|
const shouldRevealSelection = search.selectionMode !== "retain";
|
|
362
|
-
const
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
return false;
|
|
367
|
-
const exactIndex = result.changed
|
|
368
|
-
? search.selectedKey
|
|
369
|
-
? matches.findIndex((match) => getAltScreenSearchMatchKey(match) === search.selectedKey)
|
|
370
|
-
: -1
|
|
371
|
-
: search.selectedIndex;
|
|
329
|
+
const matches = findAltScreenSearchMatches(lines, search.query);
|
|
330
|
+
const exactIndex = search.selectedKey
|
|
331
|
+
? matches.findIndex((match) => getAltScreenSearchMatchKey(match) === search.selectedKey)
|
|
332
|
+
: -1;
|
|
372
333
|
let selectedIndex = -1;
|
|
373
334
|
if (matches.length > 0) {
|
|
374
335
|
if (search.selectionMode === "query") {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
const middle = low + Math.floor((high - low) / 2);
|
|
379
|
-
if ((matches[middle].segments[0]?.row ?? 0) < search.anchorRow)
|
|
380
|
-
low = middle + 1;
|
|
381
|
-
else
|
|
382
|
-
high = middle;
|
|
383
|
-
}
|
|
384
|
-
selectedIndex = low < matches.length ? low : 0;
|
|
336
|
+
selectedIndex = matches.findIndex((match) => (match.segments[0]?.row ?? 0) >= search.anchorRow);
|
|
337
|
+
if (selectedIndex < 0)
|
|
338
|
+
selectedIndex = 0;
|
|
385
339
|
}
|
|
386
340
|
else if (search.selectionMode === "next") {
|
|
387
341
|
const baseIndex = exactIndex >= 0 ? exactIndex : Math.min(search.selectedIndex, matches.length - 1);
|
|
@@ -396,6 +350,7 @@ export class TuiAltScreen extends TuiBase {
|
|
|
396
350
|
exactIndex >= 0 ? exactIndex : Math.min(Math.max(0, search.selectedIndex), matches.length - 1);
|
|
397
351
|
}
|
|
398
352
|
}
|
|
353
|
+
search.matches = matches;
|
|
399
354
|
search.selectedIndex = selectedIndex;
|
|
400
355
|
search.selectedKey = selectedIndex >= 0 ? getAltScreenSearchMatchKey(matches[selectedIndex]) : undefined;
|
|
401
356
|
search.selectionMode = "retain";
|
|
@@ -423,12 +378,6 @@ export class TuiAltScreen extends TuiBase {
|
|
|
423
378
|
shouldDeferViewportInputToOverlay() {
|
|
424
379
|
return this.isOverlayFocused() && this.activeSearch?.overlay?.isFocused() !== true;
|
|
425
380
|
}
|
|
426
|
-
clearComponentMouseGesture() {
|
|
427
|
-
this.mouseCapture = undefined;
|
|
428
|
-
this.mousePressTarget = undefined;
|
|
429
|
-
this.mousePressPoint = undefined;
|
|
430
|
-
this.mousePressMoved = false;
|
|
431
|
-
}
|
|
432
381
|
handleViewportInput(data) {
|
|
433
382
|
if (data === FOCUS_OUT) {
|
|
434
383
|
const hadActiveSelection = this.selectionPressActive;
|
|
@@ -436,13 +385,9 @@ export class TuiAltScreen extends TuiBase {
|
|
|
436
385
|
this.selectionPressActive = false;
|
|
437
386
|
this.stopSelectionAutoScroll();
|
|
438
387
|
this.stopScrollbarHover();
|
|
439
|
-
if (this.activeSearch?.component.setHoveredNavigationDirection(undefined))
|
|
440
|
-
this.requestRender();
|
|
441
388
|
this.stopScrollbarDrag();
|
|
442
389
|
this.pressedUrl = undefined;
|
|
443
390
|
this.selectionDragged = false;
|
|
444
|
-
this.clearComponentMouseGesture();
|
|
445
|
-
this.lastComponentClick = undefined;
|
|
446
391
|
if (hadActiveSelection) {
|
|
447
392
|
this.selectionAnchor = undefined;
|
|
448
393
|
this.selectionFocus = undefined;
|
|
@@ -458,16 +403,6 @@ export class TuiAltScreen extends TuiBase {
|
|
|
458
403
|
return { consume: true };
|
|
459
404
|
const wheelEvent = this.parseWheelEvent(data);
|
|
460
405
|
if (wheelEvent) {
|
|
461
|
-
const event = this.createMouseEvent("wheel", wheelEvent.button, wheelEvent.x, wheelEvent.y, {
|
|
462
|
-
wheelDelta: wheelEvent.direction * this.getWheelScrollLines(wheelEvent.button),
|
|
463
|
-
});
|
|
464
|
-
const overlay = this.dispatchMouseToOverlay(event);
|
|
465
|
-
const result = overlay.result ?? (overlay.hit ? undefined : this.dispatchMouseToLayout(event));
|
|
466
|
-
if (result) {
|
|
467
|
-
if (this.applyMouseDispatchResult(event, result))
|
|
468
|
-
this.requestRender();
|
|
469
|
-
return { consume: true };
|
|
470
|
-
}
|
|
471
406
|
if (this.shouldDeferViewportInputToOverlay())
|
|
472
407
|
return undefined;
|
|
473
408
|
this.routeWheel(wheelEvent);
|
|
@@ -475,7 +410,13 @@ export class TuiAltScreen extends TuiBase {
|
|
|
475
410
|
}
|
|
476
411
|
const mouseEvent = this.parseSgrMouseEvent(data);
|
|
477
412
|
if (mouseEvent) {
|
|
478
|
-
this.
|
|
413
|
+
if (this.handleRightClickPaste(mouseEvent))
|
|
414
|
+
return { consume: true };
|
|
415
|
+
const handled = this.handleScrollbarMouseEvent(mouseEvent);
|
|
416
|
+
if (!this.scrollbarDrag)
|
|
417
|
+
this.updateScrollbarHover(mouseEvent.x, mouseEvent.y);
|
|
418
|
+
if (!handled)
|
|
419
|
+
this.handleSelectionMouseEvent(mouseEvent);
|
|
479
420
|
return { consume: true };
|
|
480
421
|
}
|
|
481
422
|
if (this.isMouseSequence(data))
|
|
@@ -484,7 +425,7 @@ export class TuiAltScreen extends TuiBase {
|
|
|
484
425
|
const isRelease = isKeyRelease(data);
|
|
485
426
|
if (keybindings.matches(data, "tui.altScreen.search")) {
|
|
486
427
|
if (!isRelease)
|
|
487
|
-
this.
|
|
428
|
+
this.openSearch();
|
|
488
429
|
return { consume: true };
|
|
489
430
|
}
|
|
490
431
|
if (this.activeSearch?.overlay?.isFocused()) {
|
|
@@ -560,165 +501,6 @@ export class TuiAltScreen extends TuiBase {
|
|
|
560
501
|
}
|
|
561
502
|
return undefined;
|
|
562
503
|
}
|
|
563
|
-
decodeMouseButton(button) {
|
|
564
|
-
switch (button & 3) {
|
|
565
|
-
case 0:
|
|
566
|
-
return "left";
|
|
567
|
-
case 1:
|
|
568
|
-
return "middle";
|
|
569
|
-
case 2:
|
|
570
|
-
return "right";
|
|
571
|
-
default:
|
|
572
|
-
return "none";
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
createMouseEvent(type, button, x, y, extra = {}) {
|
|
576
|
-
return {
|
|
577
|
-
type,
|
|
578
|
-
button: type === "wheel" ? "none" : this.decodeMouseButton(button),
|
|
579
|
-
x,
|
|
580
|
-
y,
|
|
581
|
-
screenX: x,
|
|
582
|
-
screenY: y,
|
|
583
|
-
width: Math.max(1, this.terminal.columns),
|
|
584
|
-
height: Math.max(1, this.terminal.rows),
|
|
585
|
-
shift: (button & 4) !== 0,
|
|
586
|
-
alt: (button & 8) !== 0,
|
|
587
|
-
ctrl: (button & 16) !== 0,
|
|
588
|
-
...(extra.wheelDelta === undefined ? {} : { wheelDelta: extra.wheelDelta }),
|
|
589
|
-
...(extra.clickCount === undefined ? {} : { clickCount: extra.clickCount }),
|
|
590
|
-
};
|
|
591
|
-
}
|
|
592
|
-
dispatchMouseToLayout(event) {
|
|
593
|
-
if (!this.currentLayout)
|
|
594
|
-
return undefined;
|
|
595
|
-
const visited = new Set();
|
|
596
|
-
const boxes = getLayoutBoxesAt(this.currentLayout, event.screenX, event.screenY);
|
|
597
|
-
for (const box of boxes) {
|
|
598
|
-
if (visited.has(box.component))
|
|
599
|
-
continue;
|
|
600
|
-
if (getLayoutNode(box.component) && box.component.handleMouse === Container.prototype.handleMouse)
|
|
601
|
-
continue;
|
|
602
|
-
visited.add(box.component);
|
|
603
|
-
const result = dispatchMouseEvent(box.component, {
|
|
604
|
-
...event,
|
|
605
|
-
x: event.screenX - box.rect.x,
|
|
606
|
-
y: event.screenY - box.rect.y,
|
|
607
|
-
width: box.rect.width,
|
|
608
|
-
height: box.rect.height,
|
|
609
|
-
});
|
|
610
|
-
if (result)
|
|
611
|
-
return result;
|
|
612
|
-
}
|
|
613
|
-
return undefined;
|
|
614
|
-
}
|
|
615
|
-
applyMouseDispatchResult(event, result) {
|
|
616
|
-
const focusTarget = this.resolveMouseFocusTarget(result.focusTarget ?? result.target.component);
|
|
617
|
-
const focusChanged = result.focus === true && this.getFocusedComponent() !== focusTarget;
|
|
618
|
-
if (result.focus)
|
|
619
|
-
this.setFocus(focusTarget);
|
|
620
|
-
if (result.capture)
|
|
621
|
-
this.mouseCapture = result.target;
|
|
622
|
-
return (result.render ??
|
|
623
|
-
(focusChanged ||
|
|
624
|
-
event.type === "press" ||
|
|
625
|
-
event.type === "click" ||
|
|
626
|
-
event.type === "drag" ||
|
|
627
|
-
event.type === "wheel"));
|
|
628
|
-
}
|
|
629
|
-
dispatchMouseToTarget(event, target) {
|
|
630
|
-
return dispatchMouseEvent(target.component, retargetMouseEvent(event, target));
|
|
631
|
-
}
|
|
632
|
-
getComponentClickCount(target, x, y) {
|
|
633
|
-
const now = Date.now();
|
|
634
|
-
const previous = this.lastComponentClick;
|
|
635
|
-
const count = previous &&
|
|
636
|
-
now - previous.timestamp <= DOUBLE_CLICK_INTERVAL_MS &&
|
|
637
|
-
previous.component === target.component &&
|
|
638
|
-
previous.x === x &&
|
|
639
|
-
previous.y === y
|
|
640
|
-
? (previous.count % 3) + 1
|
|
641
|
-
: 1;
|
|
642
|
-
this.lastComponentClick = { timestamp: now, count, component: target.component, x, y };
|
|
643
|
-
return count;
|
|
644
|
-
}
|
|
645
|
-
clearTextSelection() {
|
|
646
|
-
this.stopSelectionAutoScroll();
|
|
647
|
-
this.selectionPressActive = false;
|
|
648
|
-
this.selectionAnchor = undefined;
|
|
649
|
-
this.selectionFocus = undefined;
|
|
650
|
-
this.selectionGranularity = "character";
|
|
651
|
-
this.selectionInitialRange = undefined;
|
|
652
|
-
this.pressedUrl = undefined;
|
|
653
|
-
this.selectionDragged = false;
|
|
654
|
-
}
|
|
655
|
-
handleMouseEvent(raw) {
|
|
656
|
-
const isMotion = (raw.button & 32) !== 0;
|
|
657
|
-
const type = raw.release
|
|
658
|
-
? "release"
|
|
659
|
-
: isMotion
|
|
660
|
-
? this.decodeMouseButton(raw.button) === "none"
|
|
661
|
-
? "move"
|
|
662
|
-
: "drag"
|
|
663
|
-
: "press";
|
|
664
|
-
const event = this.createMouseEvent(type, raw.button, raw.x, raw.y);
|
|
665
|
-
if (this.mouseCapture || this.mousePressTarget) {
|
|
666
|
-
const target = this.mouseCapture ?? this.mousePressTarget;
|
|
667
|
-
if (this.mousePressPoint && (raw.x !== this.mousePressPoint.x || raw.y !== this.mousePressPoint.y)) {
|
|
668
|
-
this.mousePressMoved = true;
|
|
669
|
-
this.lastComponentClick = undefined;
|
|
670
|
-
}
|
|
671
|
-
let render = false;
|
|
672
|
-
const targetResult = this.dispatchMouseToTarget(event, target);
|
|
673
|
-
if (targetResult)
|
|
674
|
-
render = this.applyMouseDispatchResult(event, targetResult);
|
|
675
|
-
if (raw.release) {
|
|
676
|
-
if (!this.mousePressMoved && this.mousePressPoint?.x === raw.x && this.mousePressPoint.y === raw.y) {
|
|
677
|
-
const clickEvent = this.createMouseEvent("click", raw.button, raw.x, raw.y, {
|
|
678
|
-
clickCount: this.getComponentClickCount(target, raw.x, raw.y),
|
|
679
|
-
});
|
|
680
|
-
const clickResult = this.dispatchMouseToTarget(clickEvent, target);
|
|
681
|
-
if (clickResult)
|
|
682
|
-
render = this.applyMouseDispatchResult(clickEvent, clickResult) || render;
|
|
683
|
-
}
|
|
684
|
-
this.clearComponentMouseGesture();
|
|
685
|
-
}
|
|
686
|
-
if (render)
|
|
687
|
-
this.requestRender();
|
|
688
|
-
return;
|
|
689
|
-
}
|
|
690
|
-
if (this.handleSearchMouseEvent(raw))
|
|
691
|
-
return;
|
|
692
|
-
const overlay = this.dispatchMouseToOverlay(event);
|
|
693
|
-
if (!overlay.hit) {
|
|
694
|
-
if (this.handleScrollToEndIndicatorMouseEvent(raw))
|
|
695
|
-
return;
|
|
696
|
-
const scrollbarHandled = this.handleScrollbarMouseEvent(raw);
|
|
697
|
-
if (!this.scrollbarDrag)
|
|
698
|
-
this.updateScrollbarHover(raw.x, raw.y);
|
|
699
|
-
if (scrollbarHandled)
|
|
700
|
-
return;
|
|
701
|
-
}
|
|
702
|
-
else {
|
|
703
|
-
this.stopScrollbarHover();
|
|
704
|
-
}
|
|
705
|
-
const result = overlay.result ?? (overlay.hit ? undefined : this.dispatchMouseToLayout(event));
|
|
706
|
-
if (result) {
|
|
707
|
-
const render = this.applyMouseDispatchResult(event, result);
|
|
708
|
-
if (type === "press") {
|
|
709
|
-
this.clearTextSelection();
|
|
710
|
-
this.mousePressTarget = result.target;
|
|
711
|
-
this.mousePressPoint = { x: raw.x, y: raw.y };
|
|
712
|
-
this.mousePressMoved = false;
|
|
713
|
-
}
|
|
714
|
-
if (render)
|
|
715
|
-
this.requestRender();
|
|
716
|
-
return;
|
|
717
|
-
}
|
|
718
|
-
if (this.handleRightClickPaste(raw))
|
|
719
|
-
return;
|
|
720
|
-
this.handleSelectionMouseEvent(raw);
|
|
721
|
-
}
|
|
722
504
|
parseWheelEvent(data) {
|
|
723
505
|
const sgr = /^\x1b\[<(\d+);(\d+);(\d+)[Mm]$/.exec(data);
|
|
724
506
|
if (sgr) {
|
|
@@ -732,7 +514,6 @@ export class TuiAltScreen extends TuiBase {
|
|
|
732
514
|
direction: direction === 0 ? -1 : 1,
|
|
733
515
|
x: Number.parseInt(sgr[2], 10) - 1,
|
|
734
516
|
y: Number.parseInt(sgr[3], 10) - 1,
|
|
735
|
-
button,
|
|
736
517
|
};
|
|
737
518
|
}
|
|
738
519
|
if (data.length === 6 && data.startsWith("\x1b[M")) {
|
|
@@ -746,17 +527,12 @@ export class TuiAltScreen extends TuiBase {
|
|
|
746
527
|
direction: direction === 0 ? -1 : 1,
|
|
747
528
|
x: data.charCodeAt(4) - 33,
|
|
748
529
|
y: data.charCodeAt(5) - 33,
|
|
749
|
-
button,
|
|
750
530
|
};
|
|
751
531
|
}
|
|
752
532
|
return undefined;
|
|
753
533
|
}
|
|
754
|
-
getWheelScrollLines(button) {
|
|
755
|
-
// SGR mouse button codes use bit 3 (value 8) for the Alt modifier.
|
|
756
|
-
return (button & 8) !== 0 ? this.wheelScrollLines * ALT_WHEEL_SCROLL_MULTIPLIER : this.wheelScrollLines;
|
|
757
|
-
}
|
|
758
534
|
routeWheel(event) {
|
|
759
|
-
let remaining = event.direction * this.
|
|
535
|
+
let remaining = event.direction * this.wheelScrollLines;
|
|
760
536
|
const seen = new Set();
|
|
761
537
|
for (const scrollView of this.currentLayout ? getScrollViewsAt(this.currentLayout, event.x, event.y) : []) {
|
|
762
538
|
seen.add(scrollView);
|
|
@@ -797,25 +573,16 @@ export class TuiAltScreen extends TuiBase {
|
|
|
797
573
|
}
|
|
798
574
|
return true;
|
|
799
575
|
}
|
|
800
|
-
|
|
801
|
-
const rect = this.scrollToEndIndicatorRect;
|
|
802
|
-
if (!rect || event.release || (event.button & 32) !== 0 || (event.button & 3) !== 0)
|
|
803
|
-
return false;
|
|
804
|
-
if (event.y !== rect.row || event.x < rect.column || event.x >= rect.column + rect.width)
|
|
805
|
-
return false;
|
|
806
|
-
this.scrollToBottom();
|
|
807
|
-
return true;
|
|
808
|
-
}
|
|
809
|
-
getScrollbarTargetAt(x, y, includeHiddenAuto = false) {
|
|
576
|
+
getScrollbarTargetAt(x, y) {
|
|
810
577
|
if (this.hasOverlay() || !this.currentLayout)
|
|
811
578
|
return undefined;
|
|
812
579
|
for (const scrollView of getScrollViewsAt(this.currentLayout, x, y)) {
|
|
813
580
|
const box = getScrollViewBox(this.currentLayout, scrollView);
|
|
814
|
-
const geometry = box ? getScrollbarGeometry(box
|
|
581
|
+
const geometry = box ? getScrollbarGeometry(box) : undefined;
|
|
815
582
|
if (geometry &&
|
|
816
583
|
x === geometry.column &&
|
|
817
|
-
y >= geometry.
|
|
818
|
-
y < geometry.
|
|
584
|
+
y >= geometry.thumbTop &&
|
|
585
|
+
y < geometry.thumbTop + geometry.thumbHeight) {
|
|
819
586
|
return { scrollView, geometry };
|
|
820
587
|
}
|
|
821
588
|
}
|
|
@@ -829,17 +596,11 @@ export class TuiAltScreen extends TuiBase {
|
|
|
829
596
|
this.scrollbarHover?.setScrollbarActive(true);
|
|
830
597
|
}
|
|
831
598
|
updateScrollbarHover(x, y) {
|
|
832
|
-
this.setScrollbarHover(this.getScrollbarTargetAt(x, y
|
|
599
|
+
this.setScrollbarHover(this.getScrollbarTargetAt(x, y)?.scrollView);
|
|
833
600
|
}
|
|
834
601
|
stopScrollbarHover() {
|
|
835
602
|
this.setScrollbarHover(undefined);
|
|
836
603
|
}
|
|
837
|
-
scrollScrollbarToPointer(scrollView, geometry, pointerY, grabOffset) {
|
|
838
|
-
const maxThumbOffset = geometry.trackHeight - geometry.thumbHeight;
|
|
839
|
-
const thumbOffset = Math.max(0, Math.min(maxThumbOffset, pointerY - geometry.trackTop - grabOffset));
|
|
840
|
-
const scrollTop = maxThumbOffset === 0 ? 0 : Math.round((thumbOffset / maxThumbOffset) * geometry.maxScrollTop);
|
|
841
|
-
scrollView.scrollTo(scrollTop);
|
|
842
|
-
}
|
|
843
604
|
handleScrollbarMouseEvent(event) {
|
|
844
605
|
if (this.scrollbarDrag) {
|
|
845
606
|
if (event.release) {
|
|
@@ -851,7 +612,10 @@ export class TuiAltScreen extends TuiBase {
|
|
|
851
612
|
: undefined;
|
|
852
613
|
const geometry = box ? getScrollbarGeometry(box) : undefined;
|
|
853
614
|
if (geometry) {
|
|
854
|
-
|
|
615
|
+
const maxThumbOffset = geometry.trackHeight - geometry.thumbHeight;
|
|
616
|
+
const thumbOffset = Math.max(0, Math.min(maxThumbOffset, event.y - geometry.trackTop - this.scrollbarDrag.grabOffset));
|
|
617
|
+
const scrollTop = maxThumbOffset === 0 ? 0 : Math.round((thumbOffset / maxThumbOffset) * geometry.maxScrollTop);
|
|
618
|
+
this.scrollbarDrag.scrollView.scrollTo(scrollTop);
|
|
855
619
|
}
|
|
856
620
|
return true;
|
|
857
621
|
}
|
|
@@ -870,13 +634,9 @@ export class TuiAltScreen extends TuiBase {
|
|
|
870
634
|
this.pressedUrl = undefined;
|
|
871
635
|
this.selectionDragged = false;
|
|
872
636
|
this.setScrollbarHover(target.scrollView);
|
|
873
|
-
const onThumb = event.y >= target.geometry.thumbTop && event.y < target.geometry.thumbTop + target.geometry.thumbHeight;
|
|
874
|
-
const grabOffset = onThumb ? event.y - target.geometry.thumbTop : Math.floor(target.geometry.thumbHeight / 2);
|
|
875
|
-
if (!onThumb)
|
|
876
|
-
this.scrollScrollbarToPointer(target.scrollView, target.geometry, event.y, grabOffset);
|
|
877
637
|
this.scrollbarDrag = {
|
|
878
638
|
scrollView: target.scrollView,
|
|
879
|
-
grabOffset,
|
|
639
|
+
grabOffset: event.y - target.geometry.thumbTop,
|
|
880
640
|
};
|
|
881
641
|
return true;
|
|
882
642
|
}
|
|
@@ -1061,11 +821,12 @@ export class TuiAltScreen extends TuiBase {
|
|
|
1061
821
|
if (!this.selectionAnchor)
|
|
1062
822
|
return;
|
|
1063
823
|
this.updateSelectionFocus(point);
|
|
1064
|
-
const
|
|
824
|
+
const clickedUrl = !this.selectionDragged &&
|
|
1065
825
|
this.selectionAnchor.scrollView === point.scrollView &&
|
|
1066
826
|
this.selectionAnchor.row === point.row &&
|
|
1067
|
-
this.selectionAnchor.col === point.col
|
|
1068
|
-
|
|
827
|
+
this.selectionAnchor.col === point.col
|
|
828
|
+
? this.pressedUrl
|
|
829
|
+
: undefined;
|
|
1069
830
|
this.pressedUrl = undefined;
|
|
1070
831
|
if (clickedUrl && this.openUrl) {
|
|
1071
832
|
this.selectionAnchor = undefined;
|
|
@@ -1079,20 +840,6 @@ export class TuiAltScreen extends TuiBase {
|
|
|
1079
840
|
this.requestRender();
|
|
1080
841
|
return;
|
|
1081
842
|
}
|
|
1082
|
-
if (isClick) {
|
|
1083
|
-
const clickEvent = this.createMouseEvent("click", event.button, event.x, event.y, {
|
|
1084
|
-
clickCount: this.lastClick?.count ?? 1,
|
|
1085
|
-
});
|
|
1086
|
-
const overlay = this.dispatchMouseToOverlay(clickEvent);
|
|
1087
|
-
const result = overlay.result ?? (overlay.hit ? undefined : this.dispatchMouseToLayout(clickEvent));
|
|
1088
|
-
if (result) {
|
|
1089
|
-
const render = this.applyMouseDispatchResult(clickEvent, result);
|
|
1090
|
-
this.clearTextSelection();
|
|
1091
|
-
if (render)
|
|
1092
|
-
this.requestRender();
|
|
1093
|
-
return;
|
|
1094
|
-
}
|
|
1095
|
-
}
|
|
1096
843
|
if (this.copyOnSelect)
|
|
1097
844
|
void this.copySelectionToClipboard();
|
|
1098
845
|
this.requestRender();
|
|
@@ -1234,24 +981,8 @@ export class TuiAltScreen extends TuiBase {
|
|
|
1234
981
|
const maxRow = Math.min(screen.length, box.rect.y + box.rect.height, box.clip.y + box.clip.height);
|
|
1235
982
|
const minColumn = Math.max(0, box.rect.x, box.clip.x);
|
|
1236
983
|
const maxColumn = Math.min(this.terminal.columns, box.rect.x + box.rect.width, box.clip.x + box.clip.width, scrollbarColumn ?? Number.POSITIVE_INFINITY);
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
let low = 0;
|
|
1240
|
-
let high = search.matches.length;
|
|
1241
|
-
while (low < high) {
|
|
1242
|
-
const middle = low + Math.floor((high - low) / 2);
|
|
1243
|
-
const match = search.matches[middle];
|
|
1244
|
-
const lastRow = match.segments[match.segments.length - 1]?.row ?? -1;
|
|
1245
|
-
if (lastRow < minContentRow)
|
|
1246
|
-
low = middle + 1;
|
|
1247
|
-
else
|
|
1248
|
-
high = middle;
|
|
1249
|
-
}
|
|
1250
|
-
for (let matchIndex = low; matchIndex < search.matches.length; matchIndex++) {
|
|
1251
|
-
const match = search.matches[matchIndex];
|
|
1252
|
-
if ((match.segments[0]?.row ?? 0) > maxContentRow)
|
|
1253
|
-
break;
|
|
1254
|
-
for (const segment of match.segments) {
|
|
984
|
+
for (let matchIndex = 0; matchIndex < search.matches.length; matchIndex++) {
|
|
985
|
+
for (const segment of search.matches[matchIndex].segments) {
|
|
1255
986
|
const row = box.rect.y + segment.row - scrollView.scrollTop;
|
|
1256
987
|
if (row < minRow || row >= maxRow)
|
|
1257
988
|
continue;
|
|
@@ -1354,30 +1085,6 @@ export class TuiAltScreen extends TuiBase {
|
|
|
1354
1085
|
isMouseSequence(data) {
|
|
1355
1086
|
return /^\x1b\[<\d+;\d+;\d+[Mm]$/.test(data) || (data.length === 6 && data.startsWith("\x1b[M"));
|
|
1356
1087
|
}
|
|
1357
|
-
compositeScrollToEndIndicator(screen, layout, width) {
|
|
1358
|
-
this.scrollToEndIndicatorRect = undefined;
|
|
1359
|
-
const scrollView = layout.primaryScrollView ?? this.implicitScrollView;
|
|
1360
|
-
if (!this.scrollToEndIndicator || !scrollView.followEnd || scrollView.isFollowingEnd)
|
|
1361
|
-
return screen;
|
|
1362
|
-
const box = getScrollViewBox(layout, scrollView);
|
|
1363
|
-
const clip = box?.clip;
|
|
1364
|
-
if (!clip || clip.width <= 0 || clip.height <= 0)
|
|
1365
|
-
return screen;
|
|
1366
|
-
const row = clip.y + clip.height - 1;
|
|
1367
|
-
if (row >= screen.length || isImageLine(screen[row] ?? ""))
|
|
1368
|
-
return screen;
|
|
1369
|
-
const scrollbarColumn = box ? getScrollbarGeometry(box)?.column : undefined;
|
|
1370
|
-
const availableWidth = Math.max(0, (scrollbarColumn ?? clip.x + clip.width) - clip.x);
|
|
1371
|
-
const text = truncateToWidth(this.scrollToEndIndicator(), availableWidth, "");
|
|
1372
|
-
const textWidth = visibleWidth(text);
|
|
1373
|
-
if (textWidth === 0)
|
|
1374
|
-
return screen;
|
|
1375
|
-
const column = clip.x + Math.floor((availableWidth - textWidth) / 2);
|
|
1376
|
-
const result = [...screen];
|
|
1377
|
-
result[row] = compositeTuiLine(result[row] ?? "", text, column, textWidth, width);
|
|
1378
|
-
this.scrollToEndIndicatorRect = { row, column, width: textWidth };
|
|
1379
|
-
return result;
|
|
1380
|
-
}
|
|
1381
1088
|
compositeFlashes(screen, width, height) {
|
|
1382
1089
|
const flashLines = this.flashes.render(width).slice(-height);
|
|
1383
1090
|
if (flashLines.length === 0)
|
|
@@ -1406,7 +1113,6 @@ export class TuiAltScreen extends TuiBase {
|
|
|
1406
1113
|
}
|
|
1407
1114
|
let screen = nextLayout.lines.map((line) => line.replace(OSC133_ZONE_PREFIX, ""));
|
|
1408
1115
|
screen = this.applySearchHighlights(screen, nextLayout);
|
|
1409
|
-
screen = this.compositeScrollToEndIndicator(screen, nextLayout, width);
|
|
1410
1116
|
screen = this.compositeOverlays(screen, width, height);
|
|
1411
1117
|
if (screen.length > height)
|
|
1412
1118
|
screen = screen.slice(screen.length - height);
|