restty 0.1.16 → 0.1.17

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.
Files changed (43) hide show
  1. package/dist/app/atlas-builder.d.ts +38 -0
  2. package/dist/app/font-sources.d.ts +2 -0
  3. package/dist/app/index.js +234 -90
  4. package/dist/app/pane-app-manager.d.ts +43 -0
  5. package/dist/app/panes-context-menu.d.ts +10 -0
  6. package/dist/app/panes-styles.d.ts +5 -0
  7. package/dist/app/panes-types.d.ts +89 -0
  8. package/dist/app/panes.d.ts +10 -0
  9. package/dist/app/restty.d.ts +20 -0
  10. package/dist/app/session.d.ts +6 -0
  11. package/dist/app/types.d.ts +123 -0
  12. package/dist/fonts/index.js +2 -1
  13. package/dist/fonts/manager.d.ts +25 -0
  14. package/dist/fonts/nerd-constraints.d.ts +4 -0
  15. package/dist/fonts/nerd-ranges.d.ts +2 -0
  16. package/dist/fonts/types.d.ts +51 -0
  17. package/dist/grid/grid.d.ts +22 -0
  18. package/dist/grid/types.d.ts +32 -0
  19. package/dist/ime/ime.d.ts +13 -0
  20. package/dist/ime/types.d.ts +8 -0
  21. package/dist/index.js +234 -90
  22. package/dist/input/ansi.d.ts +3 -0
  23. package/dist/input/index.js +5 -1
  24. package/dist/input/mouse.d.ts +10 -0
  25. package/dist/input/output.d.ts +11 -0
  26. package/dist/input/types.d.ts +7 -0
  27. package/dist/internal.js +234 -90
  28. package/dist/pty/kitty-media.d.ts +3 -0
  29. package/dist/pty/pty.d.ts +11 -0
  30. package/dist/pty/types.d.ts +49 -0
  31. package/dist/renderer/box-drawing-map.d.ts +4 -0
  32. package/dist/renderer/index.js +145 -42
  33. package/dist/renderer/shaders.d.ts +7 -0
  34. package/dist/renderer/shapes.d.ts +43 -0
  35. package/dist/renderer/types.d.ts +43 -0
  36. package/dist/renderer/webgpu.d.ts +11 -0
  37. package/dist/selection/selection.d.ts +24 -0
  38. package/dist/selection/types.d.ts +13 -0
  39. package/dist/theme/catalog.d.ts +5 -0
  40. package/dist/theme/ghostty.d.ts +18 -0
  41. package/dist/wasm/embedded.d.ts +1 -0
  42. package/dist/wasm/runtime.d.ts +29 -0
  43. package/package.json +1 -1
@@ -1,9 +1,27 @@
1
+ /**
2
+ * Metadata for constrained glyph rendering.
3
+ * - cp: Unicode code point
4
+ * - constraintWidth: target cell width constraint
5
+ * - variable: whether glyph can render at multiple widths
6
+ * - widths: set of all required cell widths for this glyph
7
+ */
1
8
  export type GlyphConstraintMeta = {
2
9
  cp: number;
3
10
  constraintWidth: number;
4
11
  variable?: boolean;
5
12
  widths?: Set<number>;
6
13
  };
14
+ /**
15
+ * Context for constraint-based atlas building, primarily for Nerd Fonts symbol alignment.
16
+ * - cellW: cell width in pixels
17
+ * - cellH: cell height in pixels
18
+ * - yPad: vertical padding
19
+ * - baselineOffset: baseline offset adjustment
20
+ * - baselineAdjust: additional baseline adjustment
21
+ * - fontScale: scale factor for font rendering
22
+ * - nerdMetrics: Nerd Fonts specific layout metrics
23
+ * - fontEntry: reference to font entry object
24
+ */
7
25
  export type AtlasConstraintContext = {
8
26
  cellW: number;
9
27
  cellH: number;
@@ -60,6 +78,17 @@ type BuildAtlasDeps = {
60
78
  atlasScale: number;
61
79
  }) => boolean;
62
80
  };
81
+ /**
82
+ * Parameters for font atlas building.
83
+ * - entry: font entry containing the font object and cached atlas
84
+ * - neededGlyphIds: set of glyph IDs required in the atlas
85
+ * - glyphMeta: optional constraint metadata for symbol font glyphs
86
+ * - fontSizePx: base font size in pixels
87
+ * - atlasScale: scaling factor for high-DPI rendering
88
+ * - fontIndex: index in the font fallback chain (0 = primary font)
89
+ * - constraintContext: optional constraint context for symbol alignment
90
+ * - deps: external dependencies for atlas building
91
+ */
63
92
  export type BuildFontAtlasParams = {
64
93
  entry: any;
65
94
  neededGlyphIds: Set<number>;
@@ -70,6 +99,14 @@ export type BuildFontAtlasParams = {
70
99
  constraintContext?: AtlasConstraintContext | null;
71
100
  deps: BuildAtlasDeps;
72
101
  };
102
+ /**
103
+ * Result from font atlas building.
104
+ * - rebuilt: true if a new atlas was generated
105
+ * - atlas: the atlas object containing glyph metrics and bitmap
106
+ * - rgba: RGBA pixel data ready for WebGL upload
107
+ * - colorGlyphs: set of glyph IDs that are color emoji
108
+ * - preferNearest: whether to use nearest-neighbor texture filtering
109
+ */
73
110
  export type BuildFontAtlasResult = {
74
111
  rebuilt: boolean;
75
112
  atlas: any | null;
@@ -77,5 +114,6 @@ export type BuildFontAtlasResult = {
77
114
  colorGlyphs?: Set<number>;
78
115
  preferNearest: boolean;
79
116
  };
117
+ /** Builds or reuses a font atlas, detecting when rebuild is needed based on glyph requirements, size changes, or constraint updates. */
80
118
  export declare function buildFontAtlasIfNeeded(params: BuildFontAtlasParams): BuildFontAtlasResult;
81
119
  export {};
@@ -1,3 +1,5 @@
1
1
  import type { ResttyFontPreset, ResttyFontSource } from "./types";
2
+ /** Default font fallback chain with JetBrains Mono, Nerd Fonts symbols, Noto symbols, color emoji, black emoji, and CJK support. */
2
3
  export declare const DEFAULT_FONT_SOURCES: ResttyFontSource[];
4
+ /** Validates user-provided font sources or returns defaults based on preset (none returns empty array, otherwise default CDN fonts). */
3
5
  export declare function normalizeFontSources(sources: ResttyFontSource[] | undefined, preset: ResttyFontPreset | undefined): ResttyFontSource[];
package/dist/app/index.js CHANGED
@@ -148,11 +148,14 @@ function isPowerline(cp) {
148
148
  function isBraille(cp) {
149
149
  return cp >= 10240 && cp <= 10495;
150
150
  }
151
+ function isTransportControlSymbol(cp) {
152
+ return cp >= 9193 && cp <= 9210;
153
+ }
151
154
  function isGraphicsElement(cp) {
152
155
  return isBoxDrawing(cp) || isBlockElement(cp) || isLegacyComputing(cp) || isPowerline(cp);
153
156
  }
154
157
  function isSymbolCp(cp) {
155
- return isPrivateUse(cp) || isGraphicsElement(cp);
158
+ return isPrivateUse(cp) || isGraphicsElement(cp) || isTransportControlSymbol(cp);
156
159
  }
157
160
  function applyAlpha(color, alpha) {
158
161
  return [color[0], color[1], color[2], color[3] * alpha];
@@ -303,8 +306,7 @@ function drawBlockElement(cp, x, y, cellW, cellH, color, out) {
303
306
  function drawBoxDrawing(cp, x, y, cellW, cellH, color, out) {
304
307
  const spec = BOX_LINE_MAP.get(cp);
305
308
  if (!spec) {
306
- const minDim2 = Math.min(cellW, cellH);
307
- const light2 = Math.max(1, Math.round(minDim2 * 0.08));
309
+ const light2 = Math.max(1, Math.round(cellH * 0.08));
308
310
  const heavy2 = Math.max(light2 + 1, Math.round(light2 * 1.8));
309
311
  const dashedH = (count, thickness) => {
310
312
  const gap2 = Math.max(1, Math.round(thickness));
@@ -338,39 +340,146 @@ function drawBoxDrawing(cp, x, y, cellW, cellH, color, out) {
338
340
  pushRectBox(out, px - thickness * 0.5, py - thickness * 0.5, thickness, thickness, color);
339
341
  }
340
342
  };
341
- const drawArc = (corner) => {
342
- const thickness = light2;
343
- const halfThick = thickness * 0.5;
344
- const cx2 = x + cellW * 0.5;
345
- const cy2 = y + cellH * 0.5;
346
- const r = Math.min(cellW, cellH) * 0.5;
347
- const steps = Math.max(24, Math.round(r * 4));
343
+ const drawRoundedCorner = (cornerCp) => {
344
+ const thickness = Math.max(1, Math.round(light2));
345
+ const half = thickness * 0.5;
348
346
  const s = 0.25;
349
- const cubicBezier = (x0, y0, cx1, cy1, cx22, cy22, x1, y1) => {
350
- for (let i = 0;i <= steps; i++) {
351
- const t = i / steps;
352
- const mt = 1 - t;
353
- const px = mt * mt * mt * x0 + 3 * mt * mt * t * cx1 + 3 * mt * t * t * cx22 + t * t * t * x1;
354
- const py = mt * mt * mt * y0 + 3 * mt * mt * t * cy1 + 3 * mt * t * t * cy22 + t * t * t * y1;
355
- pushRectBox(out, px - halfThick, py - halfThick, thickness, thickness, color);
347
+ const cx2 = x + Math.floor((cellW - thickness) * 0.5) + half;
348
+ const cy2 = y + Math.floor((cellH - thickness) * 0.5) + half;
349
+ const r = Math.min(cellW, cellH) * 0.5;
350
+ const p0 = { x: cx2, y };
351
+ const p1 = { x: cx2, y: cy2 - r };
352
+ const c1 = { x: cx2, y: cy2 - s * r };
353
+ const c2 = { x: cx2, y: cy2 };
354
+ const p2 = { x: cx2, y: cy2 };
355
+ const p3 = { x: cx2, y: cy2 };
356
+ let p4 = { x: x + cellW, y: cy2 };
357
+ switch (cornerCp) {
358
+ case 9581:
359
+ p0.y = y + cellH;
360
+ p1.y = cy2 + r;
361
+ c1.y = cy2 + s * r;
362
+ c2.x = cx2 + s * r;
363
+ p2.x = cx2 + r;
364
+ p3.x = cx2 + r;
365
+ p4 = { x: x + cellW, y: cy2 };
366
+ break;
367
+ case 9582:
368
+ p0.y = y + cellH;
369
+ p1.y = cy2 + r;
370
+ c1.y = cy2 + s * r;
371
+ c2.x = cx2 - s * r;
372
+ p2.x = cx2 - r;
373
+ p3.x = cx2 - r;
374
+ p4 = { x, y: cy2 };
375
+ break;
376
+ case 9583:
377
+ c2.x = cx2 - s * r;
378
+ p2.x = cx2 - r;
379
+ p3.x = cx2 - r;
380
+ p4 = { x, y: cy2 };
381
+ break;
382
+ case 9584:
383
+ c2.x = cx2 + s * r;
384
+ p2.x = cx2 + r;
385
+ p3.x = cx2 + r;
386
+ p4 = { x: x + cellW, y: cy2 };
387
+ break;
388
+ }
389
+ const segments = [];
390
+ const addSegment = (a, b) => {
391
+ const dx = b.x - a.x;
392
+ const dy = b.y - a.y;
393
+ const len = Math.hypot(dx, dy);
394
+ if (len <= 0.000001)
395
+ return;
396
+ const ux = dx / len;
397
+ const uy = dy / len;
398
+ segments.push({ ax: a.x, ay: a.y, ux, uy, nx: -uy, ny: ux, len });
399
+ };
400
+ const cubicPoint = (a, b, c, d, t) => {
401
+ const mt = 1 - t;
402
+ const mt2 = mt * mt;
403
+ const t2 = t * t;
404
+ return {
405
+ x: mt2 * mt * a.x + 3 * mt2 * t * b.x + 3 * mt * t2 * c.x + t2 * t * d.x,
406
+ y: mt2 * mt * a.y + 3 * mt2 * t * b.y + 3 * mt * t2 * c.y + t2 * t * d.y
407
+ };
408
+ };
409
+ const steps = Math.max(10, Math.round(Math.max(cellW, cellH) * 1.5));
410
+ const curvePoints = [];
411
+ for (let i = 0;i <= steps; i += 1) {
412
+ const t = i / steps;
413
+ curvePoints.push(cubicPoint(p1, c1, c2, p2, t));
414
+ }
415
+ addSegment(p0, p1);
416
+ for (let i = 1;i < curvePoints.length; i += 1) {
417
+ addSegment(curvePoints[i - 1], curvePoints[i]);
418
+ }
419
+ addSegment(p3, p4);
420
+ const minX = Math.max(Math.floor(x), Math.floor(Math.min(p0.x, p4.x, cx2 - r) - half - 1));
421
+ const maxX = Math.min(Math.ceil(x + cellW) - 1, Math.ceil(Math.max(p0.x, p4.x, cx2 + r) + half + 1));
422
+ const minY = Math.max(Math.floor(y), Math.floor(Math.min(p0.y, p4.y, cy2 - r) - half - 1));
423
+ const maxY = Math.min(Math.ceil(y + cellH) - 1, Math.ceil(Math.max(p0.y, p4.y, cy2 + r) + half + 1));
424
+ if (maxX < minX || maxY < minY)
425
+ return;
426
+ const antiAlias = Math.min(cellW, cellH) >= 22;
427
+ const sampleOffsets = antiAlias ? [
428
+ [0.25, 0.25],
429
+ [0.75, 0.25],
430
+ [0.25, 0.75],
431
+ [0.75, 0.75]
432
+ ] : [[0.5, 0.5]];
433
+ const sampleInsideStroke = (sx, sy) => {
434
+ for (const seg of segments) {
435
+ const rx = sx - seg.ax;
436
+ const ry = sy - seg.ay;
437
+ const along = rx * seg.ux + ry * seg.uy;
438
+ if (along < 0 || along > seg.len)
439
+ continue;
440
+ const perp = Math.abs(rx * seg.nx + ry * seg.ny);
441
+ if (perp <= half + 0.000001)
442
+ return true;
356
443
  }
444
+ return false;
357
445
  };
358
- if (corner === "br") {
359
- pushRectBox(out, cx2 - halfThick, cy2 + r, thickness, y + cellH - (cy2 + r), color);
360
- cubicBezier(cx2, cy2 + r, cx2, cy2 + s * r, cx2 + s * r, cy2, cx2 + r, cy2);
361
- pushRectBox(out, cx2 + r, cy2 - halfThick, x + cellW - (cx2 + r), thickness, color);
362
- } else if (corner === "bl") {
363
- pushRectBox(out, cx2 - halfThick, cy2 + r, thickness, y + cellH - (cy2 + r), color);
364
- cubicBezier(cx2, cy2 + r, cx2, cy2 + s * r, cx2 - s * r, cy2, cx2 - r, cy2);
365
- pushRectBox(out, x, cy2 - halfThick, cx2 - r - x, thickness, color);
366
- } else if (corner === "tl") {
367
- pushRectBox(out, cx2 - halfThick, y, thickness, cy2 - r - y, color);
368
- cubicBezier(cx2, cy2 - r, cx2, cy2 - s * r, cx2 - s * r, cy2, cx2 - r, cy2);
369
- pushRectBox(out, x, cy2 - halfThick, cx2 - r - x, thickness, color);
370
- } else {
371
- pushRectBox(out, cx2 - halfThick, y, thickness, cy2 - r - y, color);
372
- cubicBezier(cx2, cy2 - r, cx2, cy2 - s * r, cx2 + s * r, cy2, cx2 + r, cy2);
373
- pushRectBox(out, cx2 + r, cy2 - halfThick, x + cellW - (cx2 + r), thickness, color);
446
+ for (let py = minY;py <= maxY; py += 1) {
447
+ let runX = -1;
448
+ let runCoverage = 0;
449
+ for (let px = minX;px <= maxX; px += 1) {
450
+ let coverage = 0;
451
+ for (const [ox, oy] of sampleOffsets) {
452
+ if (sampleInsideStroke(px + ox, py + oy))
453
+ coverage += 1;
454
+ }
455
+ if (coverage > 0 && runX < 0) {
456
+ runX = px;
457
+ runCoverage = coverage;
458
+ continue;
459
+ }
460
+ if (coverage > 0 && coverage === runCoverage)
461
+ continue;
462
+ if (runX >= 0) {
463
+ const alphaColor = [
464
+ color[0],
465
+ color[1],
466
+ color[2],
467
+ color[3] * (runCoverage / sampleOffsets.length)
468
+ ];
469
+ pushRectBox(out, runX, py, px - runX, 1, alphaColor);
470
+ runX = coverage > 0 ? px : -1;
471
+ runCoverage = coverage;
472
+ }
473
+ }
474
+ if (runX >= 0) {
475
+ const alphaColor = [
476
+ color[0],
477
+ color[1],
478
+ color[2],
479
+ color[3] * (runCoverage / sampleOffsets.length)
480
+ ];
481
+ pushRectBox(out, runX, py, maxX - runX + 1, 1, alphaColor);
482
+ }
374
483
  }
375
484
  };
376
485
  switch (cp) {
@@ -411,16 +520,10 @@ function drawBoxDrawing(cp, x, y, cellW, cellH, color, out) {
411
520
  dashedV(2, heavy2);
412
521
  return true;
413
522
  case 9581:
414
- drawArc("br");
415
- return true;
416
523
  case 9582:
417
- drawArc("bl");
418
- return true;
419
524
  case 9583:
420
- drawArc("tl");
421
- return true;
422
525
  case 9584:
423
- drawArc("tr");
526
+ drawRoundedCorner(cp);
424
527
  return true;
425
528
  case 9585:
426
529
  drawDiagonal("ur_ll");
@@ -437,8 +540,7 @@ function drawBoxDrawing(cp, x, y, cellW, cellH, color, out) {
437
540
  }
438
541
  }
439
542
  const [up, right, down, left] = spec;
440
- const minDim = Math.min(cellW, cellH);
441
- const light = Math.max(1, Math.round(minDim * 0.08));
543
+ const light = Math.max(1, Math.round(cellH * 0.08));
442
544
  const heavy = Math.max(light + 1, Math.round(light * 1.8));
443
545
  const gap = Math.max(1, Math.round(light));
444
546
  const cx = x + cellW * 0.5;
@@ -7642,6 +7744,7 @@ var KITTY_KEYPAD_BY_CODE = {
7642
7744
  NumpadEnter: { code: 57414, final: "u" },
7643
7745
  NumpadEqual: { code: 57415, final: "u" }
7644
7746
  };
7747
+ var KITTY_LOCK_KEYS = new Set(["CapsLock", "NumLock", "ScrollLock"]);
7645
7748
  var UN_SHIFTED_CODE_BY_CODE = {
7646
7749
  Backquote: "`",
7647
7750
  Minus: "-",
@@ -7913,6 +8016,7 @@ function encodeKittyKeyEvent(event, kittyFlags) {
7913
8016
  const disambiguate = (kittyFlags & KITTY_FLAG_DISAMBIGUATE) !== 0;
7914
8017
  const key = event.key ?? "";
7915
8018
  const special = KITTY_KEYPAD_BY_CODE[event.code || ""] ?? KITTY_SPECIAL_KEYS[key];
8019
+ const isLockKey = KITTY_LOCK_KEYS.has(key);
7916
8020
  const hasText = key.length === 1;
7917
8021
  const hasTextModifiers = event.altKey || event.ctrlKey || event.metaKey;
7918
8022
  const eventType = kittyEventType(event, reportEvents);
@@ -7925,6 +8029,8 @@ function encodeKittyKeyEvent(event, kittyFlags) {
7925
8029
  return "";
7926
8030
  if (isRelease && isLegacyControlKey)
7927
8031
  return "";
8032
+ if (isLockKey && !reportAll)
8033
+ return "";
7928
8034
  if (isLegacyTextKey) {
7929
8035
  return key;
7930
8036
  }
@@ -8505,7 +8611,7 @@ class OutputFilter {
8505
8611
  const altMode = parsePrivateModeSeq(seq);
8506
8612
  if (altMode) {
8507
8613
  const { enabled, codes } = altMode;
8508
- if (codes.some((code) => code === 47 || code === 1047 || code === 1048 || code === 1049)) {
8614
+ if (codes.some((code) => code === 47 || code === 1047 || code === 1049)) {
8509
8615
  this.altScreen = enabled;
8510
8616
  }
8511
8617
  }
@@ -8823,8 +8929,9 @@ function isSymbolCp2(cp) {
8823
8929
  const isBlockElement2 = cp >= 9600 && cp <= 9631;
8824
8930
  const isLegacyComputing2 = cp >= 129792 && cp <= 130047 || cp >= 117760 && cp <= 118463;
8825
8931
  const isPowerline2 = cp >= 57520 && cp <= 57559;
8932
+ const isTransportControl = cp >= 9193 && cp <= 9210;
8826
8933
  const isGraphicsElement2 = isBoxDrawing2 || isBlockElement2 || isLegacyComputing2 || isPowerline2;
8827
- return isPrivateUse2 || isGraphicsElement2;
8934
+ return isPrivateUse2 || isGraphicsElement2 || isTransportControl;
8828
8935
  }
8829
8936
  function pickFontIndexForText(state, text, expectedSpan, shapeClusterWithFont) {
8830
8937
  if (!state.fonts.length)
@@ -50433,6 +50540,10 @@ function createResttyApp(options) {
50433
50540
  const BACKGROUND_RENDER_FPS = 15;
50434
50541
  const GLYPH_SHAPE_CACHE_LIMIT = 12000;
50435
50542
  const FONT_PICK_CACHE_LIMIT = 16000;
50543
+ const OVERLAY_SCROLLBAR_WIDTH_CSS_PX = 7;
50544
+ const OVERLAY_SCROLLBAR_MARGIN_CSS_PX = 4;
50545
+ const OVERLAY_SCROLLBAR_INSET_Y_CSS_PX = 2;
50546
+ const OVERLAY_SCROLLBAR_MIN_THUMB_CSS_PX = 28;
50436
50547
  let paused = false;
50437
50548
  let backend = "none";
50438
50549
  let preferredRenderer = options.renderer ?? "auto";
@@ -50710,30 +50821,32 @@ function createResttyApp(options) {
50710
50821
  y: (event.clientY - rect.top) * scaleY
50711
50822
  };
50712
50823
  }
50713
- function computeOverlayScrollbarLayout(rows, cellW, cellH, total, offset, len) {
50824
+ function computeOverlayScrollbarLayout(total, offset, len) {
50714
50825
  if (!(total > len && len > 0))
50715
50826
  return null;
50716
- const trackHeight = rows * cellH;
50717
- const width = Math.max(10, Math.round(cellW * 0.9));
50718
- const margin = Math.max(6, Math.round(width * 0.5));
50719
- const insetY = Math.max(2, Math.round(cellH * 0.2));
50720
- const trackX = canvas.width - margin - width;
50827
+ const dpr = Math.max(1, currentDpr || 1);
50828
+ const width = Math.max(1, Math.round(OVERLAY_SCROLLBAR_WIDTH_CSS_PX * dpr));
50829
+ const margin = Math.max(1, Math.round(OVERLAY_SCROLLBAR_MARGIN_CSS_PX * dpr));
50830
+ const insetY = Math.max(0, Math.round(OVERLAY_SCROLLBAR_INSET_Y_CSS_PX * dpr));
50831
+ const trackX = Math.max(0, canvas.width - margin - width);
50721
50832
  const trackY = insetY;
50722
- const trackH = Math.max(width, trackHeight - insetY * 2);
50833
+ const trackH = Math.max(width, canvas.height - insetY * 2);
50723
50834
  const denom = Math.max(1, total - len);
50724
- const thumbH = Math.max(width, Math.round(trackH * (len / total)));
50835
+ const dynamicThumbH = Math.round(trackH * (len / total));
50836
+ const minThumbH = Math.max(width, Math.round(OVERLAY_SCROLLBAR_MIN_THUMB_CSS_PX * dpr));
50837
+ const thumbH = Math.min(trackH, Math.max(minThumbH, dynamicThumbH));
50725
50838
  const thumbY = trackY + Math.round(offset / denom * (trackH - thumbH));
50726
50839
  return { total, offset, len, denom, width, trackX, trackY, trackH, thumbY, thumbH };
50727
50840
  }
50728
50841
  function getOverlayScrollbarLayout() {
50729
50842
  if (!showOverlayScrollbar || !wasmExports?.restty_scrollbar_total || !wasmHandle)
50730
50843
  return null;
50731
- if (!gridState.rows || !gridState.cellW || !gridState.cellH)
50844
+ if (!gridState.rows)
50732
50845
  return null;
50733
50846
  const total = wasmExports.restty_scrollbar_total(wasmHandle) || 0;
50734
50847
  const offset = wasmExports.restty_scrollbar_offset ? wasmExports.restty_scrollbar_offset(wasmHandle) : 0;
50735
50848
  const len = wasmExports.restty_scrollbar_len ? wasmExports.restty_scrollbar_len(wasmHandle) : gridState.rows;
50736
- return computeOverlayScrollbarLayout(gridState.rows, gridState.cellW, gridState.cellH, total, offset, len);
50849
+ return computeOverlayScrollbarLayout(total, offset, len);
50737
50850
  }
50738
50851
  function isPointInScrollbarHitArea(layout, x3, y) {
50739
50852
  const hitPadX = Math.max(3, Math.round(layout.width * 0.35));
@@ -50773,10 +50886,10 @@ function createResttyApp(options) {
50773
50886
  pushRectBox(out, x02 + inset, y02 + height - 1 - row, rowW, 1, color);
50774
50887
  }
50775
50888
  }
50776
- function appendOverlayScrollbar(overlayData, rows, cellW, cellH, total, offset, len) {
50889
+ function appendOverlayScrollbar(overlayData, total, offset, len) {
50777
50890
  if (!showOverlayScrollbar)
50778
50891
  return;
50779
- const layout = computeOverlayScrollbarLayout(rows, cellW, cellH, total, offset, len);
50892
+ const layout = computeOverlayScrollbarLayout(total, offset, len);
50780
50893
  if (!layout)
50781
50894
  return;
50782
50895
  const since = performance.now() - scrollbarState.lastInputAt;
@@ -51568,6 +51681,9 @@ function createResttyApp(options) {
51568
51681
  function sendKeyInput(text, source = "key") {
51569
51682
  if (!text)
51570
51683
  return;
51684
+ if (source !== "program" && (selectionState.active || selectionState.dragging)) {
51685
+ clearSelection();
51686
+ }
51571
51687
  if (ptyTransport.isConnected()) {
51572
51688
  const payload = inputHandler.mapKeyForPty(text);
51573
51689
  ptyTransport.sendInput(payload);
@@ -51604,6 +51720,14 @@ function createResttyApp(options) {
51604
51720
  function bindCanvasEvents() {
51605
51721
  if (!attachCanvasEvents)
51606
51722
  return;
51723
+ const shouldRoutePointerToAppMouse = (shiftKey) => {
51724
+ if (shiftKey)
51725
+ return false;
51726
+ if (!inputHandler.isMouseActive())
51727
+ return false;
51728
+ return inputHandler.isAltScreen ? inputHandler.isAltScreen() : false;
51729
+ };
51730
+ const shouldPreferLocalPrimarySelection = (event) => !isTouchPointer(event) && event.button === 0 && !event.altKey;
51607
51731
  canvas.style.touchAction = touchSelectionMode === "long-press" || touchSelectionMode === "drag" ? "none" : "pan-y pinch-zoom";
51608
51732
  const onPointerDown = (event) => {
51609
51733
  if (!isTouchPointer(event) && event.button === 0) {
@@ -51623,7 +51747,7 @@ function createResttyApp(options) {
51623
51747
  }
51624
51748
  }
51625
51749
  }
51626
- if (inputHandler.sendMouseEvent("down", event)) {
51750
+ if (shouldRoutePointerToAppMouse(event.shiftKey) && !shouldPreferLocalPrimarySelection(event) && inputHandler.sendMouseEvent("down", event)) {
51627
51751
  event.preventDefault();
51628
51752
  canvas.setPointerCapture?.(event.pointerId);
51629
51753
  return;
@@ -51674,10 +51798,6 @@ function createResttyApp(options) {
51674
51798
  event.preventDefault();
51675
51799
  return;
51676
51800
  }
51677
- if (inputHandler.sendMouseEvent("move", event)) {
51678
- event.preventDefault();
51679
- return;
51680
- }
51681
51801
  if (isTouchPointer(event)) {
51682
51802
  if (touchSelectionState.pendingPointerId === event.pointerId) {
51683
51803
  const dx = event.clientX - touchSelectionState.pendingStartX;
@@ -51715,15 +51835,19 @@ function createResttyApp(options) {
51715
51835
  return;
51716
51836
  }
51717
51837
  const cell = normalizeSelectionCell(positionToCell(event));
51718
- if (!selectionState.dragging) {
51719
- updateLinkHover(cell);
51838
+ if (selectionState.dragging) {
51839
+ event.preventDefault();
51840
+ selectionState.focus = cell;
51841
+ updateLinkHover(null);
51842
+ updateCanvasCursor();
51843
+ needsRender = true;
51720
51844
  return;
51721
51845
  }
51722
- event.preventDefault();
51723
- selectionState.focus = cell;
51724
- updateLinkHover(null);
51725
- updateCanvasCursor();
51726
- needsRender = true;
51846
+ if (shouldRoutePointerToAppMouse(event.shiftKey) && inputHandler.sendMouseEvent("move", event)) {
51847
+ event.preventDefault();
51848
+ return;
51849
+ }
51850
+ updateLinkHover(cell);
51727
51851
  };
51728
51852
  const onPointerUp = (event) => {
51729
51853
  if (scrollbarDragState.pointerId === event.pointerId) {
@@ -51731,10 +51855,6 @@ function createResttyApp(options) {
51731
51855
  event.preventDefault();
51732
51856
  return;
51733
51857
  }
51734
- if (inputHandler.sendMouseEvent("up", event)) {
51735
- event.preventDefault();
51736
- return;
51737
- }
51738
51858
  if (isTouchPointer(event)) {
51739
51859
  if (touchSelectionState.pendingPointerId === event.pointerId) {
51740
51860
  clearPendingTouchSelection();
@@ -51773,6 +51893,10 @@ function createResttyApp(options) {
51773
51893
  needsRender = true;
51774
51894
  }
51775
51895
  } else {
51896
+ if (shouldRoutePointerToAppMouse(event.shiftKey) && !shouldPreferLocalPrimarySelection(event) && inputHandler.sendMouseEvent("up", event)) {
51897
+ event.preventDefault();
51898
+ return;
51899
+ }
51776
51900
  updateLinkHover(cell);
51777
51901
  }
51778
51902
  if (!selectionState.active && event.button === 0 && linkState.hoverUri) {
@@ -51801,9 +51925,7 @@ function createResttyApp(options) {
51801
51925
  }
51802
51926
  };
51803
51927
  const onWheel = (event) => {
51804
- const mouseActive = inputHandler.isMouseActive();
51805
- const altScreen = inputHandler.isAltScreen ? inputHandler.isAltScreen() : false;
51806
- if (mouseActive && altScreen && !event.shiftKey) {
51928
+ if (shouldRoutePointerToAppMouse(event.shiftKey)) {
51807
51929
  if (inputHandler.sendMouseEvent("wheel", event)) {
51808
51930
  event.preventDefault();
51809
51931
  return;
@@ -53224,14 +53346,14 @@ function createResttyApp(options) {
53224
53346
  isFocused = true;
53225
53347
  focusTypingInput();
53226
53348
  if (inputHandler?.isFocusReporting?.()) {
53227
- sendKeyInput("\x1B[I");
53349
+ sendKeyInput("\x1B[I", "program");
53228
53350
  }
53229
53351
  };
53230
53352
  const handleBlur = () => {
53231
53353
  const stillFocused = typeof document !== "undefined" && imeInput ? document.activeElement === imeInput : false;
53232
53354
  isFocused = stillFocused;
53233
53355
  if (!stillFocused && inputHandler?.isFocusReporting?.()) {
53234
- sendKeyInput("\x1B[O");
53356
+ sendKeyInput("\x1B[O", "program");
53235
53357
  }
53236
53358
  };
53237
53359
  const handlePointerFocus = () => {
@@ -54679,11 +54801,20 @@ function createResttyApp(options) {
54679
54801
  let gw = metrics.width * bitmapScale;
54680
54802
  let gh = metrics.height * bitmapScale;
54681
54803
  if (symbolLike && !glyphConstrained) {
54682
- const fitScale = gw > 0 && gh > 0 ? Math.min(1, maxWidth / gw, maxHeight / gh) : 1;
54683
- if (fitScale < 1) {
54684
- bitmapScale *= fitScale;
54685
- gw *= fitScale;
54686
- gh *= fitScale;
54804
+ const scaleToFit = gw > 0 && gh > 0 ? Math.min(maxWidth / gw, maxHeight / gh) : 1;
54805
+ if (scaleToFit < 1) {
54806
+ bitmapScale *= scaleToFit;
54807
+ gw *= scaleToFit;
54808
+ gh *= scaleToFit;
54809
+ } else if (symbolFont && item.shaped.glyphs.length === 1 && gh > 0) {
54810
+ const targetHeight = Math.min(maxHeight, nerdMetrics.iconHeightSingle);
54811
+ const growToHeight = targetHeight > 0 ? targetHeight / gh : 1;
54812
+ const growScale = Math.min(scaleToFit, growToHeight);
54813
+ if (growScale > 1) {
54814
+ bitmapScale *= growScale;
54815
+ gw *= growScale;
54816
+ gh *= growScale;
54817
+ }
54687
54818
  }
54688
54819
  gw = Math.round(gw);
54689
54820
  gh = Math.round(gh);
@@ -54723,7 +54854,9 @@ function createResttyApp(options) {
54723
54854
  }
54724
54855
  }
54725
54856
  if (!glyphConstrained && !constrained && symbolFont && item.shaped.glyphs.length === 1) {
54857
+ const rowY = item.baseY - yPad - baselineOffset;
54726
54858
  x3 = item.x + (maxWidth - gw) * 0.5;
54859
+ y = rowY + (cellH - gh) * 0.5;
54727
54860
  }
54728
54861
  if (!constrained && symbolLike && !symbolFont) {
54729
54862
  const rowY = item.baseY - yPad - baselineOffset;
@@ -54819,7 +54952,7 @@ function createResttyApp(options) {
54819
54952
  scrollbarState.lastOffset = offset;
54820
54953
  scrollbarState.lastLen = len;
54821
54954
  }
54822
- appendOverlayScrollbar(overlayData, rows, cellW, cellH, total, offset, len);
54955
+ appendOverlayScrollbar(overlayData, total, offset, len);
54823
54956
  }
54824
54957
  webgpuUniforms[0] = canvas.width;
54825
54958
  webgpuUniforms[1] = canvas.height;
@@ -55509,7 +55642,7 @@ function createResttyApp(options) {
55509
55642
  scrollbarState.lastOffset = offset;
55510
55643
  scrollbarState.lastLen = len;
55511
55644
  }
55512
- appendOverlayScrollbar(overlayData, rows, cellW, cellH, total, offset, len);
55645
+ appendOverlayScrollbar(overlayData, total, offset, len);
55513
55646
  }
55514
55647
  for (const [fontIndex, neededIds] of neededGlyphIdsByFont.entries()) {
55515
55648
  const fontEntry = fontState.fonts[fontIndex];
@@ -55646,11 +55779,20 @@ function createResttyApp(options) {
55646
55779
  let gw = metrics.width * bitmapScale;
55647
55780
  let gh = metrics.height * bitmapScale;
55648
55781
  if (symbolLike && !glyphConstrained) {
55649
- const fitScale = gw > 0 && gh > 0 ? Math.min(1, maxWidth / gw, maxHeight / gh) : 1;
55650
- if (fitScale < 1) {
55651
- bitmapScale *= fitScale;
55652
- gw *= fitScale;
55653
- gh *= fitScale;
55782
+ const scaleToFit = gw > 0 && gh > 0 ? Math.min(maxWidth / gw, maxHeight / gh) : 1;
55783
+ if (scaleToFit < 1) {
55784
+ bitmapScale *= scaleToFit;
55785
+ gw *= scaleToFit;
55786
+ gh *= scaleToFit;
55787
+ } else if (symbolFont && item.shaped.glyphs.length === 1 && gh > 0) {
55788
+ const targetHeight = Math.min(maxHeight, nerdMetrics.iconHeightSingle);
55789
+ const growToHeight = targetHeight > 0 ? targetHeight / gh : 1;
55790
+ const growScale = Math.min(scaleToFit, growToHeight);
55791
+ if (growScale > 1) {
55792
+ bitmapScale *= growScale;
55793
+ gw *= growScale;
55794
+ gh *= growScale;
55795
+ }
55654
55796
  }
55655
55797
  gw = Math.round(gw);
55656
55798
  gh = Math.round(gh);
@@ -55688,7 +55830,9 @@ function createResttyApp(options) {
55688
55830
  }
55689
55831
  }
55690
55832
  if (!glyphConstrained && !constrained && symbolFont && item.shaped.glyphs.length === 1) {
55833
+ const rowY = item.baseY - yPad - baselineOffset;
55691
55834
  x3 = item.x + (maxWidth - gw) * 0.5;
55835
+ y = rowY + (cellH - gh) * 0.5;
55692
55836
  }
55693
55837
  if (!constrained && symbolLike && !symbolFont) {
55694
55838
  const rowY = item.baseY - yPad - baselineOffset;
@@ -55885,7 +56029,7 @@ function createResttyApp(options) {
55885
56029
  }
55886
56030
  appendLog(`[key] ${JSON.stringify(normalized)}${before}`);
55887
56031
  }
55888
- if (source !== "program" && (selectionState.active || selectionState.dragging)) {
56032
+ if (source === "key" && (selectionState.active || selectionState.dragging)) {
55889
56033
  clearSelection();
55890
56034
  }
55891
56035
  if (source === "pty" && linkState.hoverId)