synabun 1.3.0
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/CHANGELOG.md +665 -0
- package/LICENSE +191 -0
- package/README.md +853 -0
- package/hooks/claude-code/post-plan.mjs +391 -0
- package/hooks/claude-code/post-remember.mjs +239 -0
- package/hooks/claude-code/pre-compact.mjs +241 -0
- package/hooks/claude-code/pre-websearch.mjs +90 -0
- package/hooks/claude-code/prompt-submit.mjs +861 -0
- package/hooks/claude-code/session-start.mjs +494 -0
- package/hooks/claude-code/shared.mjs +368 -0
- package/hooks/claude-code/stop.mjs +646 -0
- package/hooks/claude-code/test-user-learning.mjs +531 -0
- package/mcp-server/package.json +38 -0
- package/mcp-server/run.mjs +10 -0
- package/mcp-server/src/config.ts +70 -0
- package/mcp-server/src/http.ts +93 -0
- package/mcp-server/src/index.ts +203 -0
- package/mcp-server/src/preload-http.ts +25 -0
- package/mcp-server/src/preload.ts +29 -0
- package/mcp-server/src/scripts/migrate-qdrant-to-sqlite.ts +506 -0
- package/mcp-server/src/services/categories.ts +429 -0
- package/mcp-server/src/services/discord.ts +702 -0
- package/mcp-server/src/services/file-checksums.ts +33 -0
- package/mcp-server/src/services/local-embeddings.ts +77 -0
- package/mcp-server/src/services/neural-interface.ts +344 -0
- package/mcp-server/src/services/sqlite.ts +823 -0
- package/mcp-server/src/tools/browser-advanced.ts +123 -0
- package/mcp-server/src/tools/browser-interact.ts +255 -0
- package/mcp-server/src/tools/browser-navigate.ts +77 -0
- package/mcp-server/src/tools/browser-observe.ts +1330 -0
- package/mcp-server/src/tools/browser.ts +107 -0
- package/mcp-server/src/tools/card-tools.ts +193 -0
- package/mcp-server/src/tools/card.ts +22 -0
- package/mcp-server/src/tools/category.ts +379 -0
- package/mcp-server/src/tools/discord-channel.ts +274 -0
- package/mcp-server/src/tools/discord-guild.ts +188 -0
- package/mcp-server/src/tools/discord-member.ts +158 -0
- package/mcp-server/src/tools/discord-message.ts +231 -0
- package/mcp-server/src/tools/discord-onboarding.ts +228 -0
- package/mcp-server/src/tools/discord-role.ts +209 -0
- package/mcp-server/src/tools/discord-thread.ts +110 -0
- package/mcp-server/src/tools/discord-webhook.ts +176 -0
- package/mcp-server/src/tools/discord.ts +40 -0
- package/mcp-server/src/tools/forget.ts +38 -0
- package/mcp-server/src/tools/git-tools.ts +141 -0
- package/mcp-server/src/tools/git.ts +13 -0
- package/mcp-server/src/tools/image-tools.ts +98 -0
- package/mcp-server/src/tools/image.ts +17 -0
- package/mcp-server/src/tools/leonardo-browser-tools.ts +281 -0
- package/mcp-server/src/tools/leonardo.ts +28 -0
- package/mcp-server/src/tools/loop.ts +348 -0
- package/mcp-server/src/tools/memories.ts +118 -0
- package/mcp-server/src/tools/recall.ts +323 -0
- package/mcp-server/src/tools/reflect.ts +145 -0
- package/mcp-server/src/tools/remember.ts +115 -0
- package/mcp-server/src/tools/response.ts +26 -0
- package/mcp-server/src/tools/restore.ts +34 -0
- package/mcp-server/src/tools/sync.ts +114 -0
- package/mcp-server/src/tools/tictactoe-tools.ts +98 -0
- package/mcp-server/src/tools/tictactoe.ts +13 -0
- package/mcp-server/src/tools/utils.ts +25 -0
- package/mcp-server/src/tools/whiteboard-tools.ts +260 -0
- package/mcp-server/src/tools/whiteboard.ts +22 -0
- package/mcp-server/src/tui.ts +940 -0
- package/mcp-server/src/types.ts +85 -0
- package/mcp-server/tsconfig.json +19 -0
- package/neural-interface/package.json +27 -0
- package/neural-interface/public/apple-touch-icon.png +0 -0
- package/neural-interface/public/category-logos/claude.svg +1 -0
- package/neural-interface/public/category-logos/gemini-color.svg +1 -0
- package/neural-interface/public/category-logos/openai.svg +1 -0
- package/neural-interface/public/category-logos/synabun.png +0 -0
- package/neural-interface/public/claude-chat.css +878 -0
- package/neural-interface/public/claude-chat.html +92 -0
- package/neural-interface/public/claude-chat.js +1168 -0
- package/neural-interface/public/favicon-16x16.png +0 -0
- package/neural-interface/public/favicon-32x32.png +0 -0
- package/neural-interface/public/gallery.svg +6 -0
- package/neural-interface/public/icon-192.png +0 -0
- package/neural-interface/public/icon-512.png +0 -0
- package/neural-interface/public/index-legacy.html +14677 -0
- package/neural-interface/public/index.html +212 -0
- package/neural-interface/public/index2d-legacy.html +8193 -0
- package/neural-interface/public/index2d.html +127 -0
- package/neural-interface/public/invite.html +351 -0
- package/neural-interface/public/leonardoai.svg +1 -0
- package/neural-interface/public/logo-synabun.png +0 -0
- package/neural-interface/public/logoHD.png +0 -0
- package/neural-interface/public/manifest.json +14 -0
- package/neural-interface/public/offline.html +299 -0
- package/neural-interface/public/onboarding.html +2335 -0
- package/neural-interface/public/openclaw-logo-text.png +0 -0
- package/neural-interface/public/pin-svgrepo-com.svg +25 -0
- package/neural-interface/public/sessionicon.svg +8 -0
- package/neural-interface/public/shared/ansi-parser.js +305 -0
- package/neural-interface/public/shared/api.js +1043 -0
- package/neural-interface/public/shared/colors.js +153 -0
- package/neural-interface/public/shared/constants.js +141 -0
- package/neural-interface/public/shared/html-shell.js +777 -0
- package/neural-interface/public/shared/html-term-renderer.js +1022 -0
- package/neural-interface/public/shared/i18n.js +147 -0
- package/neural-interface/public/shared/registry.js +82 -0
- package/neural-interface/public/shared/state.js +86 -0
- package/neural-interface/public/shared/storage.js +150 -0
- package/neural-interface/public/shared/styles.css +21212 -0
- package/neural-interface/public/shared/ui-automation-studio.js +3573 -0
- package/neural-interface/public/shared/ui-bookmarks.js +231 -0
- package/neural-interface/public/shared/ui-claude-panel.js +6333 -0
- package/neural-interface/public/shared/ui-command-runner.js +534 -0
- package/neural-interface/public/shared/ui-cost-widget.js +602 -0
- package/neural-interface/public/shared/ui-detail.js +1454 -0
- package/neural-interface/public/shared/ui-explorer.js +1356 -0
- package/neural-interface/public/shared/ui-file-explorer.js +2750 -0
- package/neural-interface/public/shared/ui-games.js +411 -0
- package/neural-interface/public/shared/ui-help.js +107 -0
- package/neural-interface/public/shared/ui-image-gallery.js +461 -0
- package/neural-interface/public/shared/ui-invite.js +519 -0
- package/neural-interface/public/shared/ui-keybinds.js +452 -0
- package/neural-interface/public/shared/ui-layouts.js +297 -0
- package/neural-interface/public/shared/ui-link.js +731 -0
- package/neural-interface/public/shared/ui-loading.js +255 -0
- package/neural-interface/public/shared/ui-menubar.js +499 -0
- package/neural-interface/public/shared/ui-multiselect.js +93 -0
- package/neural-interface/public/shared/ui-navbar.js +222 -0
- package/neural-interface/public/shared/ui-notifications.js +287 -0
- package/neural-interface/public/shared/ui-panels.js +309 -0
- package/neural-interface/public/shared/ui-resume.js +653 -0
- package/neural-interface/public/shared/ui-search.js +145 -0
- package/neural-interface/public/shared/ui-sessions.js +531 -0
- package/neural-interface/public/shared/ui-settings.js +5987 -0
- package/neural-interface/public/shared/ui-sidebar.js +1905 -0
- package/neural-interface/public/shared/ui-skills.js +2805 -0
- package/neural-interface/public/shared/ui-stats.js +38 -0
- package/neural-interface/public/shared/ui-sync.js +201 -0
- package/neural-interface/public/shared/ui-terminal.js +6385 -0
- package/neural-interface/public/shared/ui-tooltip.js +123 -0
- package/neural-interface/public/shared/ui-trash.js +439 -0
- package/neural-interface/public/shared/ui-tutorial-draw.js +875 -0
- package/neural-interface/public/shared/ui-tutorial-steps.js +2276 -0
- package/neural-interface/public/shared/ui-tutorial.js +1117 -0
- package/neural-interface/public/shared/ui-update.js +244 -0
- package/neural-interface/public/shared/ui-whiteboard.js +3262 -0
- package/neural-interface/public/shared/ui-workspaces.js +722 -0
- package/neural-interface/public/shared/utils.js +360 -0
- package/neural-interface/public/shared/vterm-buffer.js +1102 -0
- package/neural-interface/public/sw.js +52 -0
- package/neural-interface/public/synabun.png +0 -0
- package/neural-interface/public/toolbar-album.svg +8 -0
- package/neural-interface/public/toolbar-question.svg +6 -0
- package/neural-interface/public/toolbar-sessions.svg +6 -0
- package/neural-interface/public/variant/2d/background.js +113 -0
- package/neural-interface/public/variant/2d/context-menu.js +133 -0
- package/neural-interface/public/variant/2d/gfx.js +45 -0
- package/neural-interface/public/variant/2d/graph.js +1635 -0
- package/neural-interface/public/variant/2d/hulls.js +207 -0
- package/neural-interface/public/variant/2d/lasso.js +135 -0
- package/neural-interface/public/variant/2d/layout.js +594 -0
- package/neural-interface/public/variant/2d/main.js +521 -0
- package/neural-interface/public/variant/2d/minimap.js +312 -0
- package/neural-interface/public/variant/2d/settings-gfx.js +98 -0
- package/neural-interface/public/variant/3d/background.js +884 -0
- package/neural-interface/public/variant/3d/camera.js +661 -0
- package/neural-interface/public/variant/3d/gfx.js +116 -0
- package/neural-interface/public/variant/3d/graph.js +2399 -0
- package/neural-interface/public/variant/3d/main.js +500 -0
- package/neural-interface/public/variant/3d/settings-gfx.js +228 -0
- package/neural-interface/scripts/rebuild-pty.js +100 -0
- package/neural-interface/server.js +15000 -0
- package/neural-interface/templates/CLAUDE-template.md +211 -0
- package/package.json +53 -0
- package/postinstall.js +135 -0
- package/setup.js +260 -0
- package/skills/leonardo/SKILL.md +159 -0
- package/skills/leonardo/icon.svg +1 -0
- package/skills/leonardo/modules/browser-guide.md +378 -0
- package/skills/leonardo/modules/image-prompter.md +277 -0
- package/skills/leonardo/modules/model-advisor.md +100 -0
- package/skills/leonardo/modules/prompt-library.md +110 -0
- package/skills/leonardo/modules/style-guide.md +186 -0
- package/skills/leonardo/modules/video-prompter.md +311 -0
- package/skills/synabun/SKILL.md +185 -0
- package/skills/synabun/icon.png +0 -0
- package/skills/synabun/modules/audit.md +374 -0
- package/skills/synabun/modules/changelog.md +194 -0
- package/skills/synabun/modules/idea.md +144 -0
- package/skills/synabun/modules/memorize.md +188 -0
- package/skills/synabun/synabunicon.png +0 -0
|
@@ -0,0 +1,1102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vterm-buffer.js — Virtual terminal buffer (2D cell grid)
|
|
3
|
+
*
|
|
4
|
+
* Implements the handler interface for AnsiParser. Maintains terminal
|
|
5
|
+
* state: cell grid, cursor, SGR attributes, scroll region, alternate
|
|
6
|
+
* screen buffer, and scrollback.
|
|
7
|
+
*
|
|
8
|
+
* Each cell: { char, fg, bg, bold, dim, italic, underline, inverse, strikethrough, url }
|
|
9
|
+
* Dirty row tracking via Set<rowIndex> for incremental rendering.
|
|
10
|
+
*
|
|
11
|
+
* Performance optimizations:
|
|
12
|
+
* - Circular buffer for scrollback (O(1) push/shift vs O(n) Array.shift)
|
|
13
|
+
* - Cell reuse in erase operations (mutate instead of replace)
|
|
14
|
+
* - getDirtyRows returns snapshot Array (safe across clearDirty calls)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { AnsiParser } from './ansi-parser.js';
|
|
18
|
+
|
|
19
|
+
// ─── Default cell ──────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
function blankCell() {
|
|
22
|
+
return { char: ' ', fg: null, bg: null, bold: false, dim: false, italic: false, underline: false, inverse: false, strikethrough: false, url: null };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Reset an existing cell to blank state — avoids allocation */
|
|
26
|
+
function resetCell(c) {
|
|
27
|
+
c.char = ' '; c.fg = null; c.bg = null;
|
|
28
|
+
c.bold = false; c.dim = false; c.italic = false;
|
|
29
|
+
c.underline = false; c.inverse = false; c.strikethrough = false;
|
|
30
|
+
c.url = null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function blankRow(cols) {
|
|
34
|
+
const row = new Array(cols);
|
|
35
|
+
for (let i = 0; i < cols; i++) row[i] = blankCell();
|
|
36
|
+
return row;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Reset all cells in a row to blank — reuses existing cell objects */
|
|
40
|
+
function resetRow(row, cols) {
|
|
41
|
+
for (let i = 0; i < cols; i++) {
|
|
42
|
+
if (row[i]) resetCell(row[i]);
|
|
43
|
+
else row[i] = blankCell();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ─── Circular buffer for scrollback ────────────────────────
|
|
48
|
+
// Array.shift() is O(n) — moves every element. This is O(1) amortized.
|
|
49
|
+
class CircularBuffer {
|
|
50
|
+
constructor(capacity) {
|
|
51
|
+
this._capacity = capacity;
|
|
52
|
+
this._buf = new Array(capacity);
|
|
53
|
+
this._head = 0; // index of oldest element
|
|
54
|
+
this._size = 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
get length() { return this._size; }
|
|
58
|
+
|
|
59
|
+
push(item) {
|
|
60
|
+
const idx = (this._head + this._size) % this._capacity;
|
|
61
|
+
this._buf[idx] = item;
|
|
62
|
+
if (this._size < this._capacity) {
|
|
63
|
+
this._size++;
|
|
64
|
+
} else {
|
|
65
|
+
// Overwrite oldest — advance head
|
|
66
|
+
this._head = (this._head + 1) % this._capacity;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
get(index) {
|
|
71
|
+
if (index < 0 || index >= this._size) return null;
|
|
72
|
+
return this._buf[(this._head + index) % this._capacity];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
clear() {
|
|
76
|
+
// Release references for GC
|
|
77
|
+
for (let i = 0; i < this._capacity; i++) this._buf[i] = undefined;
|
|
78
|
+
this._head = 0;
|
|
79
|
+
this._size = 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Transfer excess rows from array-shrink into scrollback */
|
|
83
|
+
pushMany(rows) {
|
|
84
|
+
for (let i = 0; i < rows.length; i++) this.push(rows[i]);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ─── Standard 256-color palette (indices 0–255) ───────────
|
|
89
|
+
// 0-7: standard, 8-15: bright, 16-231: 6x6x6 cube, 232-255: grayscale
|
|
90
|
+
const PALETTE_256 = (() => {
|
|
91
|
+
const p = new Array(256);
|
|
92
|
+
// 0-15 handled by theme lookup at render time (null → use CSS class)
|
|
93
|
+
for (let i = 0; i < 16; i++) p[i] = null;
|
|
94
|
+
// 16-231: 6x6x6 color cube
|
|
95
|
+
for (let i = 16; i < 232; i++) {
|
|
96
|
+
const idx = i - 16;
|
|
97
|
+
const r = Math.floor(idx / 36);
|
|
98
|
+
const g = Math.floor((idx % 36) / 6);
|
|
99
|
+
const b = idx % 6;
|
|
100
|
+
p[i] = `rgb(${r ? r * 40 + 55 : 0},${g ? g * 40 + 55 : 0},${b ? b * 40 + 55 : 0})`;
|
|
101
|
+
}
|
|
102
|
+
// 232-255: grayscale
|
|
103
|
+
for (let i = 232; i < 256; i++) {
|
|
104
|
+
const v = (i - 232) * 10 + 8;
|
|
105
|
+
p[i] = `rgb(${v},${v},${v})`;
|
|
106
|
+
}
|
|
107
|
+
return p;
|
|
108
|
+
})();
|
|
109
|
+
|
|
110
|
+
const MAX_SCROLLBACK = 5000;
|
|
111
|
+
|
|
112
|
+
export class VTermBuffer {
|
|
113
|
+
/**
|
|
114
|
+
* @param {number} cols
|
|
115
|
+
* @param {number} rows
|
|
116
|
+
*/
|
|
117
|
+
constructor(cols, rows) {
|
|
118
|
+
this.cols = cols;
|
|
119
|
+
this.rows = rows;
|
|
120
|
+
|
|
121
|
+
// Main screen buffer
|
|
122
|
+
this._mainBuffer = [];
|
|
123
|
+
for (let r = 0; r < rows; r++) this._mainBuffer.push(blankRow(cols));
|
|
124
|
+
|
|
125
|
+
// Alternate screen buffer (created on demand)
|
|
126
|
+
this._altBuffer = null;
|
|
127
|
+
this._useAlt = false;
|
|
128
|
+
|
|
129
|
+
// Active buffer reference
|
|
130
|
+
this._buffer = this._mainBuffer;
|
|
131
|
+
|
|
132
|
+
// Scrollback (main buffer only) — circular buffer for O(1) operations
|
|
133
|
+
this._scrollback = new CircularBuffer(MAX_SCROLLBACK);
|
|
134
|
+
|
|
135
|
+
// Cursor state
|
|
136
|
+
this._cursorX = 0;
|
|
137
|
+
this._cursorY = 0;
|
|
138
|
+
this._cursorVisible = true;
|
|
139
|
+
|
|
140
|
+
// Saved cursor (DECSC/DECRC)
|
|
141
|
+
this._savedCursor = null;
|
|
142
|
+
this._savedCursorAlt = null;
|
|
143
|
+
|
|
144
|
+
// SGR attributes
|
|
145
|
+
this._attr = { fg: null, bg: null, bold: false, dim: false, italic: false, underline: false, inverse: false, strikethrough: false };
|
|
146
|
+
|
|
147
|
+
// Scroll region (DECSTBM) — 0-based, inclusive
|
|
148
|
+
this._scrollTop = 0;
|
|
149
|
+
this._scrollBottom = rows - 1;
|
|
150
|
+
|
|
151
|
+
// Mode flags
|
|
152
|
+
this._bracketedPaste = false;
|
|
153
|
+
this._appCursorKeys = false;
|
|
154
|
+
this._autoWrap = true;
|
|
155
|
+
this._originMode = false;
|
|
156
|
+
this._insertMode = false;
|
|
157
|
+
|
|
158
|
+
// OSC state
|
|
159
|
+
this._title = '';
|
|
160
|
+
this._currentUrl = null; // for OSC 8 hyperlinks
|
|
161
|
+
|
|
162
|
+
// Dirty tracking
|
|
163
|
+
this._dirty = new Set();
|
|
164
|
+
this._allDirty = true; // first render paints everything
|
|
165
|
+
|
|
166
|
+
// Alt screen transition flag — renderer uses this to force scroll reset
|
|
167
|
+
this._altTransitionPending = false;
|
|
168
|
+
|
|
169
|
+
// Wrap-pending flag: cursor at right margin, next char wraps
|
|
170
|
+
this._wrapPending = false;
|
|
171
|
+
|
|
172
|
+
// Parser
|
|
173
|
+
this._parser = new AnsiParser(this);
|
|
174
|
+
|
|
175
|
+
// Charset (G0/G1) — simplified, just track if special
|
|
176
|
+
this._charsetG0 = null;
|
|
177
|
+
this._charsetG1 = null;
|
|
178
|
+
this._activeCharset = 0; // 0=G0, 1=G1
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ─── Public API ────────────────────────────────────────
|
|
182
|
+
|
|
183
|
+
/** Feed raw PTY data */
|
|
184
|
+
write(data) {
|
|
185
|
+
this._parser.parse(data);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Get a row from the visible buffer (0 = top of screen) */
|
|
189
|
+
getRow(idx) {
|
|
190
|
+
if (idx < 0 || idx >= this.rows) return null;
|
|
191
|
+
return this._buffer[idx];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Get scrollback row (0 = oldest) */
|
|
195
|
+
getScrollbackRow(idx) {
|
|
196
|
+
if (this._useAlt) return null;
|
|
197
|
+
return this._scrollback.get(idx);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
get scrollbackLength() {
|
|
201
|
+
return this._useAlt ? 0 : this._scrollback.length;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Get dirty row indices since last clearDirty().
|
|
205
|
+
* Returns the internal Set when not allDirty — avoids Array.from allocation.
|
|
206
|
+
* Callers must iterate with for..of (Set is iterable). */
|
|
207
|
+
getDirtyRows() {
|
|
208
|
+
if (this._allDirty) {
|
|
209
|
+
const all = [];
|
|
210
|
+
for (let i = 0; i < this.rows; i++) all.push(i);
|
|
211
|
+
return all;
|
|
212
|
+
}
|
|
213
|
+
return Array.from(this._dirty);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
clearDirty() {
|
|
217
|
+
this._dirty.clear();
|
|
218
|
+
this._allDirty = false;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
get cursorX() { return this._cursorX; }
|
|
222
|
+
get cursorY() { return this._cursorY; }
|
|
223
|
+
get cursorVisible() { return this._cursorVisible; }
|
|
224
|
+
get title() { return this._title; }
|
|
225
|
+
get bracketedPaste() { return this._bracketedPaste; }
|
|
226
|
+
get appCursorKeys() { return this._appCursorKeys; }
|
|
227
|
+
|
|
228
|
+
/** Resize the terminal */
|
|
229
|
+
resize(cols, rows) {
|
|
230
|
+
if (cols === this.cols && rows === this.rows) return;
|
|
231
|
+
const oldCols = this.cols;
|
|
232
|
+
const oldRows = this.rows;
|
|
233
|
+
this.cols = cols;
|
|
234
|
+
this.rows = rows;
|
|
235
|
+
|
|
236
|
+
// Resize both buffers
|
|
237
|
+
this._resizeBuffer(this._mainBuffer, oldCols, oldRows, cols, rows, true);
|
|
238
|
+
if (this._altBuffer) {
|
|
239
|
+
this._resizeBuffer(this._altBuffer, oldCols, oldRows, cols, rows, false);
|
|
240
|
+
}
|
|
241
|
+
this._buffer = this._useAlt ? this._altBuffer : this._mainBuffer;
|
|
242
|
+
|
|
243
|
+
// Clamp cursor
|
|
244
|
+
this._cursorX = Math.min(this._cursorX, cols - 1);
|
|
245
|
+
this._cursorY = Math.min(this._cursorY, rows - 1);
|
|
246
|
+
|
|
247
|
+
// Reset scroll region
|
|
248
|
+
this._scrollTop = 0;
|
|
249
|
+
this._scrollBottom = rows - 1;
|
|
250
|
+
|
|
251
|
+
this._allDirty = true;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** Get plain text content of a region (for copy).
|
|
255
|
+
* Row indices are absolute: 0..scrollbackLength-1 = scrollback,
|
|
256
|
+
* scrollbackLength..scrollbackLength+rows-1 = screen buffer.
|
|
257
|
+
*/
|
|
258
|
+
getText(startRow, startCol, endRow, endCol) {
|
|
259
|
+
const sb = this.scrollbackLength;
|
|
260
|
+
const lines = [];
|
|
261
|
+
for (let r = startRow; r <= endRow; r++) {
|
|
262
|
+
const row = r < sb
|
|
263
|
+
? this.getScrollbackRow(r)
|
|
264
|
+
: this.getRow(r - sb);
|
|
265
|
+
if (!row) continue;
|
|
266
|
+
const s = r === startRow ? startCol : 0;
|
|
267
|
+
const e = r === endRow ? endCol : this.cols - 1;
|
|
268
|
+
let line = '';
|
|
269
|
+
for (let c = s; c <= e; c++) {
|
|
270
|
+
line += row[c]?.char || ' ';
|
|
271
|
+
}
|
|
272
|
+
lines.push(line.replace(/\s+$/, ''));
|
|
273
|
+
}
|
|
274
|
+
return lines.join('\n');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// ─── Handler interface (called by AnsiParser) ──────────
|
|
278
|
+
|
|
279
|
+
/** Printable characters */
|
|
280
|
+
print(text) {
|
|
281
|
+
const ad = this._allDirty;
|
|
282
|
+
const cols = this.cols;
|
|
283
|
+
const attr = this._attr;
|
|
284
|
+
const url = this._currentUrl;
|
|
285
|
+
|
|
286
|
+
for (let i = 0; i < text.length; i++) {
|
|
287
|
+
const ch = text[i];
|
|
288
|
+
|
|
289
|
+
// Handle wrap-pending
|
|
290
|
+
if (this._wrapPending) {
|
|
291
|
+
if (this._autoWrap) {
|
|
292
|
+
if (!ad) this._dirty.add(this._cursorY);
|
|
293
|
+
this._cursorX = 0;
|
|
294
|
+
this._lineFeed();
|
|
295
|
+
}
|
|
296
|
+
this._wrapPending = false;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Insert mode: shift chars right
|
|
300
|
+
if (this._insertMode && this._cursorX < cols) {
|
|
301
|
+
const row = this._buffer[this._cursorY];
|
|
302
|
+
for (let c = cols - 1; c > this._cursorX; c--) {
|
|
303
|
+
row[c] = row[c - 1];
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Write cell
|
|
308
|
+
const row = this._buffer[this._cursorY];
|
|
309
|
+
if (this._cursorX < cols) {
|
|
310
|
+
const cell = row[this._cursorX];
|
|
311
|
+
cell.char = ch;
|
|
312
|
+
cell.fg = attr.fg;
|
|
313
|
+
cell.bg = attr.bg;
|
|
314
|
+
cell.bold = attr.bold;
|
|
315
|
+
cell.dim = attr.dim;
|
|
316
|
+
cell.italic = attr.italic;
|
|
317
|
+
cell.underline = attr.underline;
|
|
318
|
+
cell.inverse = attr.inverse;
|
|
319
|
+
cell.strikethrough = attr.strikethrough;
|
|
320
|
+
cell.url = url;
|
|
321
|
+
if (!ad) this._dirty.add(this._cursorY);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Advance cursor
|
|
325
|
+
if (this._cursorX >= cols - 1) {
|
|
326
|
+
this._wrapPending = true;
|
|
327
|
+
} else {
|
|
328
|
+
this._cursorX++;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/** C0 control codes */
|
|
334
|
+
execute(code) {
|
|
335
|
+
switch (code) {
|
|
336
|
+
case 0x08: // BS
|
|
337
|
+
if (this._cursorX > 0) this._cursorX--;
|
|
338
|
+
this._wrapPending = false;
|
|
339
|
+
break;
|
|
340
|
+
case 0x09: // HT (tab)
|
|
341
|
+
this._cursorX = Math.min(((this._cursorX >> 3) + 1) << 3, this.cols - 1);
|
|
342
|
+
this._wrapPending = false;
|
|
343
|
+
break;
|
|
344
|
+
case 0x0a: // LF
|
|
345
|
+
case 0x0b: // VT
|
|
346
|
+
case 0x0c: // FF
|
|
347
|
+
this._lineFeed();
|
|
348
|
+
break;
|
|
349
|
+
case 0x0d: // CR
|
|
350
|
+
this._cursorX = 0;
|
|
351
|
+
this._wrapPending = false;
|
|
352
|
+
break;
|
|
353
|
+
case 0x07: // BEL
|
|
354
|
+
// Could trigger visual bell — currently ignored
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** CSI dispatch */
|
|
360
|
+
csiDispatch(params, privateMarker, intermediates, final) {
|
|
361
|
+
if (privateMarker === '?') {
|
|
362
|
+
this._csiPrivate(params, final);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
if (privateMarker === '>') {
|
|
366
|
+
// DA2 and similar — ignore
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const p0 = this._getParam(params, 0, 1);
|
|
371
|
+
const p1 = this._getParam(params, 1, 1);
|
|
372
|
+
|
|
373
|
+
switch (final) {
|
|
374
|
+
// ─── Cursor movement ─────────────────────────────
|
|
375
|
+
case 'A': // CUU — cursor up
|
|
376
|
+
this._cursorY = Math.max(this._scrollTop, this._cursorY - p0);
|
|
377
|
+
this._wrapPending = false;
|
|
378
|
+
break;
|
|
379
|
+
case 'B': // CUD — cursor down
|
|
380
|
+
this._cursorY = Math.min(this._scrollBottom, this._cursorY + p0);
|
|
381
|
+
this._wrapPending = false;
|
|
382
|
+
break;
|
|
383
|
+
case 'C': // CUF — cursor forward
|
|
384
|
+
this._cursorX = Math.min(this.cols - 1, this._cursorX + p0);
|
|
385
|
+
this._wrapPending = false;
|
|
386
|
+
break;
|
|
387
|
+
case 'D': // CUB — cursor back
|
|
388
|
+
this._cursorX = Math.max(0, this._cursorX - p0);
|
|
389
|
+
this._wrapPending = false;
|
|
390
|
+
break;
|
|
391
|
+
case 'E': // CNL — cursor next line
|
|
392
|
+
this._cursorX = 0;
|
|
393
|
+
this._cursorY = Math.min(this._scrollBottom, this._cursorY + p0);
|
|
394
|
+
this._wrapPending = false;
|
|
395
|
+
break;
|
|
396
|
+
case 'F': // CPL — cursor previous line
|
|
397
|
+
this._cursorX = 0;
|
|
398
|
+
this._cursorY = Math.max(this._scrollTop, this._cursorY - p0);
|
|
399
|
+
this._wrapPending = false;
|
|
400
|
+
break;
|
|
401
|
+
case 'G': // CHA — cursor horizontal absolute
|
|
402
|
+
this._cursorX = Math.min(Math.max(p0 - 1, 0), this.cols - 1);
|
|
403
|
+
this._wrapPending = false;
|
|
404
|
+
break;
|
|
405
|
+
case 'H': // CUP — cursor position
|
|
406
|
+
case 'f': // HVP — same as CUP
|
|
407
|
+
this._cursorY = Math.min(Math.max(p0 - 1, 0), this.rows - 1);
|
|
408
|
+
this._cursorX = Math.min(Math.max(p1 - 1, 0), this.cols - 1);
|
|
409
|
+
this._wrapPending = false;
|
|
410
|
+
break;
|
|
411
|
+
case 'd': // VPA — vertical line position absolute
|
|
412
|
+
this._cursorY = Math.min(Math.max(p0 - 1, 0), this.rows - 1);
|
|
413
|
+
this._wrapPending = false;
|
|
414
|
+
break;
|
|
415
|
+
|
|
416
|
+
// ─── Erase ───────────────────────────────────────
|
|
417
|
+
case 'J': // ED — erase in display
|
|
418
|
+
this._eraseDisplay(this._getParam(params, 0, 0));
|
|
419
|
+
break;
|
|
420
|
+
case 'K': // EL — erase in line
|
|
421
|
+
this._eraseLine(this._getParam(params, 0, 0));
|
|
422
|
+
break;
|
|
423
|
+
|
|
424
|
+
// ─── Insert/Delete ───────────────────────────────
|
|
425
|
+
case 'L': // IL — insert lines
|
|
426
|
+
this._insertLines(p0);
|
|
427
|
+
break;
|
|
428
|
+
case 'M': // DL — delete lines
|
|
429
|
+
this._deleteLines(p0);
|
|
430
|
+
break;
|
|
431
|
+
case '@': // ICH — insert characters
|
|
432
|
+
this._insertChars(p0);
|
|
433
|
+
break;
|
|
434
|
+
case 'P': // DCH — delete characters
|
|
435
|
+
this._deleteChars(p0);
|
|
436
|
+
break;
|
|
437
|
+
case 'X': // ECH — erase characters
|
|
438
|
+
this._eraseChars(p0);
|
|
439
|
+
break;
|
|
440
|
+
|
|
441
|
+
// ─── Scroll ──────────────────────────────────────
|
|
442
|
+
case 'S': // SU — scroll up
|
|
443
|
+
this._scrollUp(p0);
|
|
444
|
+
break;
|
|
445
|
+
case 'T': // SD — scroll down
|
|
446
|
+
this._scrollDown(p0);
|
|
447
|
+
break;
|
|
448
|
+
|
|
449
|
+
// ─── SGR ─────────────────────────────────────────
|
|
450
|
+
case 'm': // SGR — select graphic rendition
|
|
451
|
+
this._handleSGR(params);
|
|
452
|
+
break;
|
|
453
|
+
|
|
454
|
+
// ─── Scroll region ──────────────────────────────
|
|
455
|
+
case 'r': // DECSTBM — set top and bottom margins
|
|
456
|
+
this._scrollTop = Math.max(0, (this._getParam(params, 0, 1)) - 1);
|
|
457
|
+
this._scrollBottom = Math.min(this.rows - 1, (this._getParam(params, 1, this.rows)) - 1);
|
|
458
|
+
// Move cursor to home position
|
|
459
|
+
this._cursorX = 0;
|
|
460
|
+
this._cursorY = this._originMode ? this._scrollTop : 0;
|
|
461
|
+
this._wrapPending = false;
|
|
462
|
+
break;
|
|
463
|
+
|
|
464
|
+
// ─── Tab clear ──────────────────────────────────
|
|
465
|
+
case 'g': // TBC — tab clear (ignore, we use fixed 8-col tabs)
|
|
466
|
+
break;
|
|
467
|
+
|
|
468
|
+
// ─── Device Status Report ───────────────────────
|
|
469
|
+
case 'n': // DSR
|
|
470
|
+
// p0 === 6 → report cursor position (not needed for rendering, but some apps expect it)
|
|
471
|
+
break;
|
|
472
|
+
|
|
473
|
+
// ─── Cursor save/restore (ANSI) ─────────────────
|
|
474
|
+
case 's': // SCP — save cursor position
|
|
475
|
+
this._savedCursor = { x: this._cursorX, y: this._cursorY, attr: { ...this._attr } };
|
|
476
|
+
break;
|
|
477
|
+
case 'u': // RCP — restore cursor position
|
|
478
|
+
if (this._savedCursor) {
|
|
479
|
+
this._cursorX = this._savedCursor.x;
|
|
480
|
+
this._cursorY = this._savedCursor.y;
|
|
481
|
+
this._attr = { ...this._savedCursor.attr };
|
|
482
|
+
}
|
|
483
|
+
this._wrapPending = false;
|
|
484
|
+
break;
|
|
485
|
+
|
|
486
|
+
// ─── Repeat ─────────────────────────────────────
|
|
487
|
+
case 'b': // REP — repeat preceding character
|
|
488
|
+
// Not commonly used by CLI tools, safe to ignore
|
|
489
|
+
break;
|
|
490
|
+
|
|
491
|
+
// ─── Set mode (SM) / Reset mode (RM) ────────────
|
|
492
|
+
case 'h': // SM
|
|
493
|
+
if (this._getParam(params, 0, 0) === 4) this._insertMode = true;
|
|
494
|
+
break;
|
|
495
|
+
case 'l': // RM
|
|
496
|
+
if (this._getParam(params, 0, 0) === 4) this._insertMode = false;
|
|
497
|
+
break;
|
|
498
|
+
|
|
499
|
+
default:
|
|
500
|
+
// Unhandled CSI — silently ignore
|
|
501
|
+
break;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/** ESC dispatch (simple escape sequences) */
|
|
506
|
+
escDispatch(intermediates, final) {
|
|
507
|
+
if (intermediates === '') {
|
|
508
|
+
switch (final) {
|
|
509
|
+
case '7': // DECSC — save cursor
|
|
510
|
+
this._saveCursor();
|
|
511
|
+
break;
|
|
512
|
+
case '8': // DECRC — restore cursor
|
|
513
|
+
this._restoreCursor();
|
|
514
|
+
break;
|
|
515
|
+
case 'D': // IND — index (line feed without CR)
|
|
516
|
+
this._lineFeed();
|
|
517
|
+
break;
|
|
518
|
+
case 'E': // NEL — next line (LF + CR)
|
|
519
|
+
this._cursorX = 0;
|
|
520
|
+
this._lineFeed();
|
|
521
|
+
break;
|
|
522
|
+
case 'M': // RI — reverse index
|
|
523
|
+
this._reverseIndex();
|
|
524
|
+
break;
|
|
525
|
+
case 'c': // RIS — full reset
|
|
526
|
+
this._fullReset();
|
|
527
|
+
break;
|
|
528
|
+
case 'H': // HTS — horizontal tab set (ignore, fixed tabs)
|
|
529
|
+
break;
|
|
530
|
+
}
|
|
531
|
+
} else if (intermediates === '(') {
|
|
532
|
+
// G0 charset designation
|
|
533
|
+
this._charsetG0 = final === '0' ? 'special' : null;
|
|
534
|
+
} else if (intermediates === ')') {
|
|
535
|
+
// G1 charset designation
|
|
536
|
+
this._charsetG1 = final === '0' ? 'special' : null;
|
|
537
|
+
} else if (intermediates === '#') {
|
|
538
|
+
// DECDHL, DECSWL, DECDWL — double height/width (ignore)
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/** OSC dispatch */
|
|
543
|
+
oscDispatch(data) {
|
|
544
|
+
const semiIdx = data.indexOf(';');
|
|
545
|
+
if (semiIdx === -1) return;
|
|
546
|
+
const cmd = data.substring(0, semiIdx);
|
|
547
|
+
const payload = data.substring(semiIdx + 1);
|
|
548
|
+
|
|
549
|
+
switch (cmd) {
|
|
550
|
+
case '0': // Set title + icon name
|
|
551
|
+
case '2': // Set title
|
|
552
|
+
this._title = payload;
|
|
553
|
+
break;
|
|
554
|
+
case '8': { // Hyperlink
|
|
555
|
+
// OSC 8 ; params ; uri ST
|
|
556
|
+
// params are key=value pairs separated by :
|
|
557
|
+
// empty uri = close link
|
|
558
|
+
const semiIdx2 = payload.indexOf(';');
|
|
559
|
+
if (semiIdx2 !== -1) {
|
|
560
|
+
const uri = payload.substring(semiIdx2 + 1);
|
|
561
|
+
this._currentUrl = uri || null;
|
|
562
|
+
}
|
|
563
|
+
break;
|
|
564
|
+
}
|
|
565
|
+
// OSC 4: set color palette — ignored
|
|
566
|
+
// OSC 10/11: fg/bg color — ignored
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// ─── Private mode (CSI ? ...) ──────────────────────────
|
|
571
|
+
|
|
572
|
+
_csiPrivate(params, final) {
|
|
573
|
+
const mode = this._getParam(params, 0, 0);
|
|
574
|
+
|
|
575
|
+
switch (final) {
|
|
576
|
+
case 'h': // DECSET
|
|
577
|
+
this._setPrivateMode(mode, true);
|
|
578
|
+
// Handle multiple params
|
|
579
|
+
for (let i = 1; i < params.length; i++) {
|
|
580
|
+
const m = typeof params[i] === 'number' ? params[i] : 0;
|
|
581
|
+
if (m) this._setPrivateMode(m, true);
|
|
582
|
+
}
|
|
583
|
+
break;
|
|
584
|
+
case 'l': // DECRST
|
|
585
|
+
this._setPrivateMode(mode, false);
|
|
586
|
+
for (let i = 1; i < params.length; i++) {
|
|
587
|
+
const m = typeof params[i] === 'number' ? params[i] : 0;
|
|
588
|
+
if (m) this._setPrivateMode(m, false);
|
|
589
|
+
}
|
|
590
|
+
break;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
_setPrivateMode(mode, enable) {
|
|
595
|
+
switch (mode) {
|
|
596
|
+
case 1: // DECCKM — application cursor keys
|
|
597
|
+
this._appCursorKeys = enable;
|
|
598
|
+
break;
|
|
599
|
+
case 7: // DECAWM — auto-wrap
|
|
600
|
+
this._autoWrap = enable;
|
|
601
|
+
break;
|
|
602
|
+
case 25: // DECTCEM — cursor visible
|
|
603
|
+
this._cursorVisible = enable;
|
|
604
|
+
break;
|
|
605
|
+
case 1049: // Alt screen + save/restore cursor
|
|
606
|
+
if (enable) {
|
|
607
|
+
this._saveCursor();
|
|
608
|
+
this._switchToAltBuffer();
|
|
609
|
+
} else {
|
|
610
|
+
this._switchToMainBuffer();
|
|
611
|
+
this._restoreCursor();
|
|
612
|
+
}
|
|
613
|
+
break;
|
|
614
|
+
case 47: // Alt screen (no save/restore)
|
|
615
|
+
case 1047:
|
|
616
|
+
if (enable) {
|
|
617
|
+
this._switchToAltBuffer();
|
|
618
|
+
} else {
|
|
619
|
+
this._switchToMainBuffer();
|
|
620
|
+
}
|
|
621
|
+
break;
|
|
622
|
+
case 2004: // Bracketed paste
|
|
623
|
+
this._bracketedPaste = enable;
|
|
624
|
+
break;
|
|
625
|
+
case 6: // DECOM — origin mode
|
|
626
|
+
this._originMode = enable;
|
|
627
|
+
if (enable) {
|
|
628
|
+
this._cursorX = 0;
|
|
629
|
+
this._cursorY = this._scrollTop;
|
|
630
|
+
}
|
|
631
|
+
break;
|
|
632
|
+
case 12: // AT&T cursor blink — ignore
|
|
633
|
+
break;
|
|
634
|
+
case 1000: // Mouse tracking — ignore for now
|
|
635
|
+
case 1002:
|
|
636
|
+
case 1003:
|
|
637
|
+
case 1006:
|
|
638
|
+
break;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// ─── SGR (Select Graphic Rendition) ────────────────────
|
|
643
|
+
|
|
644
|
+
_handleSGR(params) {
|
|
645
|
+
if (params.length === 0) {
|
|
646
|
+
this._resetAttr();
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
for (let i = 0; i < params.length; i++) {
|
|
651
|
+
const p = params[i];
|
|
652
|
+
|
|
653
|
+
// Handle colon-separated sub-params (e.g., 38:2:r:g:b)
|
|
654
|
+
if (Array.isArray(p)) {
|
|
655
|
+
this._handleSGRSubParams(p);
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
switch (p) {
|
|
660
|
+
case 0:
|
|
661
|
+
this._resetAttr();
|
|
662
|
+
break;
|
|
663
|
+
case 1: this._attr.bold = true; break;
|
|
664
|
+
case 2: this._attr.dim = true; break;
|
|
665
|
+
case 3: this._attr.italic = true; break;
|
|
666
|
+
case 4: this._attr.underline = true; break;
|
|
667
|
+
case 7: this._attr.inverse = true; break;
|
|
668
|
+
case 9: this._attr.strikethrough = true; break;
|
|
669
|
+
case 21: this._attr.underline = true; break; // double underline → underline
|
|
670
|
+
case 22: this._attr.bold = false; this._attr.dim = false; break;
|
|
671
|
+
case 23: this._attr.italic = false; break;
|
|
672
|
+
case 24: this._attr.underline = false; break;
|
|
673
|
+
case 27: this._attr.inverse = false; break;
|
|
674
|
+
case 29: this._attr.strikethrough = false; break;
|
|
675
|
+
|
|
676
|
+
// Foreground colors (standard)
|
|
677
|
+
case 30: case 31: case 32: case 33:
|
|
678
|
+
case 34: case 35: case 36: case 37:
|
|
679
|
+
this._attr.fg = p - 30;
|
|
680
|
+
break;
|
|
681
|
+
case 39: // Default fg
|
|
682
|
+
this._attr.fg = null;
|
|
683
|
+
break;
|
|
684
|
+
|
|
685
|
+
// Background colors (standard)
|
|
686
|
+
case 40: case 41: case 42: case 43:
|
|
687
|
+
case 44: case 45: case 46: case 47:
|
|
688
|
+
this._attr.bg = p - 40;
|
|
689
|
+
break;
|
|
690
|
+
case 49: // Default bg
|
|
691
|
+
this._attr.bg = null;
|
|
692
|
+
break;
|
|
693
|
+
|
|
694
|
+
// Bright foreground
|
|
695
|
+
case 90: case 91: case 92: case 93:
|
|
696
|
+
case 94: case 95: case 96: case 97:
|
|
697
|
+
this._attr.fg = p - 90 + 8;
|
|
698
|
+
break;
|
|
699
|
+
|
|
700
|
+
// Bright background
|
|
701
|
+
case 100: case 101: case 102: case 103:
|
|
702
|
+
case 104: case 105: case 106: case 107:
|
|
703
|
+
this._attr.bg = p - 100 + 8;
|
|
704
|
+
break;
|
|
705
|
+
|
|
706
|
+
// Extended colors (semicolon-separated)
|
|
707
|
+
case 38: { // FG extended
|
|
708
|
+
const result = this._parseExtendedColor(params, i + 1);
|
|
709
|
+
if (result) {
|
|
710
|
+
this._attr.fg = result.color;
|
|
711
|
+
i = result.nextIndex - 1; // -1 because loop increments
|
|
712
|
+
}
|
|
713
|
+
break;
|
|
714
|
+
}
|
|
715
|
+
case 48: { // BG extended
|
|
716
|
+
const result = this._parseExtendedColor(params, i + 1);
|
|
717
|
+
if (result) {
|
|
718
|
+
this._attr.bg = result.color;
|
|
719
|
+
i = result.nextIndex - 1;
|
|
720
|
+
}
|
|
721
|
+
break;
|
|
722
|
+
}
|
|
723
|
+
case 58: { // Underline color (ignore value but consume params)
|
|
724
|
+
const result = this._parseExtendedColor(params, i + 1);
|
|
725
|
+
if (result) i = result.nextIndex - 1;
|
|
726
|
+
break;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
/** Handle colon-separated SGR sub-params like 38:2:r:g:b */
|
|
733
|
+
_handleSGRSubParams(subParams) {
|
|
734
|
+
if (subParams.length < 2) return;
|
|
735
|
+
const type = subParams[0];
|
|
736
|
+
const colorType = subParams[1];
|
|
737
|
+
|
|
738
|
+
if (type === 38 || type === 48) {
|
|
739
|
+
// FG or BG
|
|
740
|
+
const isFg = type === 38;
|
|
741
|
+
if (colorType === 2 && subParams.length >= 5) {
|
|
742
|
+
// Truecolor: 38:2:r:g:b or 38:2:colorspace:r:g:b
|
|
743
|
+
let r, g, b;
|
|
744
|
+
if (subParams.length >= 6) {
|
|
745
|
+
// With colorspace: 38:2:cs:r:g:b
|
|
746
|
+
r = subParams[3]; g = subParams[4]; b = subParams[5];
|
|
747
|
+
} else {
|
|
748
|
+
r = subParams[2]; g = subParams[3]; b = subParams[4];
|
|
749
|
+
}
|
|
750
|
+
const color = `rgb(${r & 255},${g & 255},${b & 255})`;
|
|
751
|
+
if (isFg) this._attr.fg = color; else this._attr.bg = color;
|
|
752
|
+
} else if (colorType === 5 && subParams.length >= 3) {
|
|
753
|
+
// 256-color: 38:5:idx
|
|
754
|
+
this._set256Color(isFg, subParams[2]);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* Parse extended color params (semicolon-separated):
|
|
761
|
+
* 38;2;r;g;b or 38;5;idx
|
|
762
|
+
* @returns {{ color: any, nextIndex: number }} or null
|
|
763
|
+
*/
|
|
764
|
+
_parseExtendedColor(params, startIdx) {
|
|
765
|
+
if (startIdx >= params.length) return null;
|
|
766
|
+
const mode = typeof params[startIdx] === 'number' ? params[startIdx] : 0;
|
|
767
|
+
|
|
768
|
+
if (mode === 2) {
|
|
769
|
+
// Truecolor
|
|
770
|
+
if (startIdx + 3 < params.length) {
|
|
771
|
+
const r = typeof params[startIdx + 1] === 'number' ? params[startIdx + 1] : 0;
|
|
772
|
+
const g = typeof params[startIdx + 2] === 'number' ? params[startIdx + 2] : 0;
|
|
773
|
+
const b = typeof params[startIdx + 3] === 'number' ? params[startIdx + 3] : 0;
|
|
774
|
+
return { color: `rgb(${r & 255},${g & 255},${b & 255})`, nextIndex: startIdx + 4 };
|
|
775
|
+
}
|
|
776
|
+
} else if (mode === 5) {
|
|
777
|
+
// 256-color
|
|
778
|
+
if (startIdx + 1 < params.length) {
|
|
779
|
+
const idx = typeof params[startIdx + 1] === 'number' ? params[startIdx + 1] : 0;
|
|
780
|
+
const color = this._resolve256(idx);
|
|
781
|
+
return { color, nextIndex: startIdx + 2 };
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
return null;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
_resolve256(idx) {
|
|
788
|
+
if (idx < 0 || idx > 255) return null;
|
|
789
|
+
if (idx < 16) return idx; // Theme color index (rendered via CSS class)
|
|
790
|
+
return PALETTE_256[idx]; // RGB string
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
_set256Color(isFg, idx) {
|
|
794
|
+
const color = this._resolve256(idx);
|
|
795
|
+
if (isFg) this._attr.fg = color; else this._attr.bg = color;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
_resetAttr() {
|
|
799
|
+
this._attr.fg = null;
|
|
800
|
+
this._attr.bg = null;
|
|
801
|
+
this._attr.bold = false;
|
|
802
|
+
this._attr.dim = false;
|
|
803
|
+
this._attr.italic = false;
|
|
804
|
+
this._attr.underline = false;
|
|
805
|
+
this._attr.inverse = false;
|
|
806
|
+
this._attr.strikethrough = false;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// ─── Line operations ──────────────────────────────────
|
|
810
|
+
|
|
811
|
+
_lineFeed() {
|
|
812
|
+
this._wrapPending = false;
|
|
813
|
+
if (this._cursorY === this._scrollBottom) {
|
|
814
|
+
this._scrollUp(1);
|
|
815
|
+
} else if (this._cursorY < this.rows - 1) {
|
|
816
|
+
this._cursorY++;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
_reverseIndex() {
|
|
821
|
+
this._wrapPending = false;
|
|
822
|
+
if (this._cursorY === this._scrollTop) {
|
|
823
|
+
this._scrollDown(1);
|
|
824
|
+
} else if (this._cursorY > 0) {
|
|
825
|
+
this._cursorY--;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
_scrollUp(n) {
|
|
830
|
+
for (let i = 0; i < n; i++) {
|
|
831
|
+
// Push top line to scrollback (main buffer only, full-screen scroll region)
|
|
832
|
+
if (!this._useAlt && this._scrollTop === 0) {
|
|
833
|
+
this._scrollback.push(this._buffer[this._scrollTop]);
|
|
834
|
+
}
|
|
835
|
+
// Shift rows up within scroll region
|
|
836
|
+
for (let r = this._scrollTop; r < this._scrollBottom; r++) {
|
|
837
|
+
this._buffer[r] = this._buffer[r + 1];
|
|
838
|
+
}
|
|
839
|
+
this._buffer[this._scrollBottom] = blankRow(this.cols);
|
|
840
|
+
}
|
|
841
|
+
// Mark all rows in scroll region dirty
|
|
842
|
+
if (!this._allDirty) {
|
|
843
|
+
for (let r = this._scrollTop; r <= this._scrollBottom; r++) {
|
|
844
|
+
this._dirty.add(r);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
_scrollDown(n) {
|
|
850
|
+
for (let i = 0; i < n; i++) {
|
|
851
|
+
for (let r = this._scrollBottom; r > this._scrollTop; r--) {
|
|
852
|
+
this._buffer[r] = this._buffer[r - 1];
|
|
853
|
+
}
|
|
854
|
+
this._buffer[this._scrollTop] = blankRow(this.cols);
|
|
855
|
+
}
|
|
856
|
+
if (!this._allDirty) {
|
|
857
|
+
for (let r = this._scrollTop; r <= this._scrollBottom; r++) {
|
|
858
|
+
this._dirty.add(r);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
_insertLines(n) {
|
|
864
|
+
const top = this._cursorY;
|
|
865
|
+
const bot = this._scrollBottom;
|
|
866
|
+
for (let i = 0; i < n; i++) {
|
|
867
|
+
for (let r = bot; r > top; r--) {
|
|
868
|
+
this._buffer[r] = this._buffer[r - 1];
|
|
869
|
+
}
|
|
870
|
+
this._buffer[top] = blankRow(this.cols);
|
|
871
|
+
}
|
|
872
|
+
if (!this._allDirty) {
|
|
873
|
+
for (let r = top; r <= bot; r++) this._dirty.add(r);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
_deleteLines(n) {
|
|
878
|
+
const top = this._cursorY;
|
|
879
|
+
const bot = this._scrollBottom;
|
|
880
|
+
for (let i = 0; i < n; i++) {
|
|
881
|
+
for (let r = top; r < bot; r++) {
|
|
882
|
+
this._buffer[r] = this._buffer[r + 1];
|
|
883
|
+
}
|
|
884
|
+
this._buffer[bot] = blankRow(this.cols);
|
|
885
|
+
}
|
|
886
|
+
if (!this._allDirty) {
|
|
887
|
+
for (let r = top; r <= bot; r++) this._dirty.add(r);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// ─── Character operations ─────────────────────────────
|
|
892
|
+
|
|
893
|
+
_insertChars(n) {
|
|
894
|
+
const row = this._buffer[this._cursorY];
|
|
895
|
+
for (let i = 0; i < n; i++) {
|
|
896
|
+
row.pop();
|
|
897
|
+
row.splice(this._cursorX, 0, blankCell());
|
|
898
|
+
}
|
|
899
|
+
// Ensure row stays correct length
|
|
900
|
+
while (row.length < this.cols) row.push(blankCell());
|
|
901
|
+
if (row.length > this.cols) row.length = this.cols;
|
|
902
|
+
if (!this._allDirty) this._dirty.add(this._cursorY);
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
_deleteChars(n) {
|
|
906
|
+
const row = this._buffer[this._cursorY];
|
|
907
|
+
row.splice(this._cursorX, n);
|
|
908
|
+
while (row.length < this.cols) row.push(blankCell());
|
|
909
|
+
if (!this._allDirty) this._dirty.add(this._cursorY);
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
_eraseChars(n) {
|
|
913
|
+
const row = this._buffer[this._cursorY];
|
|
914
|
+
const end = Math.min(this._cursorX + n, this.cols);
|
|
915
|
+
for (let c = this._cursorX; c < end; c++) {
|
|
916
|
+
resetCell(row[c]);
|
|
917
|
+
}
|
|
918
|
+
if (!this._allDirty) this._dirty.add(this._cursorY);
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
// ─── Erase operations ─────────────────────────────────
|
|
922
|
+
|
|
923
|
+
_eraseDisplay(mode) {
|
|
924
|
+
const ad = this._allDirty;
|
|
925
|
+
switch (mode) {
|
|
926
|
+
case 0: // Cursor to end
|
|
927
|
+
this._eraseLineFrom(this._cursorY, this._cursorX);
|
|
928
|
+
for (let r = this._cursorY + 1; r < this.rows; r++) {
|
|
929
|
+
resetRow(this._buffer[r], this.cols);
|
|
930
|
+
if (!ad) this._dirty.add(r);
|
|
931
|
+
}
|
|
932
|
+
break;
|
|
933
|
+
case 1: // Start to cursor
|
|
934
|
+
for (let r = 0; r < this._cursorY; r++) {
|
|
935
|
+
resetRow(this._buffer[r], this.cols);
|
|
936
|
+
if (!ad) this._dirty.add(r);
|
|
937
|
+
}
|
|
938
|
+
this._eraseLineTo(this._cursorY, this._cursorX);
|
|
939
|
+
break;
|
|
940
|
+
case 2: // Entire display
|
|
941
|
+
for (let r = 0; r < this.rows; r++) {
|
|
942
|
+
resetRow(this._buffer[r], this.cols);
|
|
943
|
+
}
|
|
944
|
+
this._allDirty = true;
|
|
945
|
+
break;
|
|
946
|
+
case 3: // Entire display + scrollback
|
|
947
|
+
for (let r = 0; r < this.rows; r++) {
|
|
948
|
+
resetRow(this._buffer[r], this.cols);
|
|
949
|
+
}
|
|
950
|
+
this._scrollback.clear();
|
|
951
|
+
this._allDirty = true;
|
|
952
|
+
break;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
_eraseLine(mode) {
|
|
957
|
+
switch (mode) {
|
|
958
|
+
case 0: // Cursor to end
|
|
959
|
+
this._eraseLineFrom(this._cursorY, this._cursorX);
|
|
960
|
+
break;
|
|
961
|
+
case 1: // Start to cursor
|
|
962
|
+
this._eraseLineTo(this._cursorY, this._cursorX);
|
|
963
|
+
break;
|
|
964
|
+
case 2: // Entire line
|
|
965
|
+
resetRow(this._buffer[this._cursorY], this.cols);
|
|
966
|
+
if (!this._allDirty) this._dirty.add(this._cursorY);
|
|
967
|
+
break;
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
_eraseLineFrom(row, col) {
|
|
972
|
+
const r = this._buffer[row];
|
|
973
|
+
for (let c = col; c < this.cols; c++) {
|
|
974
|
+
resetCell(r[c]);
|
|
975
|
+
}
|
|
976
|
+
if (!this._allDirty) this._dirty.add(row);
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
_eraseLineTo(row, col) {
|
|
980
|
+
const r = this._buffer[row];
|
|
981
|
+
for (let c = 0; c <= col && c < this.cols; c++) {
|
|
982
|
+
resetCell(r[c]);
|
|
983
|
+
}
|
|
984
|
+
if (!this._allDirty) this._dirty.add(row);
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// ─── Cursor save/restore ──────────────────────────────
|
|
988
|
+
|
|
989
|
+
_saveCursor() {
|
|
990
|
+
const saved = { x: this._cursorX, y: this._cursorY, attr: { ...this._attr } };
|
|
991
|
+
if (this._useAlt) {
|
|
992
|
+
this._savedCursorAlt = saved;
|
|
993
|
+
} else {
|
|
994
|
+
this._savedCursor = saved;
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
_restoreCursor() {
|
|
999
|
+
const saved = this._useAlt ? this._savedCursorAlt : this._savedCursor;
|
|
1000
|
+
if (saved) {
|
|
1001
|
+
this._cursorX = Math.min(saved.x, this.cols - 1);
|
|
1002
|
+
this._cursorY = Math.min(saved.y, this.rows - 1);
|
|
1003
|
+
this._attr = { ...saved.attr };
|
|
1004
|
+
}
|
|
1005
|
+
this._wrapPending = false;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
// ─── Alt screen buffer ────────────────────────────────
|
|
1009
|
+
|
|
1010
|
+
_switchToAltBuffer() {
|
|
1011
|
+
if (this._useAlt) return;
|
|
1012
|
+
// Create fresh alt buffer
|
|
1013
|
+
this._altBuffer = [];
|
|
1014
|
+
for (let r = 0; r < this.rows; r++) this._altBuffer.push(blankRow(this.cols));
|
|
1015
|
+
this._useAlt = true;
|
|
1016
|
+
this._buffer = this._altBuffer;
|
|
1017
|
+
this._allDirty = true;
|
|
1018
|
+
this._altTransitionPending = true;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
_switchToMainBuffer() {
|
|
1022
|
+
if (!this._useAlt) return;
|
|
1023
|
+
this._useAlt = false;
|
|
1024
|
+
this._altBuffer = null;
|
|
1025
|
+
this._buffer = this._mainBuffer;
|
|
1026
|
+
this._allDirty = true;
|
|
1027
|
+
this._altTransitionPending = true;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
// ─── Full reset ───────────────────────────────────────
|
|
1031
|
+
|
|
1032
|
+
_fullReset() {
|
|
1033
|
+
this._cursorX = 0;
|
|
1034
|
+
this._cursorY = 0;
|
|
1035
|
+
this._cursorVisible = true;
|
|
1036
|
+
this._resetAttr();
|
|
1037
|
+
this._scrollTop = 0;
|
|
1038
|
+
this._scrollBottom = this.rows - 1;
|
|
1039
|
+
this._bracketedPaste = false;
|
|
1040
|
+
this._appCursorKeys = false;
|
|
1041
|
+
this._autoWrap = true;
|
|
1042
|
+
this._originMode = false;
|
|
1043
|
+
this._insertMode = false;
|
|
1044
|
+
this._title = '';
|
|
1045
|
+
this._currentUrl = null;
|
|
1046
|
+
this._wrapPending = false;
|
|
1047
|
+
this._savedCursor = null;
|
|
1048
|
+
this._savedCursorAlt = null;
|
|
1049
|
+
|
|
1050
|
+
if (this._useAlt) {
|
|
1051
|
+
this._useAlt = false;
|
|
1052
|
+
this._altBuffer = null;
|
|
1053
|
+
this._buffer = this._mainBuffer;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
for (let r = 0; r < this.rows; r++) {
|
|
1057
|
+
resetRow(this._buffer[r], this.cols);
|
|
1058
|
+
}
|
|
1059
|
+
this._scrollback.clear();
|
|
1060
|
+
this._allDirty = true;
|
|
1061
|
+
this._parser.reset();
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
// ─── Buffer resize ────────────────────────────────────
|
|
1065
|
+
|
|
1066
|
+
_resizeBuffer(buffer, oldCols, oldRows, newCols, newRows, isMain) {
|
|
1067
|
+
// Adjust row count
|
|
1068
|
+
while (buffer.length < newRows) {
|
|
1069
|
+
buffer.push(blankRow(newCols));
|
|
1070
|
+
}
|
|
1071
|
+
// If shrinking rows, push excess to scrollback (main only)
|
|
1072
|
+
while (buffer.length > newRows) {
|
|
1073
|
+
const removed = buffer.shift();
|
|
1074
|
+
if (isMain) {
|
|
1075
|
+
this._scrollback.push(removed);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
// Adjust column count per row
|
|
1079
|
+
if (newCols !== oldCols) {
|
|
1080
|
+
for (let r = 0; r < buffer.length; r++) {
|
|
1081
|
+
const row = buffer[r];
|
|
1082
|
+
if (newCols > oldCols) {
|
|
1083
|
+
// Expand
|
|
1084
|
+
for (let c = oldCols; c < newCols; c++) row.push(blankCell());
|
|
1085
|
+
} else {
|
|
1086
|
+
// Shrink
|
|
1087
|
+
row.length = newCols;
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
// ─── Helpers ──────────────────────────────────────────
|
|
1094
|
+
|
|
1095
|
+
_getParam(params, idx, def) {
|
|
1096
|
+
if (idx >= params.length) return def;
|
|
1097
|
+
const v = params[idx];
|
|
1098
|
+
if (typeof v === 'number') return v || def;
|
|
1099
|
+
if (Array.isArray(v)) return v[0] || def;
|
|
1100
|
+
return def;
|
|
1101
|
+
}
|
|
1102
|
+
}
|