@termaxjs/web-core 0.1.1

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 (226) hide show
  1. package/LICENSE +201 -0
  2. package/dist/block/blockRange.d.ts +11 -0
  3. package/dist/block/blockRange.d.ts.map +1 -0
  4. package/dist/block/blockRange.js +16 -0
  5. package/dist/block/blockRange.js.map +1 -0
  6. package/dist/block/modeMachine.d.ts +18 -0
  7. package/dist/block/modeMachine.d.ts.map +1 -0
  8. package/dist/block/modeMachine.js +21 -0
  9. package/dist/block/modeMachine.js.map +1 -0
  10. package/dist/cursorBlink.d.ts +2 -0
  11. package/dist/cursorBlink.d.ts.map +1 -0
  12. package/dist/cursorBlink.js +4 -0
  13. package/dist/cursorBlink.js.map +1 -0
  14. package/dist/explorer/HeadlessFileTree.d.ts +27 -0
  15. package/dist/explorer/HeadlessFileTree.d.ts.map +1 -0
  16. package/dist/explorer/HeadlessFileTree.js +147 -0
  17. package/dist/explorer/HeadlessFileTree.js.map +1 -0
  18. package/dist/explorer/expansionCache.d.ts +4 -0
  19. package/dist/explorer/expansionCache.d.ts.map +1 -0
  20. package/dist/explorer/expansionCache.js +26 -0
  21. package/dist/explorer/expansionCache.js.map +1 -0
  22. package/dist/explorer/index.d.ts +5 -0
  23. package/dist/explorer/index.d.ts.map +1 -0
  24. package/dist/explorer/index.js +5 -0
  25. package/dist/explorer/index.js.map +1 -0
  26. package/dist/explorer/pathUtils.d.ts +5 -0
  27. package/dist/explorer/pathUtils.d.ts.map +1 -0
  28. package/dist/explorer/pathUtils.js +19 -0
  29. package/dist/explorer/pathUtils.js.map +1 -0
  30. package/dist/explorer/types.d.ts +65 -0
  31. package/dist/explorer/types.d.ts.map +1 -0
  32. package/dist/explorer/types.js +2 -0
  33. package/dist/explorer/types.js.map +1 -0
  34. package/dist/git/HeadlessGitGraph.d.ts +39 -0
  35. package/dist/git/HeadlessGitGraph.d.ts.map +1 -0
  36. package/dist/git/HeadlessGitGraph.js +81 -0
  37. package/dist/git/HeadlessGitGraph.js.map +1 -0
  38. package/dist/images/KittyGraphicsParser.d.ts +24 -0
  39. package/dist/images/KittyGraphicsParser.d.ts.map +1 -0
  40. package/dist/images/KittyGraphicsParser.js +87 -0
  41. package/dist/images/KittyGraphicsParser.js.map +1 -0
  42. package/dist/images/SixelDecoder.d.ts +9 -0
  43. package/dist/images/SixelDecoder.d.ts.map +1 -0
  44. package/dist/images/SixelDecoder.js +110 -0
  45. package/dist/images/SixelDecoder.js.map +1 -0
  46. package/dist/index.d.ts +25 -0
  47. package/dist/index.d.ts.map +1 -0
  48. package/dist/index.js +25 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/keymap.d.ts +9 -0
  51. package/dist/keymap.d.ts.map +1 -0
  52. package/dist/keymap.js +62 -0
  53. package/dist/keymap.js.map +1 -0
  54. package/dist/layout/HeadlessFloatingManager.d.ts +18 -0
  55. package/dist/layout/HeadlessFloatingManager.d.ts.map +1 -0
  56. package/dist/layout/HeadlessFloatingManager.js +41 -0
  57. package/dist/layout/HeadlessFloatingManager.js.map +1 -0
  58. package/dist/layout/HeadlessPaneTree.d.ts +27 -0
  59. package/dist/layout/HeadlessPaneTree.d.ts.map +1 -0
  60. package/dist/layout/HeadlessPaneTree.js +117 -0
  61. package/dist/layout/HeadlessPaneTree.js.map +1 -0
  62. package/dist/layout/PaneGeometry.d.ts +17 -0
  63. package/dist/layout/PaneGeometry.d.ts.map +1 -0
  64. package/dist/layout/PaneGeometry.js +27 -0
  65. package/dist/layout/PaneGeometry.js.map +1 -0
  66. package/dist/layout/index.d.ts +9 -0
  67. package/dist/layout/index.d.ts.map +1 -0
  68. package/dist/layout/index.js +12 -0
  69. package/dist/layout/index.js.map +1 -0
  70. package/dist/layout/paneTree.d.ts +39 -0
  71. package/dist/layout/paneTree.d.ts.map +1 -0
  72. package/dist/layout/paneTree.js +215 -0
  73. package/dist/layout/paneTree.js.map +1 -0
  74. package/dist/layout/storeUtils.d.ts +23 -0
  75. package/dist/layout/storeUtils.d.ts.map +1 -0
  76. package/dist/layout/storeUtils.js +9 -0
  77. package/dist/layout/storeUtils.js.map +1 -0
  78. package/dist/layout/tabsActions.d.ts +72 -0
  79. package/dist/layout/tabsActions.d.ts.map +1 -0
  80. package/dist/layout/tabsActions.js +975 -0
  81. package/dist/layout/tabsActions.js.map +1 -0
  82. package/dist/layout/tabsStore.d.ts +43 -0
  83. package/dist/layout/tabsStore.d.ts.map +1 -0
  84. package/dist/layout/tabsStore.js +69 -0
  85. package/dist/layout/tabsStore.js.map +1 -0
  86. package/dist/layout/types.d.ts +114 -0
  87. package/dist/layout/types.d.ts.map +1 -0
  88. package/dist/layout/types.js +2 -0
  89. package/dist/layout/types.js.map +1 -0
  90. package/dist/layout/useTabs.d.ts +55 -0
  91. package/dist/layout/useTabs.d.ts.map +1 -0
  92. package/dist/layout/useTabs.js +116 -0
  93. package/dist/layout/useTabs.js.map +1 -0
  94. package/dist/lsp/client.d.ts +16 -0
  95. package/dist/lsp/client.d.ts.map +1 -0
  96. package/dist/lsp/client.js +125 -0
  97. package/dist/lsp/client.js.map +1 -0
  98. package/dist/lsp/index.d.ts +3 -0
  99. package/dist/lsp/index.d.ts.map +1 -0
  100. package/dist/lsp/index.js +3 -0
  101. package/dist/lsp/index.js.map +1 -0
  102. package/dist/lsp/types.d.ts +44 -0
  103. package/dist/lsp/types.d.ts.map +1 -0
  104. package/dist/lsp/types.js +2 -0
  105. package/dist/lsp/types.js.map +1 -0
  106. package/dist/palette/HeadlessCommandPalette.d.ts +21 -0
  107. package/dist/palette/HeadlessCommandPalette.d.ts.map +1 -0
  108. package/dist/palette/HeadlessCommandPalette.js +48 -0
  109. package/dist/palette/HeadlessCommandPalette.js.map +1 -0
  110. package/dist/parser/VtEngine.d.ts +87 -0
  111. package/dist/parser/VtEngine.d.ts.map +1 -0
  112. package/dist/parser/VtEngine.js +1206 -0
  113. package/dist/parser/VtEngine.js.map +1 -0
  114. package/dist/parser/index.d.ts +2 -0
  115. package/dist/parser/index.d.ts.map +1 -0
  116. package/dist/parser/index.js +2 -0
  117. package/dist/parser/index.js.map +1 -0
  118. package/dist/protocol.d.ts +3 -0
  119. package/dist/protocol.d.ts.map +1 -0
  120. package/dist/protocol.js +94 -0
  121. package/dist/protocol.js.map +1 -0
  122. package/dist/settings/EditorSchema.d.ts +3 -0
  123. package/dist/settings/EditorSchema.d.ts.map +1 -0
  124. package/dist/settings/EditorSchema.js +78 -0
  125. package/dist/settings/EditorSchema.js.map +1 -0
  126. package/dist/settings/GeneralSchema.d.ts +3 -0
  127. package/dist/settings/GeneralSchema.d.ts.map +1 -0
  128. package/dist/settings/GeneralSchema.js +87 -0
  129. package/dist/settings/GeneralSchema.js.map +1 -0
  130. package/dist/settings/TerminalSchema.d.ts +3 -0
  131. package/dist/settings/TerminalSchema.d.ts.map +1 -0
  132. package/dist/settings/TerminalSchema.js +196 -0
  133. package/dist/settings/TerminalSchema.js.map +1 -0
  134. package/dist/settings/UiCustomizationSchema.d.ts +3 -0
  135. package/dist/settings/UiCustomizationSchema.d.ts.map +1 -0
  136. package/dist/settings/UiCustomizationSchema.js +85 -0
  137. package/dist/settings/UiCustomizationSchema.js.map +1 -0
  138. package/dist/settings/constants.d.ts +6 -0
  139. package/dist/settings/constants.d.ts.map +1 -0
  140. package/dist/settings/constants.js +27 -0
  141. package/dist/settings/constants.js.map +1 -0
  142. package/dist/settings/index.d.ts +7 -0
  143. package/dist/settings/index.d.ts.map +1 -0
  144. package/dist/settings/index.js +7 -0
  145. package/dist/settings/index.js.map +1 -0
  146. package/dist/settings/types.d.ts +34 -0
  147. package/dist/settings/types.d.ts.map +1 -0
  148. package/dist/settings/types.js +2 -0
  149. package/dist/settings/types.js.map +1 -0
  150. package/dist/spaces/HeadlessSpaces.d.ts +31 -0
  151. package/dist/spaces/HeadlessSpaces.d.ts.map +1 -0
  152. package/dist/spaces/HeadlessSpaces.js +48 -0
  153. package/dist/spaces/HeadlessSpaces.js.map +1 -0
  154. package/dist/theme/applyTheme.d.ts +4 -0
  155. package/dist/theme/applyTheme.d.ts.map +1 -0
  156. package/dist/theme/applyTheme.js +130 -0
  157. package/dist/theme/applyTheme.js.map +1 -0
  158. package/dist/theme/index.d.ts +3 -0
  159. package/dist/theme/index.d.ts.map +1 -0
  160. package/dist/theme/index.js +3 -0
  161. package/dist/theme/index.js.map +1 -0
  162. package/dist/theme/validateTheme.d.ts +10 -0
  163. package/dist/theme/validateTheme.d.ts.map +1 -0
  164. package/dist/theme/validateTheme.js +158 -0
  165. package/dist/theme/validateTheme.js.map +1 -0
  166. package/dist/theme.d.ts +12 -0
  167. package/dist/theme.d.ts.map +1 -0
  168. package/dist/theme.js +32 -0
  169. package/dist/theme.js.map +1 -0
  170. package/dist/themes/caffeine.d.ts +3 -0
  171. package/dist/themes/caffeine.d.ts.map +1 -0
  172. package/dist/themes/caffeine.js +121 -0
  173. package/dist/themes/caffeine.js.map +1 -0
  174. package/dist/themes/catppuccin.d.ts +3 -0
  175. package/dist/themes/catppuccin.d.ts.map +1 -0
  176. package/dist/themes/catppuccin.js +119 -0
  177. package/dist/themes/catppuccin.js.map +1 -0
  178. package/dist/themes/claude.d.ts +3 -0
  179. package/dist/themes/claude.d.ts.map +1 -0
  180. package/dist/themes/claude.js +121 -0
  181. package/dist/themes/claude.js.map +1 -0
  182. package/dist/themes/gruvbox.d.ts +3 -0
  183. package/dist/themes/gruvbox.d.ts.map +1 -0
  184. package/dist/themes/gruvbox.js +119 -0
  185. package/dist/themes/gruvbox.js.map +1 -0
  186. package/dist/themes/index.d.ts +16 -0
  187. package/dist/themes/index.d.ts.map +1 -0
  188. package/dist/themes/index.js +45 -0
  189. package/dist/themes/index.js.map +1 -0
  190. package/dist/themes/nord.d.ts +3 -0
  191. package/dist/themes/nord.d.ts.map +1 -0
  192. package/dist/themes/nord.js +103 -0
  193. package/dist/themes/nord.js.map +1 -0
  194. package/dist/themes/rose-pine.d.ts +3 -0
  195. package/dist/themes/rose-pine.d.ts.map +1 -0
  196. package/dist/themes/rose-pine.js +119 -0
  197. package/dist/themes/rose-pine.js.map +1 -0
  198. package/dist/themes/sage.d.ts +3 -0
  199. package/dist/themes/sage.d.ts.map +1 -0
  200. package/dist/themes/sage.js +121 -0
  201. package/dist/themes/sage.js.map +1 -0
  202. package/dist/themes/termax-default.d.ts +3 -0
  203. package/dist/themes/termax-default.d.ts.map +1 -0
  204. package/dist/themes/termax-default.js +119 -0
  205. package/dist/themes/termax-default.js.map +1 -0
  206. package/dist/themes/tide.d.ts +3 -0
  207. package/dist/themes/tide.d.ts.map +1 -0
  208. package/dist/themes/tide.js +121 -0
  209. package/dist/themes/tide.js.map +1 -0
  210. package/dist/themes/tokyo-night.d.ts +3 -0
  211. package/dist/themes/tokyo-night.d.ts.map +1 -0
  212. package/dist/themes/tokyo-night.js +64 -0
  213. package/dist/themes/tokyo-night.js.map +1 -0
  214. package/dist/themes/types.d.ts +75 -0
  215. package/dist/themes/types.d.ts.map +1 -0
  216. package/dist/themes/types.js +2 -0
  217. package/dist/themes/types.js.map +1 -0
  218. package/dist/types.d.ts +217 -0
  219. package/dist/types.d.ts.map +1 -0
  220. package/dist/types.js +16 -0
  221. package/dist/types.js.map +1 -0
  222. package/dist/utils/shellQuote.d.ts +3 -0
  223. package/dist/utils/shellQuote.d.ts.map +1 -0
  224. package/dist/utils/shellQuote.js +12 -0
  225. package/dist/utils/shellQuote.js.map +1 -0
  226. package/package.json +69 -0
@@ -0,0 +1,1206 @@
1
+ import { CellFlags, } from '../types.js';
2
+ /**
3
+ * Returns true if the character is a wide (double-width) East Asian character.
4
+ * Covers CJK Unified Ideographs, Hangul, fullwidth forms, and common wide symbols.
5
+ */
6
+ function isWideChar(char) {
7
+ const cp = char.codePointAt(0);
8
+ if (cp === undefined)
9
+ return false;
10
+ return (
11
+ // CJK Unified Ideographs
12
+ (cp >= 0x4E00 && cp <= 0x9FFF) ||
13
+ // CJK Unified Ideographs Extension A
14
+ (cp >= 0x3400 && cp <= 0x4DBF) ||
15
+ // CJK Compatibility Ideographs
16
+ (cp >= 0xF900 && cp <= 0xFAFF) ||
17
+ // CJK Unified Ideographs Extension B-F (Supplementary)
18
+ (cp >= 0x20000 && cp <= 0x2FA2D) ||
19
+ // Hangul Syllables
20
+ (cp >= 0xAC00 && cp <= 0xD7AF) ||
21
+ // Fullwidth Latin, Fullwidth symbols
22
+ (cp >= 0xFF01 && cp <= 0xFF60) ||
23
+ (cp >= 0xFFE0 && cp <= 0xFFE6) ||
24
+ // CJK Symbols and Punctuation
25
+ (cp >= 0x3000 && cp <= 0x303F) ||
26
+ // Hiragana and Katakana
27
+ (cp >= 0x3040 && cp <= 0x30FF) ||
28
+ // Katakana Phonetic Extensions
29
+ (cp >= 0x31F0 && cp <= 0x31FF) ||
30
+ // Enclosed CJK Letters and Months
31
+ (cp >= 0x3200 && cp <= 0x32FF) ||
32
+ // CJK Compatibility
33
+ (cp >= 0x3300 && cp <= 0x33FF) ||
34
+ // Bopomofo
35
+ (cp >= 0x3100 && cp <= 0x312F) ||
36
+ // CJK Radicals Supplement
37
+ (cp >= 0x2E80 && cp <= 0x2EFF) ||
38
+ // Kangxi Radicals
39
+ (cp >= 0x2F00 && cp <= 0x2FDF) ||
40
+ // Emoticons and pictographs (verified against unicode-width)
41
+ (cp >= 0x1F300 && cp <= 0x1F64F) ||
42
+ (cp >= 0x1F680 && cp <= 0x1F6DF) ||
43
+ cp === 0x2705 || cp === 0x274C || cp === 0x2B50 || cp === 0x26A1 ||
44
+ cp === 0x1FA70 || cp === 0x1F9FF ||
45
+ // Angle brackets used in CJK text
46
+ (cp >= 0x2329 && cp <= 0x232A));
47
+ }
48
+ /**
49
+ * Zero-width characters (combining marks, joiners, variation selectors,
50
+ * tags). Verified against unicode-width; kept in sync with the Rust core.
51
+ * The engines skip these rather than combining glyphs (documented limit).
52
+ */
53
+ function isZeroWidthChar(char) {
54
+ const cp = char.codePointAt(0);
55
+ if (cp === undefined)
56
+ return false;
57
+ return ((cp >= 0x0300 && cp <= 0x036F) ||
58
+ (cp >= 0x1AB0 && cp <= 0x1AFF) ||
59
+ (cp >= 0x1DC0 && cp <= 0x1DFF) ||
60
+ (cp >= 0x20D0 && cp <= 0x20E3) ||
61
+ (cp >= 0xFE00 && cp <= 0xFE0F) ||
62
+ (cp >= 0xFE20 && cp <= 0xFE2F) ||
63
+ (cp >= 0x200B && cp <= 0x200F) ||
64
+ (cp >= 0xE0000 && cp <= 0xE0FFF) ||
65
+ cp === 0x00AD || cp === 0x2060 || cp === 0x061C ||
66
+ (cp >= 0x2066 && cp <= 0x2069) ||
67
+ (cp >= 0x3099 && cp <= 0x309A) ||
68
+ (cp >= 0xFF9E && cp <= 0xFF9F));
69
+ }
70
+ export class VtEngine {
71
+ cols;
72
+ rows;
73
+ maxScrollback = 10000;
74
+ lines = [];
75
+ scrollback = [];
76
+ viewportY = 0; // 0 to scrollback.length
77
+ // Alternate screen buffer
78
+ isAltBuffer = false;
79
+ normalLines = [];
80
+ normalScrollback = [];
81
+ normalCursor = {
82
+ col: 0,
83
+ row: 0,
84
+ visible: true,
85
+ blinking: true,
86
+ shape: 'block',
87
+ };
88
+ altLines = [];
89
+ altCursor = {
90
+ col: 0,
91
+ row: 0,
92
+ visible: true,
93
+ blinking: true,
94
+ shape: 'block',
95
+ };
96
+ // Mouse & Focus modes
97
+ tabStops = Array.from({ length: 500 }, (_, i) => i % 8 === 0);
98
+ savedFlags = CellFlags.EMPTY;
99
+ savedFg = { type: 'default' };
100
+ savedBg = { type: 'default' };
101
+ saveCursor() {
102
+ this.savedCursor = { ...this.cursor };
103
+ this.savedFlags = this.currentFlags;
104
+ this.savedFg = { ...this.currentFg };
105
+ this.savedBg = { ...this.currentBg };
106
+ }
107
+ restoreCursor() {
108
+ this.cursor.col = Math.min(this.cols - 1, this.savedCursor.col);
109
+ this.cursor.row = Math.min(this.rows - 1, this.savedCursor.row);
110
+ this.currentFlags = this.savedFlags;
111
+ this.currentFg = { ...this.savedFg };
112
+ this.currentBg = { ...this.savedBg };
113
+ }
114
+ scrollDown(count = 1) {
115
+ for (let k = 0; k < count; k++) {
116
+ this.lines.splice(this.scrollBottom, 1);
117
+ this.lines.splice(this.scrollTop, 0, this.createEmptyLine());
118
+ }
119
+ for (let r = this.scrollTop; r <= this.scrollBottom; r++) {
120
+ this.dirtyRowSet.add(r);
121
+ }
122
+ }
123
+ mouseMode = {
124
+ x10: false,
125
+ button: false,
126
+ any: false,
127
+ sgr: false,
128
+ };
129
+ focusReporting = false;
130
+ bracketedPasteMode = false;
131
+ synchronizedOutput = false;
132
+ cursorShape = 'block';
133
+ cursorBlinking = true;
134
+ autowrap = true;
135
+ originMode = false;
136
+ // Autowrap-pending: cursor is at the right margin but has not yet wrapped.
137
+ // The next printable character triggers the wrap.
138
+ wrapPending = false;
139
+ // Last printed character for REP (CSI b)
140
+ lastPrintedChar = '';
141
+ // Hyperlink state
142
+ currentHyperlink;
143
+ cursor = {
144
+ col: 0,
145
+ row: 0,
146
+ visible: true,
147
+ blinking: true,
148
+ shape: 'block',
149
+ };
150
+ savedCursor = { col: 0, row: 0 };
151
+ scrollTop = 0;
152
+ scrollBottom = 23;
153
+ currentFg = { type: 'default' };
154
+ currentBg = { type: 'default' };
155
+ currentFlags = CellFlags.EMPTY;
156
+ currentUnderlineColor;
157
+ dirtyRowSet = new Set();
158
+ events;
159
+ // Parser state
160
+ state = 'GROUND';
161
+ csiParams = '';
162
+ oscBuffer = '';
163
+ // Streaming UTF-8 decoder: a multibyte character may straddle two writes.
164
+ utf8Decoder = new TextDecoder();
165
+ constructor(cols = 80, rows = 24, events = {}) {
166
+ this.cols = Math.max(1, cols);
167
+ this.rows = Math.max(1, rows);
168
+ this.scrollBottom = this.rows - 1;
169
+ this.events = events;
170
+ this.initGrid();
171
+ }
172
+ flushDiff() {
173
+ const dirtyRows = [];
174
+ for (const r of this.dirtyRowSet) {
175
+ if (r < this.lines.length) {
176
+ dirtyRows.push({
177
+ row: r,
178
+ cells: [...this.lines[r]],
179
+ });
180
+ }
181
+ }
182
+ this.dirtyRowSet.clear();
183
+ return {
184
+ cols: this.cols,
185
+ rows: this.rows,
186
+ cursor: {
187
+ ...this.cursor,
188
+ shape: this.cursorShape,
189
+ blinking: this.cursorBlinking,
190
+ },
191
+ dirtyRows,
192
+ };
193
+ }
194
+ feed(input) {
195
+ this.parse(input);
196
+ return this.flushDiff();
197
+ }
198
+ resize(cols, rows) {
199
+ const oldCols = this.cols;
200
+ const oldRows = this.rows;
201
+ const newCols = Math.max(1, cols);
202
+ const newRows = Math.max(1, rows);
203
+ if (newCols === oldCols && newRows === oldRows) {
204
+ return this.flushDiff();
205
+ }
206
+ this.cols = newCols;
207
+ this.rows = newRows;
208
+ this.scrollTop = 0;
209
+ this.scrollBottom = this.rows - 1;
210
+ // Resize existing lines to match newCols
211
+ const nextLines = [];
212
+ for (let r = 0; r < this.lines.length; r++) {
213
+ const line = this.lines[r];
214
+ if (line.length < newCols) {
215
+ const padded = [...line];
216
+ for (let c = line.length; c < newCols; c++) {
217
+ padded.push({
218
+ char: ' ',
219
+ width: 1,
220
+ flags: CellFlags.EMPTY,
221
+ fg: { type: 'default' },
222
+ bg: { type: 'default' },
223
+ });
224
+ }
225
+ nextLines.push(padded);
226
+ }
227
+ else {
228
+ nextLines.push(line.slice(0, newCols));
229
+ }
230
+ }
231
+ if (newRows > nextLines.length) {
232
+ while (nextLines.length < newRows) {
233
+ nextLines.push(this.createEmptyLine());
234
+ }
235
+ }
236
+ else if (newRows < nextLines.length) {
237
+ const excess = nextLines.length - newRows;
238
+ for (let i = 0; i < excess; i++) {
239
+ const topRow = nextLines.shift();
240
+ if (topRow && !this.isAltBuffer) {
241
+ this.scrollback.push(topRow);
242
+ if (this.scrollback.length > this.maxScrollback) {
243
+ this.scrollback.shift();
244
+ }
245
+ }
246
+ }
247
+ }
248
+ this.lines = nextLines;
249
+ this.cursor.col = Math.min(this.cols - 1, this.cursor.col);
250
+ this.cursor.row = Math.min(this.rows - 1, this.cursor.row);
251
+ this.viewportY = this.scrollback.length;
252
+ for (let r = 0; r < this.rows; r++) {
253
+ this.dirtyRowSet.add(r);
254
+ }
255
+ return this.flushDiff();
256
+ }
257
+ scrollLines(delta) {
258
+ if (this.isAltBuffer)
259
+ return; // Alt buffer has no scrollback
260
+ const prev = this.viewportY;
261
+ this.viewportY = Math.max(0, Math.min(this.scrollback.length, this.viewportY + delta));
262
+ if (prev !== this.viewportY) {
263
+ for (let r = 0; r < this.rows; r++)
264
+ this.dirtyRowSet.add(r);
265
+ }
266
+ }
267
+ scrollToBottom() {
268
+ this.scrollLines(this.scrollback.length - this.viewportY);
269
+ }
270
+ scrollToTop() {
271
+ this.scrollLines(-this.viewportY);
272
+ }
273
+ scrollToLine(line) {
274
+ const target = Math.max(0, Math.min(this.scrollback.length, line));
275
+ this.scrollLines(target - this.viewportY);
276
+ }
277
+ getVisibleLines() {
278
+ const visible = [];
279
+ for (let r = 0; r < this.rows; r++) {
280
+ const idx = this.viewportY + r;
281
+ if (idx < this.scrollback.length) {
282
+ visible.push(this.scrollback[idx]);
283
+ }
284
+ else {
285
+ const gridIdx = idx - this.scrollback.length;
286
+ if (gridIdx < this.lines.length) {
287
+ visible.push(this.lines[gridIdx]);
288
+ }
289
+ else {
290
+ visible.push(this.createEmptyLine());
291
+ }
292
+ }
293
+ }
294
+ return visible;
295
+ }
296
+ initGrid() {
297
+ this.lines = [];
298
+ for (let r = 0; r < this.rows; r++) {
299
+ this.lines.push(this.createEmptyLine());
300
+ this.dirtyRowSet.add(r);
301
+ }
302
+ }
303
+ createEmptyLine() {
304
+ const line = new Array(this.cols);
305
+ for (let c = 0; c < this.cols; c++) {
306
+ line[c] = {
307
+ char: ' ',
308
+ width: 1,
309
+ flags: CellFlags.EMPTY,
310
+ fg: { type: 'default' },
311
+ bg: { type: 'default' },
312
+ };
313
+ }
314
+ return line;
315
+ }
316
+ parse(input) {
317
+ const text = typeof input === 'string' ? input : this.utf8Decoder.decode(input, { stream: true });
318
+ for (const char of text) {
319
+ const code = char.codePointAt(0) ?? 0;
320
+ switch (this.state) {
321
+ case 'GROUND':
322
+ if (char === '\x1b') {
323
+ this.state = 'ESC';
324
+ }
325
+ else if (char === '\r') {
326
+ this.cursor.col = 0;
327
+ this.wrapPending = false;
328
+ }
329
+ else if (char === '\n') {
330
+ this.lineFeed();
331
+ }
332
+ else if (char === '\b') {
333
+ this.wrapPending = false;
334
+ this.cursor.col = Math.max(0, this.cursor.col - 1);
335
+ }
336
+ else if (char === '\t') {
337
+ if (this.wrapPending)
338
+ this.wrapPending = false;
339
+ // Use custom tab stops set via HTS (ESC H)
340
+ let nextStop = this.cols - 1;
341
+ for (let t = this.cursor.col + 1; t < this.tabStops.length && t < this.cols; t++) {
342
+ if (this.tabStops[t]) {
343
+ nextStop = t;
344
+ break;
345
+ }
346
+ }
347
+ this.cursor.col = Math.min(this.cols - 1, nextStop);
348
+ }
349
+ else if (char === '\x07') {
350
+ this.events.onBell?.();
351
+ }
352
+ else if (code >= 32 && code !== 127) {
353
+ this.printChar(char);
354
+ }
355
+ break;
356
+ case 'ESC':
357
+ if (char === '[') {
358
+ this.state = 'CSI';
359
+ this.csiParams = '';
360
+ }
361
+ else if (char === ']') {
362
+ this.state = 'OSC';
363
+ this.oscBuffer = '';
364
+ }
365
+ else if (char === '7') {
366
+ this.saveCursor();
367
+ this.state = 'GROUND';
368
+ }
369
+ else if (char === '8') {
370
+ this.restoreCursor();
371
+ this.state = 'GROUND';
372
+ }
373
+ else if (char === 'D') {
374
+ // IND - Index (move down, scroll if at bottom)
375
+ this.lineFeed();
376
+ this.state = 'GROUND';
377
+ }
378
+ else if (char === 'M') {
379
+ // RI - Reverse Index (move up, scroll down if at top)
380
+ if (this.cursor.row === this.scrollTop) {
381
+ this.scrollDown(1);
382
+ }
383
+ else if (this.cursor.row > 0) {
384
+ this.cursor.row--;
385
+ }
386
+ this.state = 'GROUND';
387
+ }
388
+ else if (char === 'E') {
389
+ // NEL - Next Line
390
+ this.cursor.col = 0;
391
+ this.lineFeed();
392
+ this.state = 'GROUND';
393
+ }
394
+ else if (char === 'H') {
395
+ // HTS - Horizontal Tab Set
396
+ const col = this.cursor.col;
397
+ if (col < this.tabStops.length) {
398
+ this.tabStops[col] = true;
399
+ }
400
+ this.state = 'GROUND';
401
+ }
402
+ else if (char === 'c') {
403
+ this.reset();
404
+ this.state = 'GROUND';
405
+ }
406
+ else {
407
+ this.events.onUnhandledSequence?.(`\\x1b${char}`);
408
+ this.state = 'GROUND';
409
+ }
410
+ break;
411
+ case 'CSI':
412
+ if ((code >= 48 && code <= 57) || char === ';' || char === ':' || char === '?' || char === ' ' || char === '>') {
413
+ this.csiParams += char;
414
+ }
415
+ else {
416
+ this.handleCsi(char);
417
+ this.state = 'GROUND';
418
+ }
419
+ break;
420
+ case 'OSC':
421
+ if (char === '\x07') {
422
+ this.handleOsc(this.oscBuffer);
423
+ this.state = 'GROUND';
424
+ }
425
+ else if (char === '\x1b') {
426
+ this.state = 'OSC_ESC';
427
+ }
428
+ else {
429
+ this.oscBuffer += char;
430
+ }
431
+ break;
432
+ case 'OSC_ESC':
433
+ if (char === '\\') {
434
+ this.handleOsc(this.oscBuffer);
435
+ this.state = 'GROUND';
436
+ }
437
+ else {
438
+ this.state = 'GROUND';
439
+ }
440
+ break;
441
+ }
442
+ }
443
+ }
444
+ printChar(char) {
445
+ // Zero-width marks are skipped (not combined), mirroring the Rust core
446
+ // which drops zero-width chars to keep grid math sane.
447
+ if (isZeroWidthChar(char))
448
+ return;
449
+ const wide = isWideChar(char);
450
+ const charWidth = wide ? 2 : 1;
451
+ // Deferred autowrap: if wrapPending, the NEXT printable char triggers the wrap
452
+ if (this.wrapPending) {
453
+ this.wrapPending = false;
454
+ if (this.autowrap) {
455
+ this.cursor.col = 0;
456
+ this.lineFeed();
457
+ }
458
+ else {
459
+ this.cursor.col = this.cols - 1;
460
+ }
461
+ }
462
+ // Wide char at last column: wrap first
463
+ if (wide && this.cursor.col >= this.cols - 1) {
464
+ // Fill current cell with space, then wrap
465
+ if (this.cursor.col < this.cols) {
466
+ const r = this.cursor.row;
467
+ this.lines[r][this.cursor.col] = {
468
+ char: ' ',
469
+ width: 1,
470
+ flags: CellFlags.EMPTY,
471
+ fg: { type: 'default' },
472
+ bg: { type: 'default' },
473
+ };
474
+ this.dirtyRowSet.add(r);
475
+ }
476
+ this.cursor.col = 0;
477
+ this.lineFeed();
478
+ }
479
+ const r = this.cursor.row;
480
+ const c = this.cursor.col;
481
+ if (r < this.lines.length && c < this.cols) {
482
+ this.lines[r][c] = {
483
+ char,
484
+ width: charWidth,
485
+ flags: this.currentFlags,
486
+ fg: { ...this.currentFg },
487
+ bg: { ...this.currentBg },
488
+ hyperlink: this.currentHyperlink,
489
+ underlineColor: this.currentUnderlineColor,
490
+ };
491
+ // For wide chars, the second cell is a zero-width continuation
492
+ if (wide && c + 1 < this.cols) {
493
+ this.lines[r][c + 1] = {
494
+ char: '',
495
+ width: 0,
496
+ flags: this.currentFlags,
497
+ fg: { ...this.currentFg },
498
+ bg: { ...this.currentBg },
499
+ };
500
+ }
501
+ this.dirtyRowSet.add(r);
502
+ }
503
+ this.lastPrintedChar = char;
504
+ this.cursor.col += charWidth;
505
+ // If cursor is now at or past the right margin, enter wrap-pending state
506
+ if (this.cursor.col >= this.cols) {
507
+ this.cursor.col = this.cols - 1;
508
+ this.wrapPending = true;
509
+ }
510
+ }
511
+ lineFeed() {
512
+ if (this.cursor.row === this.scrollBottom) {
513
+ // Cursor is at the bottom of the scroll region: scroll up within the region
514
+ this.scrollUp(1);
515
+ }
516
+ else if (this.cursor.row >= this.rows - 1) {
517
+ // Cursor is at the absolute bottom of the screen: do nothing (clamped)
518
+ }
519
+ else if (this.cursor.row > this.scrollBottom) {
520
+ // Cursor is below the scroll region: just move down, no scrolling
521
+ this.cursor.row++;
522
+ }
523
+ else {
524
+ // Cursor is within or above the scroll region: just move down
525
+ this.cursor.row++;
526
+ }
527
+ }
528
+ scrollUp(count) {
529
+ for (let k = 0; k < count; k++) {
530
+ const removed = this.lines.splice(this.scrollTop, 1)[0];
531
+ if (removed && !this.isAltBuffer) {
532
+ this.scrollback.push(removed);
533
+ if (this.scrollback.length > this.maxScrollback) {
534
+ this.scrollback.shift();
535
+ }
536
+ }
537
+ this.lines.splice(this.scrollBottom, 0, this.createEmptyLine());
538
+ }
539
+ for (let r = this.scrollTop; r <= this.scrollBottom; r++) {
540
+ this.dirtyRowSet.add(r);
541
+ }
542
+ }
543
+ handleCsi(finalChar) {
544
+ const isPrivate = this.csiParams.startsWith('?');
545
+ const paramClean = this.csiParams.replace(/^[?]/, '').trim();
546
+ const args = paramClean
547
+ ? paramClean.split(/[;:]/).map((v) => parseInt(v, 10) || 0)
548
+ : [];
549
+ switch (finalChar) {
550
+ case '@': // ICH - Insert Character
551
+ {
552
+ const count = args[0] || 1;
553
+ const r = this.cursor.row;
554
+ const c = this.cursor.col;
555
+ if (r < this.lines.length) {
556
+ const line = this.lines[r];
557
+ const emptyCells = [];
558
+ for (let i = 0; i < count; i++) {
559
+ emptyCells.push({
560
+ char: ' ',
561
+ width: 1,
562
+ flags: this.currentFlags,
563
+ fg: { ...this.currentFg },
564
+ bg: { ...this.currentBg },
565
+ });
566
+ }
567
+ line.splice(c, 0, ...emptyCells);
568
+ line.length = this.cols;
569
+ this.dirtyRowSet.add(r);
570
+ }
571
+ }
572
+ break;
573
+ case 'P': // DCH - Delete Character
574
+ {
575
+ const count = args[0] || 1;
576
+ const r = this.cursor.row;
577
+ const c = this.cursor.col;
578
+ if (r < this.lines.length) {
579
+ const line = this.lines[r];
580
+ line.splice(c, count);
581
+ while (line.length < this.cols) {
582
+ line.push({
583
+ char: ' ',
584
+ width: 1,
585
+ flags: CellFlags.EMPTY,
586
+ fg: { type: 'default' },
587
+ bg: { type: 'default' },
588
+ });
589
+ }
590
+ this.dirtyRowSet.add(r);
591
+ }
592
+ }
593
+ break;
594
+ case 'X': // ECH - Erase Character
595
+ {
596
+ const count = args[0] || 1;
597
+ const r = this.cursor.row;
598
+ const c = this.cursor.col;
599
+ if (r < this.lines.length) {
600
+ const line = this.lines[r];
601
+ const end = Math.min(this.cols, c + count);
602
+ for (let col = c; col < end; col++) {
603
+ line[col] = {
604
+ char: ' ',
605
+ width: 1,
606
+ flags: this.currentFlags,
607
+ fg: { ...this.currentFg },
608
+ bg: { ...this.currentBg },
609
+ };
610
+ }
611
+ this.dirtyRowSet.add(r);
612
+ }
613
+ }
614
+ break;
615
+ case 'L': // IL - Insert Line
616
+ {
617
+ const count = args[0] || 1;
618
+ const curRow = this.cursor.row;
619
+ if (curRow >= this.scrollTop && curRow <= this.scrollBottom) {
620
+ for (let k = 0; k < count; k++) {
621
+ this.lines.splice(this.scrollBottom, 1);
622
+ this.lines.splice(curRow, 0, this.createEmptyLine());
623
+ }
624
+ for (let r = curRow; r <= this.scrollBottom; r++) {
625
+ this.dirtyRowSet.add(r);
626
+ }
627
+ }
628
+ }
629
+ break;
630
+ case 'M': // DL - Delete Line
631
+ {
632
+ const count = args[0] || 1;
633
+ const curRow = this.cursor.row;
634
+ if (curRow >= this.scrollTop && curRow <= this.scrollBottom) {
635
+ for (let k = 0; k < count; k++) {
636
+ this.lines.splice(curRow, 1);
637
+ this.lines.splice(this.scrollBottom, 0, this.createEmptyLine());
638
+ }
639
+ for (let r = curRow; r <= this.scrollBottom; r++) {
640
+ this.dirtyRowSet.add(r);
641
+ }
642
+ }
643
+ }
644
+ break;
645
+ case 'S': // SU - Scroll Up
646
+ {
647
+ const count = args[0] || 1;
648
+ this.scrollUp(count);
649
+ }
650
+ break;
651
+ case 'T': // SD - Scroll Down
652
+ {
653
+ const count = args[0] || 1;
654
+ this.scrollDown(count);
655
+ }
656
+ break;
657
+ case 's': // Save Cursor
658
+ this.saveCursor();
659
+ break;
660
+ case 'u': // Restore Cursor
661
+ this.restoreCursor();
662
+ this.wrapPending = false;
663
+ break;
664
+ case 'n': // DSR - Device Status Report
665
+ {
666
+ const code = args[0] || 0;
667
+ if (code === 5) {
668
+ this.events.onPtyWrite?.('\x1b[0n');
669
+ }
670
+ else if (code === 6) {
671
+ const row = this.cursor.row + 1;
672
+ const col = this.cursor.col + 1;
673
+ this.events.onPtyWrite?.(`\x1b[${row};${col}R`);
674
+ }
675
+ }
676
+ break;
677
+ case 'c': // DA - Device Attributes
678
+ {
679
+ if (isPrivate || this.csiParams.startsWith('>')) {
680
+ this.events.onPtyWrite?.('\x1b[>0;10;1c');
681
+ }
682
+ else {
683
+ this.events.onPtyWrite?.('\x1b[?1;2c');
684
+ }
685
+ }
686
+ break;
687
+ case 'm': // SGR
688
+ this.handleSgr(args);
689
+ break;
690
+ case 'H': // CUP
691
+ case 'f':
692
+ {
693
+ const row = (args[0] || 1) - 1;
694
+ const col = (args[1] || 1) - 1;
695
+ if (this.originMode) {
696
+ this.cursor.row = Math.min(this.scrollBottom, this.scrollTop + Math.max(0, row));
697
+ }
698
+ else {
699
+ this.cursor.row = Math.max(0, Math.min(this.rows - 1, row));
700
+ }
701
+ this.cursor.col = Math.max(0, Math.min(this.cols - 1, col));
702
+ this.wrapPending = false;
703
+ }
704
+ break;
705
+ case 'A': // CUU
706
+ {
707
+ const top = this.originMode ? this.scrollTop : 0;
708
+ this.cursor.row = Math.max(top, this.cursor.row - (args[0] || 1));
709
+ this.wrapPending = false;
710
+ }
711
+ break;
712
+ case 'B': // CUD
713
+ {
714
+ const bottom = this.originMode ? this.scrollBottom : this.rows - 1;
715
+ this.cursor.row = Math.min(bottom, this.cursor.row + (args[0] || 1));
716
+ this.wrapPending = false;
717
+ }
718
+ break;
719
+ case 'C': // CUF
720
+ this.cursor.col = Math.min(this.cols - 1, this.cursor.col + (args[0] || 1));
721
+ this.wrapPending = false;
722
+ break;
723
+ case 'D': // CUB
724
+ this.cursor.col = Math.max(0, this.cursor.col - (args[0] || 1));
725
+ this.wrapPending = false;
726
+ break;
727
+ case 'E': // CNL - Cursor Next Line
728
+ this.cursor.row = Math.min(this.rows - 1, this.cursor.row + (args[0] || 1));
729
+ this.cursor.col = 0;
730
+ this.wrapPending = false;
731
+ break;
732
+ case 'F': // CPL - Cursor Previous Line
733
+ this.cursor.row = Math.max(0, this.cursor.row - (args[0] || 1));
734
+ this.cursor.col = 0;
735
+ this.wrapPending = false;
736
+ break;
737
+ case 'G': // CHA
738
+ this.cursor.col = Math.max(0, Math.min(this.cols - 1, (args[0] || 1) - 1));
739
+ this.wrapPending = false;
740
+ break;
741
+ case 'I': // CHT - Cursor Horizontal Tab
742
+ {
743
+ let count = args[0] || 1;
744
+ let col = this.cursor.col;
745
+ for (let t = 0; t < count; t++) {
746
+ let found = false;
747
+ for (let c = col + 1; c < this.cols; c++) {
748
+ if (c < this.tabStops.length && this.tabStops[c]) {
749
+ col = c;
750
+ found = true;
751
+ break;
752
+ }
753
+ }
754
+ if (!found) {
755
+ col = this.cols - 1;
756
+ break;
757
+ }
758
+ }
759
+ this.cursor.col = col;
760
+ this.wrapPending = false;
761
+ }
762
+ break;
763
+ case 'Z': // CBT - Cursor Backward Tab
764
+ {
765
+ let count = args[0] || 1;
766
+ let col = this.cursor.col;
767
+ for (let t = 0; t < count; t++) {
768
+ let found = false;
769
+ for (let c = col - 1; c >= 0; c--) {
770
+ if (c < this.tabStops.length && this.tabStops[c]) {
771
+ col = c;
772
+ found = true;
773
+ break;
774
+ }
775
+ }
776
+ if (!found) {
777
+ col = 0;
778
+ break;
779
+ }
780
+ }
781
+ this.cursor.col = col;
782
+ this.wrapPending = false;
783
+ }
784
+ break;
785
+ case 'b': // REP - Repeat last printed character
786
+ {
787
+ const count = args[0] || 1;
788
+ if (this.lastPrintedChar) {
789
+ for (let k = 0; k < count; k++) {
790
+ this.printChar(this.lastPrintedChar);
791
+ }
792
+ }
793
+ }
794
+ break;
795
+ case 'd': // VPA
796
+ this.cursor.row = Math.max(0, Math.min(this.rows - 1, (args[0] || 1) - 1));
797
+ this.wrapPending = false;
798
+ break;
799
+ case 'J': // ED
800
+ {
801
+ const mode = args[0] || 0;
802
+ if (mode === 0) {
803
+ this.clearLineRange(this.cursor.row, this.cursor.col, this.cols - 1);
804
+ for (let r = this.cursor.row + 1; r < this.rows; r++) {
805
+ this.clearLine(r);
806
+ }
807
+ }
808
+ else if (mode === 1) {
809
+ for (let r = 0; r < this.cursor.row; r++) {
810
+ this.clearLine(r);
811
+ }
812
+ this.clearLineRange(this.cursor.row, 0, this.cursor.col);
813
+ }
814
+ else if (mode === 2 || mode === 3) {
815
+ for (let r = 0; r < this.rows; r++) {
816
+ this.clearLine(r);
817
+ }
818
+ if (mode === 3 && !this.isAltBuffer)
819
+ this.scrollback = [];
820
+ }
821
+ }
822
+ break;
823
+ case 'K': // EL
824
+ {
825
+ const mode = args[0] || 0;
826
+ if (mode === 0) {
827
+ this.clearLineRange(this.cursor.row, this.cursor.col, this.cols - 1);
828
+ }
829
+ else if (mode === 1) {
830
+ this.clearLineRange(this.cursor.row, 0, this.cursor.col);
831
+ }
832
+ else if (mode === 2) {
833
+ this.clearLine(this.cursor.row);
834
+ }
835
+ }
836
+ break;
837
+ case 'r': // DECSTBM
838
+ {
839
+ const top = (args[0] || 1) - 1;
840
+ const bottom = (args[1] || this.rows) - 1;
841
+ if (top < bottom && bottom < this.rows) {
842
+ this.scrollTop = top;
843
+ this.scrollBottom = bottom;
844
+ }
845
+ else {
846
+ this.scrollTop = 0;
847
+ this.scrollBottom = this.rows - 1;
848
+ }
849
+ this.cursor.row = 0;
850
+ this.cursor.col = 0;
851
+ }
852
+ break;
853
+ case 'q': // DECSCUSR (Set Cursor Style)
854
+ {
855
+ const n = args[0] || 0;
856
+ if (n === 0 || n === 1) {
857
+ this.cursorShape = 'block';
858
+ this.cursorBlinking = true;
859
+ }
860
+ else if (n === 2) {
861
+ this.cursorShape = 'block';
862
+ this.cursorBlinking = false;
863
+ }
864
+ else if (n === 3) {
865
+ this.cursorShape = 'underline';
866
+ this.cursorBlinking = true;
867
+ }
868
+ else if (n === 4) {
869
+ this.cursorShape = 'underline';
870
+ this.cursorBlinking = false;
871
+ }
872
+ else if (n === 5) {
873
+ this.cursorShape = 'bar';
874
+ this.cursorBlinking = true;
875
+ }
876
+ else if (n === 6) {
877
+ this.cursorShape = 'bar';
878
+ this.cursorBlinking = false;
879
+ }
880
+ }
881
+ break;
882
+ case 'g': // TBC - Tab Clear
883
+ {
884
+ const mode = args[0] || 0;
885
+ if (mode === 0) {
886
+ // Clear tab stop at current position
887
+ if (this.cursor.col < this.tabStops.length) {
888
+ this.tabStops[this.cursor.col] = false;
889
+ }
890
+ }
891
+ else if (mode === 3) {
892
+ // Clear all tab stops
893
+ this.tabStops.fill(false);
894
+ }
895
+ }
896
+ break;
897
+ case 'h': // Set Mode
898
+ if (isPrivate) {
899
+ if (args.includes(25))
900
+ this.cursor.visible = true;
901
+ if (args.includes(6))
902
+ this.originMode = true;
903
+ if (args.includes(7))
904
+ this.autowrap = true;
905
+ // Alternate Screen Buffer (1049, 1047, 47)
906
+ if (args.includes(1049) || args.includes(1047) || args.includes(47)) {
907
+ this.enterAltBuffer();
908
+ }
909
+ // Mouse modes
910
+ if (args.includes(1000))
911
+ this.mouseMode.x10 = true;
912
+ if (args.includes(1002))
913
+ this.mouseMode.button = true;
914
+ if (args.includes(1003))
915
+ this.mouseMode.any = true;
916
+ if (args.includes(1006))
917
+ this.mouseMode.sgr = true;
918
+ // Focus reporting
919
+ if (args.includes(1004))
920
+ this.focusReporting = true;
921
+ // Bracketed paste (2004)
922
+ if (args.includes(2004))
923
+ this.bracketedPasteMode = true;
924
+ // Synchronized output (2026)
925
+ if (args.includes(2026)) {
926
+ this.synchronizedOutput = true;
927
+ this.events.onSyncOutput?.(true);
928
+ }
929
+ }
930
+ break;
931
+ case 'l': // Reset Mode
932
+ if (isPrivate) {
933
+ if (args.includes(25))
934
+ this.cursor.visible = false;
935
+ if (args.includes(6))
936
+ this.originMode = false;
937
+ if (args.includes(7))
938
+ this.autowrap = false;
939
+ // Alternate Screen Buffer (1049, 1047, 47)
940
+ if (args.includes(1049) || args.includes(1047) || args.includes(47)) {
941
+ this.exitAltBuffer();
942
+ }
943
+ // Mouse modes
944
+ if (args.includes(1000))
945
+ this.mouseMode.x10 = false;
946
+ if (args.includes(1002))
947
+ this.mouseMode.button = false;
948
+ if (args.includes(1003))
949
+ this.mouseMode.any = false;
950
+ if (args.includes(1006))
951
+ this.mouseMode.sgr = false;
952
+ // Focus reporting
953
+ if (args.includes(1004))
954
+ this.focusReporting = false;
955
+ // Bracketed paste (2004)
956
+ if (args.includes(2004))
957
+ this.bracketedPasteMode = false;
958
+ // Synchronized output (2026)
959
+ if (args.includes(2026)) {
960
+ this.synchronizedOutput = false;
961
+ for (let r = 0; r < this.rows; r++)
962
+ this.dirtyRowSet.add(r);
963
+ this.events.onSyncOutput?.(false);
964
+ }
965
+ }
966
+ break;
967
+ default:
968
+ this.events.onUnhandledSequence?.(`\x1b[${this.csiParams}${finalChar}`);
969
+ break;
970
+ }
971
+ }
972
+ enterAltBuffer() {
973
+ if (this.isAltBuffer)
974
+ return;
975
+ this.isAltBuffer = true;
976
+ this.normalLines = this.lines;
977
+ this.normalScrollback = this.scrollback;
978
+ this.normalCursor = { ...this.cursor };
979
+ this.altLines = [];
980
+ for (let r = 0; r < this.rows; r++) {
981
+ this.altLines.push(this.createEmptyLine());
982
+ }
983
+ this.lines = this.altLines;
984
+ this.scrollback = [];
985
+ this.viewportY = 0;
986
+ this.cursor = { ...this.altCursor };
987
+ for (let r = 0; r < this.rows; r++) {
988
+ this.dirtyRowSet.add(r);
989
+ }
990
+ }
991
+ exitAltBuffer() {
992
+ if (!this.isAltBuffer)
993
+ return;
994
+ this.isAltBuffer = false;
995
+ this.altCursor = { ...this.cursor };
996
+ this.lines = this.normalLines;
997
+ this.scrollback = this.normalScrollback;
998
+ this.cursor = { ...this.normalCursor };
999
+ this.viewportY = this.scrollback.length;
1000
+ for (let r = 0; r < this.rows; r++) {
1001
+ this.dirtyRowSet.add(r);
1002
+ }
1003
+ }
1004
+ handleSgr(args) {
1005
+ if (args.length === 0)
1006
+ args = [0];
1007
+ let i = 0;
1008
+ while (i < args.length) {
1009
+ const code = args[i++];
1010
+ if (code === 0) {
1011
+ this.currentFlags = CellFlags.EMPTY;
1012
+ this.currentFg = { type: 'default' };
1013
+ this.currentBg = { type: 'default' };
1014
+ this.currentUnderlineColor = undefined;
1015
+ }
1016
+ else if (code === 1)
1017
+ this.currentFlags |= CellFlags.BOLD;
1018
+ else if (code === 2)
1019
+ this.currentFlags |= CellFlags.DIM;
1020
+ else if (code === 3)
1021
+ this.currentFlags |= CellFlags.ITALIC;
1022
+ else if (code === 4) {
1023
+ // Check sub-parameters for 4:x undercurl styles if followed by subcodes
1024
+ if (args[i] === 3) {
1025
+ this.currentFlags |= CellFlags.UNDERCURL;
1026
+ i++;
1027
+ }
1028
+ else if (args[i] === 4) {
1029
+ this.currentFlags |= CellFlags.UNDERLINE_DOTTED;
1030
+ i++;
1031
+ }
1032
+ else if (args[i] === 5) {
1033
+ this.currentFlags |= CellFlags.UNDERLINE_DASHED;
1034
+ i++;
1035
+ }
1036
+ else {
1037
+ this.currentFlags |= CellFlags.UNDERLINE;
1038
+ }
1039
+ }
1040
+ else if (code === 5)
1041
+ this.currentFlags |= CellFlags.BLINK;
1042
+ else if (code === 7)
1043
+ this.currentFlags |= CellFlags.INVERSE;
1044
+ else if (code === 8)
1045
+ this.currentFlags |= CellFlags.HIDDEN;
1046
+ else if (code === 9)
1047
+ this.currentFlags |= CellFlags.STRIKETHROUGH;
1048
+ else if (code === 22)
1049
+ this.currentFlags &= ~(CellFlags.BOLD | CellFlags.DIM);
1050
+ else if (code === 23)
1051
+ this.currentFlags &= ~CellFlags.ITALIC;
1052
+ else if (code === 24) {
1053
+ this.currentFlags &= ~(CellFlags.UNDERLINE | CellFlags.UNDERCURL | CellFlags.UNDERLINE_DOTTED | CellFlags.UNDERLINE_DASHED);
1054
+ }
1055
+ else if (code === 25)
1056
+ this.currentFlags &= ~CellFlags.BLINK;
1057
+ else if (code === 27)
1058
+ this.currentFlags &= ~CellFlags.INVERSE;
1059
+ else if (code === 28)
1060
+ this.currentFlags &= ~CellFlags.HIDDEN;
1061
+ else if (code === 29)
1062
+ this.currentFlags &= ~CellFlags.STRIKETHROUGH;
1063
+ else if (code >= 30 && code <= 37) {
1064
+ this.currentFg = { type: 'indexed', index: code - 30 };
1065
+ }
1066
+ else if (code === 38) {
1067
+ if (args[i] === 5) {
1068
+ this.currentFg = { type: 'indexed', index: args[i + 1] };
1069
+ i += 2;
1070
+ }
1071
+ else if (args[i] === 2) {
1072
+ this.currentFg = { type: 'rgb', r: args[i + 1], g: args[i + 2], b: args[i + 3] };
1073
+ i += 4;
1074
+ }
1075
+ }
1076
+ else if (code === 39) {
1077
+ this.currentFg = { type: 'default' };
1078
+ }
1079
+ else if (code >= 40 && code <= 47) {
1080
+ this.currentBg = { type: 'indexed', index: code - 40 };
1081
+ }
1082
+ else if (code === 48) {
1083
+ if (args[i] === 5) {
1084
+ this.currentBg = { type: 'indexed', index: args[i + 1] };
1085
+ i += 2;
1086
+ }
1087
+ else if (args[i] === 2) {
1088
+ this.currentBg = { type: 'rgb', r: args[i + 1], g: args[i + 2], b: args[i + 3] };
1089
+ i += 4;
1090
+ }
1091
+ }
1092
+ else if (code === 49) {
1093
+ this.currentBg = { type: 'default' };
1094
+ }
1095
+ else if (code === 58) {
1096
+ // Underline color: 58;2;r;g;b or 58;5;idx
1097
+ if (args[i] === 5) {
1098
+ this.currentUnderlineColor = { type: 'indexed', index: args[i + 1] };
1099
+ i += 2;
1100
+ }
1101
+ else if (args[i] === 2) {
1102
+ this.currentUnderlineColor = { type: 'rgb', r: args[i + 1], g: args[i + 2], b: args[i + 3] };
1103
+ i += 4;
1104
+ }
1105
+ }
1106
+ else if (code === 59) {
1107
+ this.currentUnderlineColor = undefined;
1108
+ }
1109
+ else if (code >= 90 && code <= 97) {
1110
+ this.currentFg = { type: 'indexed', index: code - 90 + 8 };
1111
+ }
1112
+ else if (code >= 100 && code <= 107) {
1113
+ this.currentBg = { type: 'indexed', index: code - 100 + 8 };
1114
+ }
1115
+ }
1116
+ }
1117
+ handleOsc(buffer) {
1118
+ const semiIdx = buffer.indexOf(';');
1119
+ if (semiIdx === -1)
1120
+ return;
1121
+ const ident = parseInt(buffer.slice(0, semiIdx), 10);
1122
+ const content = buffer.slice(semiIdx + 1);
1123
+ this.events.onOsc?.(ident, content);
1124
+ if (ident === 0 || ident === 2) {
1125
+ this.events.onTitle?.(content);
1126
+ }
1127
+ else if (ident === 7) {
1128
+ let url = content;
1129
+ if (url.startsWith('file://'))
1130
+ url = url.slice(7);
1131
+ const slash = url.indexOf('/');
1132
+ if (slash !== -1 && !url.startsWith('/'))
1133
+ url = url.slice(slash);
1134
+ this.events.onCwd?.(decodeURIComponent(url));
1135
+ }
1136
+ else if (ident === 52) {
1137
+ const secondSemi = content.indexOf(';');
1138
+ const encoded = secondSemi !== -1 ? content.slice(secondSemi + 1) : content;
1139
+ if (encoded && encoded !== '?') {
1140
+ try {
1141
+ const decoded = typeof atob === 'function'
1142
+ ? atob(encoded)
1143
+ : typeof globalThis.Buffer !== 'undefined'
1144
+ ? globalThis.Buffer.from(encoded, 'base64').toString('utf8')
1145
+ : encoded;
1146
+ this.events.onClipboard?.(decoded);
1147
+ }
1148
+ catch {
1149
+ // ignore malformed base64
1150
+ }
1151
+ }
1152
+ }
1153
+ else if (ident === 8) {
1154
+ // OSC 8 Hyperlink: ]8;params;url
1155
+ const secondSemi = content.indexOf(';');
1156
+ if (secondSemi !== -1) {
1157
+ const url = content.slice(secondSemi + 1);
1158
+ this.currentHyperlink = url.length > 0 ? url : undefined;
1159
+ }
1160
+ else {
1161
+ this.currentHyperlink = content.length > 0 ? content : undefined;
1162
+ }
1163
+ }
1164
+ }
1165
+ clearLine(row) {
1166
+ if (row < this.lines.length) {
1167
+ this.lines[row] = this.createEmptyLine();
1168
+ this.dirtyRowSet.add(row);
1169
+ }
1170
+ }
1171
+ clearLineRange(row, startCol, endCol) {
1172
+ if (row < this.lines.length) {
1173
+ for (let c = Math.max(0, startCol); c <= Math.min(this.cols - 1, endCol); c++) {
1174
+ this.lines[row][c] = {
1175
+ char: ' ',
1176
+ width: 1,
1177
+ flags: CellFlags.EMPTY,
1178
+ fg: { type: 'default' },
1179
+ bg: { type: 'default' },
1180
+ };
1181
+ }
1182
+ this.dirtyRowSet.add(row);
1183
+ }
1184
+ }
1185
+ reset() {
1186
+ this.currentFlags = CellFlags.EMPTY;
1187
+ this.currentFg = { type: 'default' };
1188
+ this.currentBg = { type: 'default' };
1189
+ this.currentUnderlineColor = undefined;
1190
+ this.currentHyperlink = undefined;
1191
+ this.wrapPending = false;
1192
+ this.autowrap = true;
1193
+ this.originMode = false;
1194
+ this.lastPrintedChar = '';
1195
+ this.cursor.col = 0;
1196
+ this.cursor.row = 0;
1197
+ this.cursor.visible = true;
1198
+ this.scrollback = [];
1199
+ this.viewportY = 0;
1200
+ this.isAltBuffer = false;
1201
+ this.mouseMode = { x10: false, button: false, any: false, sgr: false };
1202
+ this.focusReporting = false;
1203
+ this.initGrid();
1204
+ }
1205
+ }
1206
+ //# sourceMappingURL=VtEngine.js.map