@alchemy.run/sigil 0.0.0-alpha.1 → 0.0.0-alpha.10
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 +499 -313
- package/THIRD_PARTY_NOTICES.md +70 -23
- package/dist/Text-DV9CuzAT.d.ts +452 -0
- package/dist/ansi.d.ts +217 -0
- package/dist/ansi.js +87 -0
- package/dist/capabilities.d.ts +5 -0
- package/dist/capabilities.js +3 -0
- package/dist/cell-_ZVhbfl0.js +44 -0
- package/dist/color-CkbalRqK.js +2 -0
- package/dist/color-policy-BAC9-TZX.js +592 -0
- package/dist/color-policy-CCxuHIdD.d.ts +22 -0
- package/dist/color-profile-CyeHnG1T.d.ts +97 -0
- package/dist/color-profile-DHhQHY55.js +36 -0
- package/dist/color.d.ts +21 -0
- package/dist/color.js +3 -0
- package/dist/cursor-position-D2LAkRG0.d.ts +7 -0
- package/dist/detect-B3dL4Q11.js +374 -0
- package/dist/detect-Db6GbKOm.d.ts +195 -0
- package/dist/{devtools-QpCMm9JH.mjs → devtools-DbthxoD1.js} +23 -24
- package/dist/env-YVw64yZS.js +9 -0
- package/dist/escapes-CB_6CWOE.d.ts +72 -0
- package/dist/geometry-BxXOzJgo.d.ts +11 -0
- package/dist/index-D48vQhhe.d.ts +21 -0
- package/dist/index-DDVME65c.d.ts +919 -0
- package/dist/index.d.ts +1310 -0
- package/dist/index.js +3209 -0
- package/dist/osc-BFKKSqpg.js +71 -0
- package/dist/osc-Cn0fw77g.d.ts +23 -0
- package/dist/paint-Cx-zC_sX.d.ts +81 -0
- package/dist/query-BNc2B8GD.d.ts +152 -0
- package/dist/router.d.ts +392 -0
- package/dist/router.js +709 -0
- package/dist/sample-Cqw1bjUL.js +445 -0
- package/dist/screen-CgC2WlVM.d.ts +49 -0
- package/dist/screen-CiPytswf.js +342 -0
- package/dist/screen.d.ts +5 -0
- package/dist/screen.js +5 -0
- package/dist/semantic-text-style-DIMzC7xt.js +91 -0
- package/dist/serialize-BTkAZgw1.js +79 -0
- package/dist/session-DDQ5V300.js +723 -0
- package/dist/sgr-BhwaWAJB.js +246 -0
- package/dist/store-C1P5fOUi.d.ts +72 -0
- package/dist/string-width-CijQwpIk.js +69 -0
- package/dist/strip-BvU4toXG.js +6 -0
- package/dist/terminal.d.ts +121 -0
- package/dist/terminal.js +2 -0
- package/dist/tokenize-AjqbvtiT.js +1242 -0
- package/dist/tokenize-Dx1y_l5H.d.ts +57 -0
- package/dist/truncate-D31fhU6i.js +562 -0
- package/dist/use-focus-CcCIdCWA.js +1337 -0
- package/dist/yoga-5jKhYCJC.js +3465 -0
- package/dist/yoga.d.ts +2 -0
- package/dist/yoga.js +2 -0
- package/package.json +70 -24
- package/src/ansi/chalk.ts +138 -0
- package/src/ansi/cursor.ts +46 -0
- package/src/ansi/east-asian-width.ts +259 -0
- package/src/ansi/escapes.ts +120 -0
- package/src/ansi/graphemes.ts +8 -0
- package/src/ansi/hyperlink.ts +44 -0
- package/src/ansi/index.ts +27 -0
- package/src/ansi/osc.ts +77 -0
- package/src/ansi/sgr.ts +234 -0
- package/src/ansi/slice.ts +43 -0
- package/src/ansi/string-width.ts +121 -0
- package/src/ansi/strip.ts +13 -0
- package/src/ansi/tokenize.ts +380 -0
- package/src/ansi/truncate.ts +196 -0
- package/src/ansi/wrap.ts +765 -0
- package/src/ansi-tokenizer.ts +510 -0
- package/src/capabilities/color-policy.ts +34 -0
- package/src/capabilities/detect.ts +608 -0
- package/src/capabilities/index.ts +37 -0
- package/src/capabilities/query.ts +679 -0
- package/src/capabilities/store.ts +394 -0
- package/src/code-excerpt.ts +39 -0
- package/src/color/index.ts +3 -0
- package/src/color/paint.ts +169 -0
- package/src/color/palette.ts +48 -0
- package/src/color/sample.ts +323 -0
- package/src/color.ts +1 -0
- package/src/components/AccessibilityContext.ts +5 -0
- package/src/components/AnimationContext.ts +24 -0
- package/src/components/AnsiText.tsx +42 -0
- package/src/components/App.tsx +878 -0
- package/src/components/AppContext.ts +111 -0
- package/src/components/BackgroundContext.ts +7 -0
- package/src/components/Box.tsx +100 -0
- package/src/components/CursorContext.ts +19 -0
- package/src/components/ErrorBoundary.tsx +39 -0
- package/src/components/ErrorOverview.tsx +135 -0
- package/src/components/FocusContext.ts +30 -0
- package/src/components/Hyperlink.tsx +56 -0
- package/src/components/Newline.tsx +16 -0
- package/src/components/Spacer.tsx +11 -0
- package/src/components/Static.tsx +60 -0
- package/src/components/StderrContext.ts +24 -0
- package/src/components/StdinContext.ts +48 -0
- package/src/components/StdoutContext.ts +26 -0
- package/src/components/TerminalOscContext.ts +25 -0
- package/src/components/Text.tsx +122 -0
- package/src/components/Transform.tsx +38 -0
- package/src/components/VirtualList.tsx +128 -0
- package/src/cursor-position.ts +103 -0
- package/src/devtools.ts +103 -0
- package/src/dom.ts +301 -0
- package/src/env.ts +12 -0
- package/src/get-max-width.ts +11 -0
- package/src/global.d.ts +38 -0
- package/src/glyphs.ts +99 -0
- package/src/hooks/use-animation.ts +142 -0
- package/src/hooks/use-app.ts +8 -0
- package/src/hooks/use-box-metrics.ts +134 -0
- package/src/hooks/use-capabilities.ts +73 -0
- package/src/hooks/use-cursor.ts +33 -0
- package/src/hooks/use-focus-manager.ts +62 -0
- package/src/hooks/use-focus.ts +82 -0
- package/src/hooks/use-input.ts +267 -0
- package/src/hooks/use-is-screen-reader-enabled.ts +12 -0
- package/src/hooks/use-paste.ts +78 -0
- package/src/hooks/use-stderr.ts +8 -0
- package/src/hooks/use-stdin.ts +10 -0
- package/src/hooks/use-stdout.ts +8 -0
- package/src/hooks/use-terminal-osc.ts +59 -0
- package/src/hooks/use-virtual-scroll.ts +84 -0
- package/src/hooks/use-window-size.ts +37 -0
- package/src/index.ts +96 -0
- package/src/ink.tsx +1452 -0
- package/src/input-parser.ts +303 -0
- package/src/instances.ts +9 -0
- package/src/kitty-keyboard.ts +185 -0
- package/src/measure-element.ts +62 -0
- package/src/measure-text.ts +31 -0
- package/src/paint-tree.ts +220 -0
- package/src/parse-keypress.ts +515 -0
- package/src/parse-stack-line.ts +138 -0
- package/src/patch-console.ts +106 -0
- package/src/quick-lru.ts +85 -0
- package/src/reconciler.ts +425 -0
- package/src/render-background.ts +59 -0
- package/src/render-border.ts +167 -0
- package/src/render-frame.ts +83 -0
- package/src/render-to-string.ts +146 -0
- package/src/render.ts +284 -0
- package/src/router/components.tsx +343 -0
- package/src/router/context.ts +41 -0
- package/src/router/history.ts +194 -0
- package/src/router/hooks.tsx +391 -0
- package/src/router/index.ts +34 -0
- package/src/router/matcher.ts +571 -0
- package/src/sanitize-ansi.ts +33 -0
- package/src/screen/ansi.ts +184 -0
- package/src/screen/canvas.ts +160 -0
- package/src/screen/cell.ts +138 -0
- package/src/screen/color-profile.ts +47 -0
- package/src/screen/geometry.ts +9 -0
- package/src/screen/index.ts +6 -0
- package/src/screen/screen.ts +305 -0
- package/src/screen/serialize.ts +129 -0
- package/src/screen.ts +1 -0
- package/src/semantic-text-style.ts +118 -0
- package/src/signal-exit.ts +106 -0
- package/src/squash-text-nodes.ts +37 -0
- package/src/stream.ts +28 -0
- package/src/structured-text.ts +325 -0
- package/src/styles.ts +753 -0
- package/src/terminal/index.ts +2 -0
- package/src/terminal/inline-presenter.ts +120 -0
- package/src/terminal/input.ts +92 -0
- package/src/terminal/render-scheduler.ts +37 -0
- package/src/terminal/screen-presenter.ts +242 -0
- package/src/terminal/session.ts +408 -0
- package/src/terminal-size.ts +58 -0
- package/src/terminal.ts +1 -0
- package/src/testing/browser.ts +588 -0
- package/src/testing/emulators.ts +205 -0
- package/src/testing/explorer-app/index.html +12 -0
- package/src/testing/explorer-app/main.ts +381 -0
- package/src/testing/explorer-app/style.css +194 -0
- package/src/testing/explorer-app/tsconfig.json +15 -0
- package/src/testing/explorer-app/vite-env.d.ts +1 -0
- package/src/testing/index.ts +26 -0
- package/src/testing/keys.ts +56 -0
- package/src/testing/live.ts +85 -0
- package/src/testing/matchers.ts +70 -0
- package/src/testing/public.ts +94 -0
- package/src/testing/terminal.ts +361 -0
- package/src/testing/vitest.ts +157 -0
- package/src/throttle.ts +73 -0
- package/src/transform-adapter.ts +14 -0
- package/src/types.ts +10 -0
- package/src/virtual-scroll.ts +133 -0
- package/src/wrap-text.ts +54 -0
- package/src/yoga/config.ts +57 -0
- package/src/yoga/core/absoluteLayout.ts +626 -0
- package/src/yoga/core/baseline.ts +66 -0
- package/src/yoga/core/cache.ts +136 -0
- package/src/yoga/core/calculateLayout.ts +2926 -0
- package/src/yoga/core/config.ts +104 -0
- package/src/yoga/core/flexLine.ts +177 -0
- package/src/yoga/core/helpers.ts +293 -0
- package/src/yoga/core/layoutResults.ts +167 -0
- package/src/yoga/core/node.ts +611 -0
- package/src/yoga/core/numeric.ts +44 -0
- package/src/yoga/core/pixelGrid.ts +151 -0
- package/src/yoga/core/style.ts +887 -0
- package/src/yoga/core/types.ts +224 -0
- package/src/yoga/generated/YGEnums.ts +263 -0
- package/src/yoga/index.ts +19 -0
- package/src/yoga/node.ts +1140 -0
- package/dist/index.d.mts +0 -2379
- package/dist/index.mjs +0 -10072
|
@@ -0,0 +1,723 @@
|
|
|
1
|
+
import { G as pasteEnd, K as pasteStart, S as ansiEscapes } from "./sgr-BhwaWAJB.js";
|
|
2
|
+
import { a as setPointerShape, c as setWorkingDirectory, i as setClipboard, l as tmuxPassthrough, n as notify, o as setTerminalProgress, s as setWindowTitle, u as cliCursor } from "./osc-BFKKSqpg.js";
|
|
3
|
+
import { d as isTerminalQueryResponse, i as getCapabilities, t as colorState } from "./color-policy-BAC9-TZX.js";
|
|
4
|
+
import { n as cellsEqual, t as cellAttributes } from "./cell-_ZVhbfl0.js";
|
|
5
|
+
import { n as serializeScreen, t as serializeLine } from "./serialize-BTkAZgw1.js";
|
|
6
|
+
//#region src/cursor-position.ts
|
|
7
|
+
const showCursorEscape = ansiEscapes.cursorShow;
|
|
8
|
+
const hideCursorEscape = ansiEscapes.cursorHide;
|
|
9
|
+
/**
|
|
10
|
+
Compare two cursor positions. Returns true if they differ.
|
|
11
|
+
*/
|
|
12
|
+
const cursorPositionChanged = (a, b) => a?.x !== b?.x || a?.y !== b?.y;
|
|
13
|
+
/**
|
|
14
|
+
Build escape sequence to move cursor from the bottom of the output to the target position and show it.
|
|
15
|
+
|
|
16
|
+
`bottomLine` is the row the renderer left the cursor on, counted from the top of the output.
|
|
17
|
+
That is always `lines.length - 1` for `lines = str.split('\n')`, whether or not the output ends
|
|
18
|
+
with a newline:
|
|
19
|
+
|
|
20
|
+
- With a trailing newline, `split` yields one extra empty element and the renderer stops just
|
|
21
|
+
past the last visible line — which is `lines.length - 1`.
|
|
22
|
+
- Without one, there is no extra element and the renderer deliberately stops on the last visible
|
|
23
|
+
line instead of moving past it — which is also `lines.length - 1`.
|
|
24
|
+
|
|
25
|
+
This is the same row basis `buildReturnToBottom` measures from, so the two stay in step.
|
|
26
|
+
*/
|
|
27
|
+
const buildCursorSuffix = (bottomLine, cursorPosition) => {
|
|
28
|
+
if (!cursorPosition) return "";
|
|
29
|
+
const moveUp = bottomLine - cursorPosition.y;
|
|
30
|
+
return (moveUp > 0 ? ansiEscapes.cursorUp(moveUp) : "") + ansiEscapes.cursorTo(cursorPosition.x) + showCursorEscape;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
Build escape sequence to move cursor from previousCursorPosition back to the bottom of output.
|
|
34
|
+
This must be done before eraseLines or any operation that assumes cursor is at the bottom.
|
|
35
|
+
*/
|
|
36
|
+
const buildReturnToBottom = (previousLineCount, previousCursorPosition) => {
|
|
37
|
+
if (!previousCursorPosition) return "";
|
|
38
|
+
const down = previousLineCount - 1 - previousCursorPosition.y;
|
|
39
|
+
return (down > 0 ? ansiEscapes.cursorDown(down) : "") + ansiEscapes.cursorTo(0);
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
Build the escape sequence for cursor-only updates (output unchanged, cursor moved).
|
|
43
|
+
Hides cursor if it was previously shown, returns to bottom, then repositions.
|
|
44
|
+
|
|
45
|
+
`buildReturnToBottom` has just placed the cursor on row `previousLineCount - 1`, so the
|
|
46
|
+
suffix measures from there rather than recomputing the row from the output.
|
|
47
|
+
*/
|
|
48
|
+
const buildCursorOnlySequence = (input) => {
|
|
49
|
+
const hidePrefix = input.cursorWasShown ? hideCursorEscape : "";
|
|
50
|
+
const returnToBottom = buildReturnToBottom(input.previousLineCount, input.previousCursorPosition);
|
|
51
|
+
const cursorSuffix = buildCursorSuffix(input.previousLineCount - 1, input.cursorPosition);
|
|
52
|
+
return hidePrefix + returnToBottom + cursorSuffix;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
Build the prefix that hides cursor and returns to bottom before erasing or rewriting.
|
|
56
|
+
Returns empty string if cursor was not shown.
|
|
57
|
+
*/
|
|
58
|
+
const buildReturnToBottomPrefix = (cursorWasShown, previousLineCount, previousCursorPosition) => {
|
|
59
|
+
if (!cursorWasShown) return "";
|
|
60
|
+
return hideCursorEscape + buildReturnToBottom(previousLineCount, previousCursorPosition);
|
|
61
|
+
};
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region src/input-parser.ts
|
|
64
|
+
const escape = "\x1B";
|
|
65
|
+
const isCsiParameterByte = (byte) => {
|
|
66
|
+
return byte >= 48 && byte <= 63;
|
|
67
|
+
};
|
|
68
|
+
const isCsiIntermediateByte = (byte) => {
|
|
69
|
+
return byte >= 32 && byte <= 47;
|
|
70
|
+
};
|
|
71
|
+
const isCsiFinalByte = (byte) => {
|
|
72
|
+
return byte >= 64 && byte <= 126;
|
|
73
|
+
};
|
|
74
|
+
const parseCsiSequence = (input, startIndex, prefixLength) => {
|
|
75
|
+
const csiPayloadStart = startIndex + prefixLength + 1;
|
|
76
|
+
let index = csiPayloadStart;
|
|
77
|
+
for (; index < input.length; index++) {
|
|
78
|
+
const byte = input.codePointAt(index);
|
|
79
|
+
if (byte === void 0) return "pending";
|
|
80
|
+
if (byte === 27 || byte === 3) return {
|
|
81
|
+
sequence: "",
|
|
82
|
+
nextIndex: index
|
|
83
|
+
};
|
|
84
|
+
if (isCsiParameterByte(byte) || isCsiIntermediateByte(byte)) continue;
|
|
85
|
+
if (byte === 91 && index === csiPayloadStart) continue;
|
|
86
|
+
if (isCsiFinalByte(byte)) return {
|
|
87
|
+
sequence: input.slice(startIndex, index + 1),
|
|
88
|
+
nextIndex: index + 1
|
|
89
|
+
};
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
return "pending";
|
|
93
|
+
};
|
|
94
|
+
const parseSs3Sequence = (input, startIndex, prefixLength) => {
|
|
95
|
+
const nextIndex = startIndex + prefixLength + 2;
|
|
96
|
+
if (nextIndex > input.length) return "pending";
|
|
97
|
+
const finalByte = input.codePointAt(nextIndex - 1);
|
|
98
|
+
if (finalByte === void 0 || !isCsiFinalByte(finalByte)) return;
|
|
99
|
+
return {
|
|
100
|
+
sequence: input.slice(startIndex, nextIndex),
|
|
101
|
+
nextIndex
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
const parseControlSequence = (input, startIndex, prefixLength) => {
|
|
105
|
+
const sequenceType = input[startIndex + prefixLength];
|
|
106
|
+
if (sequenceType === void 0) return "pending";
|
|
107
|
+
if (sequenceType === "[") return parseCsiSequence(input, startIndex, prefixLength);
|
|
108
|
+
if (sequenceType === "]" || sequenceType === "P" || sequenceType === "_") {
|
|
109
|
+
for (let index = startIndex + prefixLength + 1; index < input.length; index++) {
|
|
110
|
+
if (input[index] === "" || input[index] === escape && index + 1 < input.length && input[index + 1] !== "\\") return {
|
|
111
|
+
sequence: "",
|
|
112
|
+
nextIndex: index
|
|
113
|
+
};
|
|
114
|
+
const bel = sequenceType === "]" && input[index] === "\x07";
|
|
115
|
+
const st = input[index] === escape && input[index + 1] === "\\";
|
|
116
|
+
if (bel || st) {
|
|
117
|
+
const nextIndex = index + (st ? 2 : 1);
|
|
118
|
+
return {
|
|
119
|
+
sequence: input.slice(startIndex, nextIndex),
|
|
120
|
+
nextIndex
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return "pending";
|
|
125
|
+
}
|
|
126
|
+
if (sequenceType === "O") return parseSs3Sequence(input, startIndex, prefixLength);
|
|
127
|
+
};
|
|
128
|
+
const parseEscapedCodePoint = (input, escapeIndex) => {
|
|
129
|
+
const nextCodePoint = input.codePointAt(escapeIndex + 1);
|
|
130
|
+
const nextCodePointLength = nextCodePoint !== void 0 && nextCodePoint > 65535 ? 2 : 1;
|
|
131
|
+
const nextIndex = escapeIndex + 1 + nextCodePointLength;
|
|
132
|
+
return {
|
|
133
|
+
sequence: input.slice(escapeIndex, nextIndex),
|
|
134
|
+
nextIndex
|
|
135
|
+
};
|
|
136
|
+
};
|
|
137
|
+
const parseEscapeSequence = (input, escapeIndex) => {
|
|
138
|
+
if (escapeIndex === input.length - 1) return "pending";
|
|
139
|
+
if (input[escapeIndex + 1] === escape) {
|
|
140
|
+
if (escapeIndex + 2 >= input.length) return "pending";
|
|
141
|
+
const doubleEscapeSequence = parseControlSequence(input, escapeIndex, 2);
|
|
142
|
+
if (doubleEscapeSequence === "pending") return "pending";
|
|
143
|
+
if (doubleEscapeSequence) return doubleEscapeSequence;
|
|
144
|
+
return {
|
|
145
|
+
sequence: input.slice(escapeIndex, escapeIndex + 2),
|
|
146
|
+
nextIndex: escapeIndex + 2
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
const controlSequence = parseControlSequence(input, escapeIndex, 1);
|
|
150
|
+
if (controlSequence === "pending") return "pending";
|
|
151
|
+
if (controlSequence) return controlSequence;
|
|
152
|
+
return parseEscapedCodePoint(input, escapeIndex);
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
Split a chunk of non-escape text so that backspace bytes (`0x7F` and `0x08`) become individual events. When a user holds the backspace key, the terminal sends repeated bytes in a single stdin chunk. Without splitting, `parseKeypress` receives the multi-byte string and fails to recognize it as a key event, corrupting the input state.
|
|
156
|
+
|
|
157
|
+
Other control characters like `\r` and `\t` are NOT split because they can legitimately appear inside pasted text.
|
|
158
|
+
*/
|
|
159
|
+
const splitBackspaceBytes = (text, events) => {
|
|
160
|
+
let textSegmentStart = 0;
|
|
161
|
+
for (let index = 0; index < text.length; index++) {
|
|
162
|
+
const character = text[index];
|
|
163
|
+
if (character === "" || character === "\b") {
|
|
164
|
+
if (index > textSegmentStart) events.push(text.slice(textSegmentStart, index));
|
|
165
|
+
events.push(character);
|
|
166
|
+
textSegmentStart = index + 1;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (textSegmentStart < text.length) events.push(text.slice(textSegmentStart));
|
|
170
|
+
};
|
|
171
|
+
const parseKeypresses = (input) => {
|
|
172
|
+
const events = [];
|
|
173
|
+
let index = 0;
|
|
174
|
+
const pendingFrom = (pendingStartIndex) => ({
|
|
175
|
+
events,
|
|
176
|
+
pending: input.slice(pendingStartIndex)
|
|
177
|
+
});
|
|
178
|
+
while (index < input.length) {
|
|
179
|
+
const escapeIndex = input.indexOf(escape, index);
|
|
180
|
+
if (escapeIndex === -1) {
|
|
181
|
+
splitBackspaceBytes(input.slice(index), events);
|
|
182
|
+
return {
|
|
183
|
+
events,
|
|
184
|
+
pending: ""
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
if (escapeIndex > index) splitBackspaceBytes(input.slice(index, escapeIndex), events);
|
|
188
|
+
const parsedEscapeSequence = parseEscapeSequence(input, escapeIndex);
|
|
189
|
+
if (parsedEscapeSequence === "pending") return pendingFrom(escapeIndex);
|
|
190
|
+
if (parsedEscapeSequence.sequence === pasteStart) {
|
|
191
|
+
const afterStart = parsedEscapeSequence.nextIndex;
|
|
192
|
+
const endIndex = input.indexOf(pasteEnd, afterStart);
|
|
193
|
+
if (endIndex === -1) return pendingFrom(escapeIndex);
|
|
194
|
+
events.push({ paste: input.slice(afterStart, endIndex) });
|
|
195
|
+
index = endIndex + pasteEnd.length;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
if (parsedEscapeSequence.sequence.length > 0) events.push(parsedEscapeSequence.sequence);
|
|
199
|
+
index = parsedEscapeSequence.nextIndex;
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
events,
|
|
203
|
+
pending: ""
|
|
204
|
+
};
|
|
205
|
+
};
|
|
206
|
+
const createInputParser = () => {
|
|
207
|
+
let pending = "";
|
|
208
|
+
const hasPendingEscape = () => pending.startsWith(escape) && !/^\u001B{1,2}[[\]P_]/.test(pending);
|
|
209
|
+
return {
|
|
210
|
+
push(chunk) {
|
|
211
|
+
const parsedInput = parseKeypresses(pending + chunk);
|
|
212
|
+
pending = parsedInput.pending;
|
|
213
|
+
return parsedInput.events;
|
|
214
|
+
},
|
|
215
|
+
hasPendingEscape,
|
|
216
|
+
flushPendingEscape() {
|
|
217
|
+
if (!hasPendingEscape()) return;
|
|
218
|
+
const pendingEscape = pending;
|
|
219
|
+
pending = "";
|
|
220
|
+
return pendingEscape;
|
|
221
|
+
},
|
|
222
|
+
reset() {
|
|
223
|
+
pending = "";
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
};
|
|
227
|
+
//#endregion
|
|
228
|
+
//#region src/terminal/input.ts
|
|
229
|
+
const sgrMouse = /^\u001B\[<(\d+);(\d+);(\d+)([Mm])$/;
|
|
230
|
+
/** Stateful stdin decoder that separates terminal reports from application input. */
|
|
231
|
+
var TerminalInput = class {
|
|
232
|
+
#parser = createInputParser();
|
|
233
|
+
#capabilities;
|
|
234
|
+
#mouseListeners = /* @__PURE__ */ new Set();
|
|
235
|
+
constructor(capabilities) {
|
|
236
|
+
this.#capabilities = capabilities;
|
|
237
|
+
}
|
|
238
|
+
push(chunk) {
|
|
239
|
+
return this.#parser.push(chunk).flatMap((event) => {
|
|
240
|
+
if (typeof event !== "string") return [event];
|
|
241
|
+
if (event.startsWith("\x1B\x1B") && isTerminalQueryResponse(event.slice(1))) return ["\x1B"];
|
|
242
|
+
if (this.#capabilities.ingest(event)) return [];
|
|
243
|
+
if (isTerminalQueryResponse(event)) return [];
|
|
244
|
+
const mouse = parseMouseEvent(event);
|
|
245
|
+
if (!mouse) return [event];
|
|
246
|
+
for (const listener of this.#mouseListeners) listener(mouse);
|
|
247
|
+
return [];
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
subscribeMouse(listener) {
|
|
251
|
+
this.#mouseListeners.add(listener);
|
|
252
|
+
return () => this.#mouseListeners.delete(listener);
|
|
253
|
+
}
|
|
254
|
+
hasPendingEscape() {
|
|
255
|
+
return this.#parser.hasPendingEscape();
|
|
256
|
+
}
|
|
257
|
+
flushPendingEscape() {
|
|
258
|
+
return this.#parser.flushPendingEscape();
|
|
259
|
+
}
|
|
260
|
+
reset() {
|
|
261
|
+
this.#parser.reset();
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
function parseMouseEvent(sequence) {
|
|
265
|
+
const match = sequence.match(sgrMouse);
|
|
266
|
+
if (!match) return;
|
|
267
|
+
const code = Number(match[1]);
|
|
268
|
+
const column = Number(match[2]);
|
|
269
|
+
const row = Number(match[3]);
|
|
270
|
+
if (column < 1 || row < 1) return;
|
|
271
|
+
const wheel = (code & 64) !== 0;
|
|
272
|
+
const move = (code & 32) !== 0;
|
|
273
|
+
const buttonCode = code & 3;
|
|
274
|
+
const button = wheel ? buttonCode === 0 ? "wheel-up" : "wheel-down" : [
|
|
275
|
+
"left",
|
|
276
|
+
"middle",
|
|
277
|
+
"right",
|
|
278
|
+
"none"
|
|
279
|
+
][buttonCode];
|
|
280
|
+
return {
|
|
281
|
+
type: wheel ? "wheel" : move ? "move" : match[4] === "m" || buttonCode === 3 ? "release" : "press",
|
|
282
|
+
x: column - 1,
|
|
283
|
+
y: row - 1,
|
|
284
|
+
button,
|
|
285
|
+
shift: (code & 4) !== 0,
|
|
286
|
+
alt: (code & 8) !== 0,
|
|
287
|
+
ctrl: (code & 16) !== 0
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
//#endregion
|
|
291
|
+
//#region src/terminal/screen-presenter.ts
|
|
292
|
+
/** Presents structured frames in an inline terminal region. */
|
|
293
|
+
var ScreenPresenter = class {
|
|
294
|
+
#screen;
|
|
295
|
+
#lineCount = 0;
|
|
296
|
+
#cursor;
|
|
297
|
+
#cursorWasShown = false;
|
|
298
|
+
#fullscreen = false;
|
|
299
|
+
#hidden = false;
|
|
300
|
+
#write;
|
|
301
|
+
#stream;
|
|
302
|
+
constructor(write, stream) {
|
|
303
|
+
this.#write = write;
|
|
304
|
+
this.#stream = stream;
|
|
305
|
+
}
|
|
306
|
+
present(screen, options) {
|
|
307
|
+
if (!this.#hidden) {
|
|
308
|
+
cliCursor.hide(this.#stream);
|
|
309
|
+
this.#hidden = true;
|
|
310
|
+
}
|
|
311
|
+
const previousScreen = this.#screen;
|
|
312
|
+
const sameScreen = previousScreen !== void 0 && screensEqual(previousScreen, screen);
|
|
313
|
+
const cursorChanged = cursorPositionChanged(options.cursor, this.#cursor);
|
|
314
|
+
const fullscreen = options.fullscreen ?? false;
|
|
315
|
+
const presentationModeChanged = this.#fullscreen !== fullscreen;
|
|
316
|
+
if (!options.forceRewrite && sameScreen && !cursorChanged && !presentationModeChanged) return false;
|
|
317
|
+
if (!options.forceRewrite && sameScreen && !presentationModeChanged) {
|
|
318
|
+
this.#write(buildCursorOnlySequence({
|
|
319
|
+
cursorWasShown: this.#cursorWasShown,
|
|
320
|
+
previousLineCount: this.#lineCount,
|
|
321
|
+
previousCursorPosition: this.#cursor,
|
|
322
|
+
cursorPosition: options.cursor
|
|
323
|
+
}));
|
|
324
|
+
this.#remember(screen, options.cursor, options.fullscreen ?? false);
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
const firstChangedRow = !options.forceRewrite && previousScreen !== void 0 && previousScreen.width === screen.width && this.#fullscreen === fullscreen ? findFirstChangedRow(previousScreen, screen) : void 0;
|
|
328
|
+
const serializeOptions = {
|
|
329
|
+
colorProfile: options.colorProfile,
|
|
330
|
+
styles: options.colorProfile !== "none"
|
|
331
|
+
};
|
|
332
|
+
if (firstChangedRow !== void 0) {
|
|
333
|
+
const eraseCount = this.#lineCount - firstChangedRow;
|
|
334
|
+
const body = screen.toRows().slice(firstChangedRow).map((line) => serializeLine(line, serializeOptions)).join("\n");
|
|
335
|
+
const output = fullscreen || body === "" ? body : `${body}\n`;
|
|
336
|
+
this.#write(buildReturnToBottomPrefix(this.#cursorWasShown, this.#lineCount, this.#cursor) + ansiEscapes.eraseLines(eraseCount) + output + buildCursorSuffix(screen.height - (fullscreen ? 1 : 0), options.cursor));
|
|
337
|
+
} else {
|
|
338
|
+
const body = serializeScreen(screen, serializeOptions);
|
|
339
|
+
const output = fullscreen ? body : `${body}\n`;
|
|
340
|
+
this.#write(buildReturnToBottomPrefix(this.#cursorWasShown, this.#lineCount, this.#cursor) + ansiEscapes.eraseLines(this.#lineCount) + output + buildCursorSuffix(screen.height - (fullscreen ? 1 : 0), options.cursor));
|
|
341
|
+
}
|
|
342
|
+
this.#remember(screen, options.cursor, fullscreen);
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
willPresent(screen, cursor, fullscreen = false, forceRewrite = false) {
|
|
346
|
+
return forceRewrite || this.#screen === void 0 || !screensEqual(this.#screen, screen) || cursorPositionChanged(cursor, this.#cursor) || fullscreen !== this.#fullscreen;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
Erases the presented frame from the cursor upward.
|
|
350
|
+
|
|
351
|
+
`columns` is the terminal's current width. When it is narrower than the width
|
|
352
|
+
the frame was painted at, a reflowing emulator (xterm.js, Ghostty, kitty,
|
|
353
|
+
iTerm2, WezTerm, tmux, …) has already rewrapped every wider row onto several
|
|
354
|
+
physical rows before the resize event reaches us, so the erase has to cover
|
|
355
|
+
that physical footprint. Erasing only the logical row count leaves the frame's
|
|
356
|
+
top rows behind as ghosts, one more batch per resize event.
|
|
357
|
+
*/
|
|
358
|
+
clear(options = {}) {
|
|
359
|
+
this.#write(buildReturnToBottomPrefix(this.#cursorWasShown, this.#lineCount, this.#cursor) + ansiEscapes.eraseLines(this.#physicalLineCount(options.columns)));
|
|
360
|
+
this.#lineCount = 0;
|
|
361
|
+
this.#cursor = void 0;
|
|
362
|
+
this.#cursorWasShown = false;
|
|
363
|
+
this.#fullscreen = false;
|
|
364
|
+
}
|
|
365
|
+
#physicalLineCount(columns) {
|
|
366
|
+
if (this.#screen === void 0 || this.#lineCount === 0 || columns === void 0 || columns < 1) return this.#lineCount;
|
|
367
|
+
let rows = 0;
|
|
368
|
+
for (const line of this.#screen.toRows()) rows += Math.max(1, Math.ceil(contentWidth(line) / columns));
|
|
369
|
+
return rows + (this.#fullscreen ? 0 : 1);
|
|
370
|
+
}
|
|
371
|
+
reset() {
|
|
372
|
+
this.#screen = void 0;
|
|
373
|
+
this.#lineCount = 0;
|
|
374
|
+
this.#cursor = void 0;
|
|
375
|
+
this.#cursorWasShown = false;
|
|
376
|
+
}
|
|
377
|
+
done() {
|
|
378
|
+
this.reset();
|
|
379
|
+
if (this.#hidden) {
|
|
380
|
+
cliCursor.show(this.#stream);
|
|
381
|
+
this.#hidden = false;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
get current() {
|
|
385
|
+
return this.#screen;
|
|
386
|
+
}
|
|
387
|
+
#remember(screen, cursor, fullscreen) {
|
|
388
|
+
this.#screen = screen;
|
|
389
|
+
this.#lineCount = screen.height + (fullscreen ? 0 : 1);
|
|
390
|
+
this.#cursor = cursor ? { ...cursor } : void 0;
|
|
391
|
+
this.#cursorWasShown = cursor !== void 0;
|
|
392
|
+
this.#fullscreen = fullscreen;
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
/**
|
|
396
|
+
The columns a row occupies once written: everything up to its last visible
|
|
397
|
+
cell. Trailing unstyled blanks are trimmed by the serializer and never reach
|
|
398
|
+
the terminal, while styled blanks (a highlighted tab's padding) do.
|
|
399
|
+
*/
|
|
400
|
+
function contentWidth(line) {
|
|
401
|
+
let width = 0;
|
|
402
|
+
let end = 0;
|
|
403
|
+
for (const cell of line) {
|
|
404
|
+
width += cell.width;
|
|
405
|
+
if (!isBlank(cell)) end = width;
|
|
406
|
+
}
|
|
407
|
+
return end;
|
|
408
|
+
}
|
|
409
|
+
function isBlank(cell) {
|
|
410
|
+
const { style } = cell;
|
|
411
|
+
return (cell.grapheme === " " || cell.width === 0) && style.foreground === void 0 && style.background === void 0 && style.underlineColor === void 0 && style.underline === "none" && style.attributes === cellAttributes.none && cell.hyperlink === void 0;
|
|
412
|
+
}
|
|
413
|
+
function findFirstChangedRow(previous, next) {
|
|
414
|
+
const height = Math.max(previous.height, next.height);
|
|
415
|
+
for (let y = 0; y < height; y++) if (!rowsEqual(previous, next, y)) return y;
|
|
416
|
+
}
|
|
417
|
+
function rowsEqual(left, right, y) {
|
|
418
|
+
const length = Math.max(left.rowLength(y), right.rowLength(y));
|
|
419
|
+
for (let x = 0; x < length; x++) if (!cellsEqual(left.cellAt(x, y), right.cellAt(x, y))) return false;
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
422
|
+
function screensEqual(left, right) {
|
|
423
|
+
if (left.width !== right.width || left.height !== right.height) return false;
|
|
424
|
+
for (let y = 0; y < left.height; y++) if (!rowsEqual(left, right, y)) return false;
|
|
425
|
+
return true;
|
|
426
|
+
}
|
|
427
|
+
//#endregion
|
|
428
|
+
//#region src/terminal/session.ts
|
|
429
|
+
const imperativeProgressOwner = Symbol("imperative-progress");
|
|
430
|
+
const imperativeTitleOwner = Symbol("imperative-title");
|
|
431
|
+
/** Instance-scoped terminal state and lifecycle ownership. */
|
|
432
|
+
var TerminalSession = class {
|
|
433
|
+
stdin;
|
|
434
|
+
stdout;
|
|
435
|
+
stderr;
|
|
436
|
+
capabilities;
|
|
437
|
+
input;
|
|
438
|
+
#presenter;
|
|
439
|
+
cursor = {
|
|
440
|
+
visible: true,
|
|
441
|
+
shape: "block",
|
|
442
|
+
blinking: true
|
|
443
|
+
};
|
|
444
|
+
suspended = false;
|
|
445
|
+
alternateScreen = false;
|
|
446
|
+
inlineScreen = true;
|
|
447
|
+
#policy;
|
|
448
|
+
#profile;
|
|
449
|
+
#modes = /* @__PURE__ */ new Map();
|
|
450
|
+
#pendingWrites = /* @__PURE__ */ new Set();
|
|
451
|
+
#unsubscribe;
|
|
452
|
+
#cleaned = false;
|
|
453
|
+
#progressActive = false;
|
|
454
|
+
#progressSequence;
|
|
455
|
+
#progressHeartbeat;
|
|
456
|
+
#progressPublishers = /* @__PURE__ */ new Map();
|
|
457
|
+
#titlePublishers = /* @__PURE__ */ new Map();
|
|
458
|
+
#titleActive = false;
|
|
459
|
+
constructor(options) {
|
|
460
|
+
this.stdin = options.stdin;
|
|
461
|
+
this.stdout = options.stdout;
|
|
462
|
+
this.stderr = options.stderr;
|
|
463
|
+
this.capabilities = getCapabilities(this.stdin, this.stdout);
|
|
464
|
+
this.input = new TerminalInput(this.capabilities);
|
|
465
|
+
this.#presenter = new ScreenPresenter((data) => this.write(data), this.stdout);
|
|
466
|
+
this.#policy = options.colorPolicy ?? "auto";
|
|
467
|
+
this.#profile = colorState(this.capabilities.current, this.#policy).effective;
|
|
468
|
+
this.#unsubscribe = this.capabilities.subscribe((capabilities) => {
|
|
469
|
+
const profile = colorState(capabilities, this.#policy).effective;
|
|
470
|
+
if (profile !== this.#profile) {
|
|
471
|
+
this.#profile = profile;
|
|
472
|
+
this.#presenter.reset();
|
|
473
|
+
}
|
|
474
|
+
options.onCapabilitiesChange?.();
|
|
475
|
+
}, { resizes: false });
|
|
476
|
+
}
|
|
477
|
+
get colorProfile() {
|
|
478
|
+
return this.#profile;
|
|
479
|
+
}
|
|
480
|
+
/** Encodes a structured frame at the terminal boundary. */
|
|
481
|
+
encode(screen) {
|
|
482
|
+
return serializeScreen(screen, {
|
|
483
|
+
colorProfile: this.colorProfile,
|
|
484
|
+
styles: this.colorProfile !== "none"
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
present(screen, options = {}) {
|
|
488
|
+
return this.#presenter.present(screen, {
|
|
489
|
+
colorProfile: this.colorProfile,
|
|
490
|
+
cursor: this.cursor.position,
|
|
491
|
+
...options
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
willPresent(screen, options = {}) {
|
|
495
|
+
return this.#presenter.willPresent(screen, this.cursor.position, options.fullscreen, options.forceRewrite);
|
|
496
|
+
}
|
|
497
|
+
/** Erases the presented frame; pass the current `columns` after a resize so the erase covers rows the emulator rewrapped. */
|
|
498
|
+
clearFrame(options = {}) {
|
|
499
|
+
this.#presenter.clear(options);
|
|
500
|
+
}
|
|
501
|
+
resetFrame() {
|
|
502
|
+
this.#presenter.reset();
|
|
503
|
+
}
|
|
504
|
+
finishFrame() {
|
|
505
|
+
this.#presenter.done();
|
|
506
|
+
}
|
|
507
|
+
setColorPolicy(policy) {
|
|
508
|
+
this.#policy = policy;
|
|
509
|
+
const profile = colorState(this.capabilities.current, policy).effective;
|
|
510
|
+
if (profile !== this.#profile) {
|
|
511
|
+
this.#profile = profile;
|
|
512
|
+
this.#presenter.reset();
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
/** Writes through the session and tracks callback-based backpressure completion. */
|
|
516
|
+
write(data) {
|
|
517
|
+
let settle;
|
|
518
|
+
const completion = new Promise((resolve) => {
|
|
519
|
+
settle = resolve;
|
|
520
|
+
});
|
|
521
|
+
this.#pendingWrites.add(completion);
|
|
522
|
+
try {
|
|
523
|
+
return this.stdout.write(data, () => {
|
|
524
|
+
this.#pendingWrites.delete(completion);
|
|
525
|
+
settle();
|
|
526
|
+
});
|
|
527
|
+
} catch (error) {
|
|
528
|
+
this.#pendingWrites.delete(completion);
|
|
529
|
+
settle();
|
|
530
|
+
throw error;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
async flush() {
|
|
534
|
+
await Promise.all(this.#pendingWrites);
|
|
535
|
+
}
|
|
536
|
+
/** Copy text through OSC 52, including tmux passthrough when required. */
|
|
537
|
+
copyToClipboard(text, selection = "clipboard") {
|
|
538
|
+
return this.write(this.#wrapOsc(setClipboard(text, selection)));
|
|
539
|
+
}
|
|
540
|
+
setTitle(title) {
|
|
541
|
+
return this.publishTitle(imperativeTitleOwner, title);
|
|
542
|
+
}
|
|
543
|
+
publishTitle(owner, title) {
|
|
544
|
+
this.#titlePublishers.delete(owner);
|
|
545
|
+
if (title !== void 0) this.#titlePublishers.set(owner, title);
|
|
546
|
+
const current = [...this.#titlePublishers.values()].at(-1);
|
|
547
|
+
if (current !== void 0) {
|
|
548
|
+
this.#titleActive = true;
|
|
549
|
+
return this.write(this.#wrapOsc(setWindowTitle(current)));
|
|
550
|
+
}
|
|
551
|
+
if (!this.#titleActive) return true;
|
|
552
|
+
this.#titleActive = false;
|
|
553
|
+
return this.write(this.#wrapOsc(setWindowTitle("")));
|
|
554
|
+
}
|
|
555
|
+
setWorkingDirectory(directory) {
|
|
556
|
+
return this.write(this.#wrapOsc(setWorkingDirectory(directory)));
|
|
557
|
+
}
|
|
558
|
+
notify(title) {
|
|
559
|
+
return this.write(this.#wrapOsc(notify(title)));
|
|
560
|
+
}
|
|
561
|
+
setPointerShape(shape) {
|
|
562
|
+
return this.write(this.#wrapOsc(setPointerShape(shape)));
|
|
563
|
+
}
|
|
564
|
+
publishProgress(owner, state, value) {
|
|
565
|
+
this.#progressPublishers.delete(owner);
|
|
566
|
+
if (state !== "inactive") this.#progressPublishers.set(owner, {
|
|
567
|
+
state,
|
|
568
|
+
value
|
|
569
|
+
});
|
|
570
|
+
return this.#applyPublishedProgress();
|
|
571
|
+
}
|
|
572
|
+
#applyPublishedProgress() {
|
|
573
|
+
const current = [...this.#progressPublishers.values()].at(-1);
|
|
574
|
+
return current === void 0 ? this.#writeProgress("inactive") : this.#writeProgress(current.state, current.value);
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Update terminal-native progress. Active progress is reset during cleanup
|
|
578
|
+
* so a completed or failed process cannot leave a stale terminal indicator.
|
|
579
|
+
*/
|
|
580
|
+
setProgress(state, value) {
|
|
581
|
+
this.#progressPublishers.delete(imperativeProgressOwner);
|
|
582
|
+
if (state !== "inactive") this.#progressPublishers.set(imperativeProgressOwner, {
|
|
583
|
+
state,
|
|
584
|
+
value
|
|
585
|
+
});
|
|
586
|
+
return this.#applyPublishedProgress();
|
|
587
|
+
}
|
|
588
|
+
#writeProgress(state, value) {
|
|
589
|
+
this.#progressActive = state !== "inactive";
|
|
590
|
+
const sequence = this.#wrapOsc(setTerminalProgress(state, value));
|
|
591
|
+
if (!this.#progressActive) this.#stopProgressHeartbeat();
|
|
592
|
+
else {
|
|
593
|
+
this.#progressSequence = sequence;
|
|
594
|
+
if (this.#progressHeartbeat === void 0) {
|
|
595
|
+
this.#progressHeartbeat = setInterval(() => {
|
|
596
|
+
if (this.#progressSequence !== void 0) this.write(this.#progressSequence);
|
|
597
|
+
}, 1e3);
|
|
598
|
+
this.#progressHeartbeat.unref?.();
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
return this.write(sequence);
|
|
602
|
+
}
|
|
603
|
+
#stopProgressHeartbeat() {
|
|
604
|
+
if (this.#progressHeartbeat !== void 0) {
|
|
605
|
+
clearInterval(this.#progressHeartbeat);
|
|
606
|
+
this.#progressHeartbeat = void 0;
|
|
607
|
+
}
|
|
608
|
+
this.#progressSequence = void 0;
|
|
609
|
+
}
|
|
610
|
+
enableMode(modeOrEnable, disable) {
|
|
611
|
+
const mode = typeof modeOrEnable === "string" ? {
|
|
612
|
+
id: disable ?? modeOrEnable,
|
|
613
|
+
enable: modeOrEnable,
|
|
614
|
+
disable: disable ?? ""
|
|
615
|
+
} : modeOrEnable;
|
|
616
|
+
const active = this.#modes.get(mode.id);
|
|
617
|
+
if (active) {
|
|
618
|
+
active.count++;
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
this.write(mode.enable);
|
|
622
|
+
this.#modes.set(mode.id, {
|
|
623
|
+
...mode,
|
|
624
|
+
count: 1
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
disableMode(idOrDisable) {
|
|
628
|
+
const entry = this.#modes.get(idOrDisable) ?? [...this.#modes.values()].find((mode) => mode.disable === idOrDisable);
|
|
629
|
+
if (!entry) return;
|
|
630
|
+
if (--entry.count > 0) return;
|
|
631
|
+
this.#modes.delete(entry.id);
|
|
632
|
+
this.write(entry.disable);
|
|
633
|
+
}
|
|
634
|
+
setCursor(position) {
|
|
635
|
+
this.cursor = {
|
|
636
|
+
...this.cursor,
|
|
637
|
+
position,
|
|
638
|
+
visible: position !== void 0
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
setCursorAppearance(options) {
|
|
642
|
+
const next = {
|
|
643
|
+
...this.cursor,
|
|
644
|
+
...options.visible === void 0 ? {} : { visible: options.visible },
|
|
645
|
+
...options.shape === void 0 ? {} : { shape: options.shape },
|
|
646
|
+
...options.blinking === void 0 ? {} : { blinking: options.blinking },
|
|
647
|
+
...options.color === void 0 ? {} : { color: options.color ?? void 0 }
|
|
648
|
+
};
|
|
649
|
+
let output = "";
|
|
650
|
+
if (next.shape !== this.cursor.shape || next.blinking !== this.cursor.blinking) output += ansiEscapes.cursorShape(next.shape, next.blinking);
|
|
651
|
+
if (options.color !== void 0) output += options.color === null ? ansiEscapes.resetCursorColor : ansiEscapes.cursorColor(options.color);
|
|
652
|
+
if (next.visible !== this.cursor.visible) output += next.visible ? ansiEscapes.cursorShow : ansiEscapes.cursorHide;
|
|
653
|
+
this.cursor = next;
|
|
654
|
+
if (output) this.write(output);
|
|
655
|
+
}
|
|
656
|
+
setAlternateScreen(enabled, options = {}) {
|
|
657
|
+
if (enabled === this.alternateScreen) return;
|
|
658
|
+
this.alternateScreen = enabled;
|
|
659
|
+
this.inlineScreen = !enabled;
|
|
660
|
+
if (enabled) {
|
|
661
|
+
this.enableMode({
|
|
662
|
+
id: "alternate-screen",
|
|
663
|
+
enable: ansiEscapes.enterAlternativeScreen,
|
|
664
|
+
disable: ansiEscapes.exitAlternativeScreen
|
|
665
|
+
});
|
|
666
|
+
if (options.hideCursor) this.setCursorAppearance({ visible: false });
|
|
667
|
+
} else this.disableMode("alternate-screen");
|
|
668
|
+
this.#presenter.reset();
|
|
669
|
+
}
|
|
670
|
+
beginSuspension() {
|
|
671
|
+
if (this.suspended) throw new Error("The terminal session is already suspended");
|
|
672
|
+
this.suspended = true;
|
|
673
|
+
}
|
|
674
|
+
resume() {
|
|
675
|
+
if (!this.suspended) return;
|
|
676
|
+
this.suspended = false;
|
|
677
|
+
this.#presenter.reset();
|
|
678
|
+
}
|
|
679
|
+
cleanup() {
|
|
680
|
+
if (this.#cleaned) return;
|
|
681
|
+
this.#cleaned = true;
|
|
682
|
+
this.#unsubscribe();
|
|
683
|
+
this.#stopProgressHeartbeat();
|
|
684
|
+
this.#progressPublishers.clear();
|
|
685
|
+
if (this.#progressActive) {
|
|
686
|
+
try {
|
|
687
|
+
this.stdout.write(this.#wrapOsc(setTerminalProgress("inactive")));
|
|
688
|
+
} catch {}
|
|
689
|
+
this.#progressActive = false;
|
|
690
|
+
}
|
|
691
|
+
this.#titlePublishers.clear();
|
|
692
|
+
if (this.#titleActive) {
|
|
693
|
+
try {
|
|
694
|
+
this.stdout.write(this.#wrapOsc(setWindowTitle("")));
|
|
695
|
+
} catch {}
|
|
696
|
+
this.#titleActive = false;
|
|
697
|
+
}
|
|
698
|
+
for (const mode of [...this.#modes.values()].reverse()) try {
|
|
699
|
+
this.stdout.write(mode.disable);
|
|
700
|
+
} catch {}
|
|
701
|
+
if (this.cursor.color !== void 0) try {
|
|
702
|
+
this.stdout.write(ansiEscapes.resetCursorColor);
|
|
703
|
+
} catch {}
|
|
704
|
+
if (!this.cursor.visible) try {
|
|
705
|
+
this.stdout.write(ansiEscapes.cursorShow);
|
|
706
|
+
} catch {}
|
|
707
|
+
this.#modes.clear();
|
|
708
|
+
this.alternateScreen = false;
|
|
709
|
+
this.inlineScreen = true;
|
|
710
|
+
this.suspended = false;
|
|
711
|
+
this.cursor = {
|
|
712
|
+
visible: true,
|
|
713
|
+
shape: "block",
|
|
714
|
+
blinking: true
|
|
715
|
+
};
|
|
716
|
+
this.#presenter.done();
|
|
717
|
+
}
|
|
718
|
+
#wrapOsc(sequence) {
|
|
719
|
+
return this.capabilities.current.terminal.multiplexer === "tmux" ? tmuxPassthrough(sequence) : sequence;
|
|
720
|
+
}
|
|
721
|
+
};
|
|
722
|
+
//#endregion
|
|
723
|
+
export { buildCursorSuffix as a, hideCursorEscape as c, buildCursorOnlySequence as i, TerminalInput as n, buildReturnToBottomPrefix as o, parseMouseEvent as r, cursorPositionChanged as s, TerminalSession as t };
|