@jonsoc/app 1.1.34
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/AGENTS.md +30 -0
- package/README.md +51 -0
- package/bunfig.toml +2 -0
- package/e2e/context.spec.ts +45 -0
- package/e2e/file-open.spec.ts +23 -0
- package/e2e/file-viewer.spec.ts +35 -0
- package/e2e/fixtures.ts +40 -0
- package/e2e/home.spec.ts +21 -0
- package/e2e/model-picker.spec.ts +43 -0
- package/e2e/navigation.spec.ts +9 -0
- package/e2e/palette.spec.ts +15 -0
- package/e2e/prompt-mention.spec.ts +26 -0
- package/e2e/prompt-slash-open.spec.ts +22 -0
- package/e2e/prompt.spec.ts +62 -0
- package/e2e/session.spec.ts +21 -0
- package/e2e/settings.spec.ts +44 -0
- package/e2e/sidebar.spec.ts +21 -0
- package/e2e/terminal-init.spec.ts +25 -0
- package/e2e/terminal.spec.ts +16 -0
- package/e2e/tsconfig.json +8 -0
- package/e2e/utils.ts +38 -0
- package/happydom.ts +75 -0
- package/index.html +23 -0
- package/package.json +72 -0
- package/playwright.config.ts +43 -0
- package/public/_headers +17 -0
- package/public/apple-touch-icon-v3.png +1 -0
- package/public/apple-touch-icon.png +1 -0
- package/public/favicon-96x96-v3.png +1 -0
- package/public/favicon-96x96.png +1 -0
- package/public/favicon-v3.ico +1 -0
- package/public/favicon-v3.svg +1 -0
- package/public/favicon.ico +1 -0
- package/public/favicon.svg +1 -0
- package/public/oc-theme-preload.js +28 -0
- package/public/site.webmanifest +1 -0
- package/public/social-share-zen.png +1 -0
- package/public/social-share.png +1 -0
- package/public/web-app-manifest-192x192.png +1 -0
- package/public/web-app-manifest-512x512.png +1 -0
- package/script/e2e-local.ts +143 -0
- package/src/addons/serialize.test.ts +319 -0
- package/src/addons/serialize.ts +591 -0
- package/src/app.tsx +150 -0
- package/src/components/dialog-connect-provider.tsx +428 -0
- package/src/components/dialog-edit-project.tsx +259 -0
- package/src/components/dialog-fork.tsx +104 -0
- package/src/components/dialog-manage-models.tsx +59 -0
- package/src/components/dialog-select-directory.tsx +208 -0
- package/src/components/dialog-select-file.tsx +196 -0
- package/src/components/dialog-select-mcp.tsx +96 -0
- package/src/components/dialog-select-model-unpaid.tsx +130 -0
- package/src/components/dialog-select-model.tsx +162 -0
- package/src/components/dialog-select-provider.tsx +70 -0
- package/src/components/dialog-select-server.tsx +249 -0
- package/src/components/dialog-settings.tsx +112 -0
- package/src/components/file-tree.tsx +112 -0
- package/src/components/link.tsx +17 -0
- package/src/components/model-tooltip.tsx +91 -0
- package/src/components/prompt-input.tsx +2076 -0
- package/src/components/session/index.ts +5 -0
- package/src/components/session/session-context-tab.tsx +428 -0
- package/src/components/session/session-header.tsx +343 -0
- package/src/components/session/session-new-view.tsx +93 -0
- package/src/components/session/session-sortable-tab.tsx +56 -0
- package/src/components/session/session-sortable-terminal-tab.tsx +187 -0
- package/src/components/session-context-usage.tsx +113 -0
- package/src/components/session-lsp-indicator.tsx +42 -0
- package/src/components/session-mcp-indicator.tsx +34 -0
- package/src/components/settings-agents.tsx +15 -0
- package/src/components/settings-commands.tsx +15 -0
- package/src/components/settings-general.tsx +306 -0
- package/src/components/settings-keybinds.tsx +437 -0
- package/src/components/settings-mcp.tsx +15 -0
- package/src/components/settings-models.tsx +15 -0
- package/src/components/settings-permissions.tsx +234 -0
- package/src/components/settings-providers.tsx +15 -0
- package/src/components/terminal.tsx +315 -0
- package/src/components/titlebar.tsx +156 -0
- package/src/context/command.tsx +308 -0
- package/src/context/comments.tsx +140 -0
- package/src/context/file.tsx +409 -0
- package/src/context/global-sdk.tsx +106 -0
- package/src/context/global-sync.tsx +898 -0
- package/src/context/language.tsx +161 -0
- package/src/context/layout-scroll.test.ts +73 -0
- package/src/context/layout-scroll.ts +118 -0
- package/src/context/layout.tsx +648 -0
- package/src/context/local.tsx +578 -0
- package/src/context/notification.tsx +173 -0
- package/src/context/permission.tsx +167 -0
- package/src/context/platform.tsx +59 -0
- package/src/context/prompt.tsx +245 -0
- package/src/context/sdk.tsx +48 -0
- package/src/context/server.tsx +214 -0
- package/src/context/settings.tsx +166 -0
- package/src/context/sync.tsx +320 -0
- package/src/context/terminal.tsx +267 -0
- package/src/custom-elements.d.ts +17 -0
- package/src/entry.tsx +76 -0
- package/src/env.d.ts +8 -0
- package/src/hooks/use-providers.ts +31 -0
- package/src/i18n/ar.ts +656 -0
- package/src/i18n/br.ts +667 -0
- package/src/i18n/da.ts +582 -0
- package/src/i18n/de.ts +591 -0
- package/src/i18n/en.ts +665 -0
- package/src/i18n/es.ts +585 -0
- package/src/i18n/fr.ts +592 -0
- package/src/i18n/ja.ts +579 -0
- package/src/i18n/ko.ts +580 -0
- package/src/i18n/no.ts +602 -0
- package/src/i18n/pl.ts +661 -0
- package/src/i18n/ru.ts +664 -0
- package/src/i18n/zh.ts +574 -0
- package/src/i18n/zht.ts +570 -0
- package/src/index.css +57 -0
- package/src/index.ts +2 -0
- package/src/pages/directory-layout.tsx +57 -0
- package/src/pages/error.tsx +290 -0
- package/src/pages/home.tsx +125 -0
- package/src/pages/layout.tsx +2599 -0
- package/src/pages/session.tsx +2505 -0
- package/src/sst-env.d.ts +10 -0
- package/src/utils/dom.ts +51 -0
- package/src/utils/id.ts +99 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/perf.ts +135 -0
- package/src/utils/persist.ts +377 -0
- package/src/utils/prompt.ts +203 -0
- package/src/utils/same.ts +6 -0
- package/src/utils/solid-dnd.tsx +55 -0
- package/src/utils/sound.ts +110 -0
- package/src/utils/speech.ts +302 -0
- package/src/utils/worktree.ts +58 -0
- package/sst-env.d.ts +9 -0
- package/tsconfig.json +26 -0
- package/vite.config.ts +15 -0
- package/vite.js +26 -0
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SerializeAddon - Serialize terminal buffer contents
|
|
3
|
+
*
|
|
4
|
+
* Port of xterm.js addon-serialize for ghostty-web.
|
|
5
|
+
* Enables serialization of terminal contents to a string that can
|
|
6
|
+
* be written back to restore terminal state.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* ```typescript
|
|
10
|
+
* const serializeAddon = new SerializeAddon();
|
|
11
|
+
* term.loadAddon(serializeAddon);
|
|
12
|
+
* const content = serializeAddon.serialize();
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { ITerminalAddon, ITerminalCore, IBufferRange } from "ghostty-web"
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// Buffer Types (matching ghostty-web internal interfaces)
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
interface IBuffer {
|
|
23
|
+
readonly type: "normal" | "alternate"
|
|
24
|
+
readonly cursorX: number
|
|
25
|
+
readonly cursorY: number
|
|
26
|
+
readonly viewportY: number
|
|
27
|
+
readonly baseY: number
|
|
28
|
+
readonly length: number
|
|
29
|
+
getLine(y: number): IBufferLine | undefined
|
|
30
|
+
getNullCell(): IBufferCell
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface IBufferLine {
|
|
34
|
+
readonly length: number
|
|
35
|
+
readonly isWrapped: boolean
|
|
36
|
+
getCell(x: number): IBufferCell | undefined
|
|
37
|
+
translateToString(trimRight?: boolean, startColumn?: number, endColumn?: number): string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface IBufferCell {
|
|
41
|
+
getChars(): string
|
|
42
|
+
getCode(): number
|
|
43
|
+
getWidth(): number
|
|
44
|
+
getFgColorMode(): number
|
|
45
|
+
getBgColorMode(): number
|
|
46
|
+
getFgColor(): number
|
|
47
|
+
getBgColor(): number
|
|
48
|
+
isBold(): number
|
|
49
|
+
isItalic(): number
|
|
50
|
+
isUnderline(): number
|
|
51
|
+
isStrikethrough(): number
|
|
52
|
+
isBlink(): number
|
|
53
|
+
isInverse(): number
|
|
54
|
+
isInvisible(): number
|
|
55
|
+
isFaint(): number
|
|
56
|
+
isDim(): boolean
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ============================================================================
|
|
60
|
+
// Types
|
|
61
|
+
// ============================================================================
|
|
62
|
+
|
|
63
|
+
export interface ISerializeOptions {
|
|
64
|
+
/**
|
|
65
|
+
* The row range to serialize. When an explicit range is specified, the cursor
|
|
66
|
+
* will get its final repositioning.
|
|
67
|
+
*/
|
|
68
|
+
range?: ISerializeRange
|
|
69
|
+
/**
|
|
70
|
+
* The number of rows in the scrollback buffer to serialize, starting from
|
|
71
|
+
* the bottom of the scrollback buffer. When not specified, all available
|
|
72
|
+
* rows in the scrollback buffer will be serialized.
|
|
73
|
+
*/
|
|
74
|
+
scrollback?: number
|
|
75
|
+
/**
|
|
76
|
+
* Whether to exclude the terminal modes from the serialization.
|
|
77
|
+
* Default: false
|
|
78
|
+
*/
|
|
79
|
+
excludeModes?: boolean
|
|
80
|
+
/**
|
|
81
|
+
* Whether to exclude the alt buffer from the serialization.
|
|
82
|
+
* Default: false
|
|
83
|
+
*/
|
|
84
|
+
excludeAltBuffer?: boolean
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface ISerializeRange {
|
|
88
|
+
/**
|
|
89
|
+
* The line to start serializing (inclusive).
|
|
90
|
+
*/
|
|
91
|
+
start: number
|
|
92
|
+
/**
|
|
93
|
+
* The line to end serializing (inclusive).
|
|
94
|
+
*/
|
|
95
|
+
end: number
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface IHTMLSerializeOptions {
|
|
99
|
+
/**
|
|
100
|
+
* The number of rows in the scrollback buffer to serialize, starting from
|
|
101
|
+
* the bottom of the scrollback buffer.
|
|
102
|
+
*/
|
|
103
|
+
scrollback?: number
|
|
104
|
+
/**
|
|
105
|
+
* Whether to only serialize the selection.
|
|
106
|
+
* Default: false
|
|
107
|
+
*/
|
|
108
|
+
onlySelection?: boolean
|
|
109
|
+
/**
|
|
110
|
+
* Whether to include the global background of the terminal.
|
|
111
|
+
* Default: false
|
|
112
|
+
*/
|
|
113
|
+
includeGlobalBackground?: boolean
|
|
114
|
+
/**
|
|
115
|
+
* The range to serialize. This is prioritized over onlySelection.
|
|
116
|
+
*/
|
|
117
|
+
range?: {
|
|
118
|
+
startLine: number
|
|
119
|
+
endLine: number
|
|
120
|
+
startCol: number
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ============================================================================
|
|
125
|
+
// Helper Functions
|
|
126
|
+
// ============================================================================
|
|
127
|
+
|
|
128
|
+
function constrain(value: number, low: number, high: number): number {
|
|
129
|
+
return Math.max(low, Math.min(value, high))
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function equalFg(cell1: IBufferCell, cell2: IBufferCell): boolean {
|
|
133
|
+
return cell1.getFgColorMode() === cell2.getFgColorMode() && cell1.getFgColor() === cell2.getFgColor()
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function equalBg(cell1: IBufferCell, cell2: IBufferCell): boolean {
|
|
137
|
+
return cell1.getBgColorMode() === cell2.getBgColorMode() && cell1.getBgColor() === cell2.getBgColor()
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function equalFlags(cell1: IBufferCell, cell2: IBufferCell): boolean {
|
|
141
|
+
return (
|
|
142
|
+
!!cell1.isInverse() === !!cell2.isInverse() &&
|
|
143
|
+
!!cell1.isBold() === !!cell2.isBold() &&
|
|
144
|
+
!!cell1.isUnderline() === !!cell2.isUnderline() &&
|
|
145
|
+
!!cell1.isBlink() === !!cell2.isBlink() &&
|
|
146
|
+
!!cell1.isInvisible() === !!cell2.isInvisible() &&
|
|
147
|
+
!!cell1.isItalic() === !!cell2.isItalic() &&
|
|
148
|
+
!!cell1.isDim() === !!cell2.isDim() &&
|
|
149
|
+
!!cell1.isStrikethrough() === !!cell2.isStrikethrough()
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ============================================================================
|
|
154
|
+
// Base Serialize Handler
|
|
155
|
+
// ============================================================================
|
|
156
|
+
|
|
157
|
+
abstract class BaseSerializeHandler {
|
|
158
|
+
constructor(protected readonly _buffer: IBuffer) {}
|
|
159
|
+
|
|
160
|
+
public serialize(range: IBufferRange, excludeFinalCursorPosition?: boolean): string {
|
|
161
|
+
let oldCell = this._buffer.getNullCell()
|
|
162
|
+
|
|
163
|
+
const startRow = range.start.y
|
|
164
|
+
const endRow = range.end.y
|
|
165
|
+
const startColumn = range.start.x
|
|
166
|
+
const endColumn = range.end.x
|
|
167
|
+
|
|
168
|
+
this._beforeSerialize(endRow - startRow + 1, startRow, endRow)
|
|
169
|
+
|
|
170
|
+
for (let row = startRow; row <= endRow; row++) {
|
|
171
|
+
const line = this._buffer.getLine(row)
|
|
172
|
+
if (line) {
|
|
173
|
+
const startLineColumn = row === range.start.y ? startColumn : 0
|
|
174
|
+
const endLineColumn = Math.min(endColumn, line.length)
|
|
175
|
+
|
|
176
|
+
for (let col = startLineColumn; col < endLineColumn; col++) {
|
|
177
|
+
const c = line.getCell(col)
|
|
178
|
+
if (!c) {
|
|
179
|
+
continue
|
|
180
|
+
}
|
|
181
|
+
this._nextCell(c, oldCell, row, col)
|
|
182
|
+
oldCell = c
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
this._rowEnd(row, row === endRow)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
this._afterSerialize()
|
|
189
|
+
|
|
190
|
+
return this._serializeString(excludeFinalCursorPosition)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
protected _nextCell(_cell: IBufferCell, _oldCell: IBufferCell, _row: number, _col: number): void {}
|
|
194
|
+
protected _rowEnd(_row: number, _isLastRow: boolean): void {}
|
|
195
|
+
protected _beforeSerialize(_rows: number, _startRow: number, _endRow: number): void {}
|
|
196
|
+
protected _afterSerialize(): void {}
|
|
197
|
+
protected _serializeString(_excludeFinalCursorPosition?: boolean): string {
|
|
198
|
+
return ""
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ============================================================================
|
|
203
|
+
// String Serialize Handler
|
|
204
|
+
// ============================================================================
|
|
205
|
+
|
|
206
|
+
class StringSerializeHandler extends BaseSerializeHandler {
|
|
207
|
+
private _rowIndex: number = 0
|
|
208
|
+
private _allRows: string[] = []
|
|
209
|
+
private _allRowSeparators: string[] = []
|
|
210
|
+
private _currentRow: string = ""
|
|
211
|
+
private _nullCellCount: number = 0
|
|
212
|
+
private _cursorStyle: IBufferCell
|
|
213
|
+
private _firstRow: number = 0
|
|
214
|
+
private _lastCursorRow: number = 0
|
|
215
|
+
private _lastCursorCol: number = 0
|
|
216
|
+
private _lastContentCursorRow: number = 0
|
|
217
|
+
private _lastContentCursorCol: number = 0
|
|
218
|
+
|
|
219
|
+
constructor(
|
|
220
|
+
buffer: IBuffer,
|
|
221
|
+
private readonly _terminal: ITerminalCore,
|
|
222
|
+
) {
|
|
223
|
+
super(buffer)
|
|
224
|
+
this._cursorStyle = this._buffer.getNullCell()
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
protected _beforeSerialize(rows: number, start: number, _end: number): void {
|
|
228
|
+
this._allRows = new Array<string>(rows)
|
|
229
|
+
this._allRowSeparators = new Array<string>(rows)
|
|
230
|
+
this._rowIndex = 0
|
|
231
|
+
|
|
232
|
+
this._currentRow = ""
|
|
233
|
+
this._nullCellCount = 0
|
|
234
|
+
this._cursorStyle = this._buffer.getNullCell()
|
|
235
|
+
|
|
236
|
+
this._lastContentCursorRow = start
|
|
237
|
+
this._lastCursorRow = start
|
|
238
|
+
this._firstRow = start
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
protected _rowEnd(row: number, isLastRow: boolean): void {
|
|
242
|
+
let rowSeparator = ""
|
|
243
|
+
|
|
244
|
+
if (this._nullCellCount > 0) {
|
|
245
|
+
this._currentRow += " ".repeat(this._nullCellCount)
|
|
246
|
+
this._nullCellCount = 0
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (!isLastRow) {
|
|
250
|
+
const nextLine = this._buffer.getLine(row + 1)
|
|
251
|
+
|
|
252
|
+
if (!nextLine?.isWrapped) {
|
|
253
|
+
rowSeparator = "\r\n"
|
|
254
|
+
this._lastCursorRow = row + 1
|
|
255
|
+
this._lastCursorCol = 0
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
this._allRows[this._rowIndex] = this._currentRow
|
|
260
|
+
this._allRowSeparators[this._rowIndex++] = rowSeparator
|
|
261
|
+
this._currentRow = ""
|
|
262
|
+
this._nullCellCount = 0
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
private _diffStyle(cell: IBufferCell, oldCell: IBufferCell): number[] {
|
|
266
|
+
const sgrSeq: number[] = []
|
|
267
|
+
const fgChanged = !equalFg(cell, oldCell)
|
|
268
|
+
const bgChanged = !equalBg(cell, oldCell)
|
|
269
|
+
const flagsChanged = !equalFlags(cell, oldCell)
|
|
270
|
+
|
|
271
|
+
if (fgChanged || bgChanged || flagsChanged) {
|
|
272
|
+
if (this._isAttributeDefault(cell)) {
|
|
273
|
+
if (!this._isAttributeDefault(oldCell)) {
|
|
274
|
+
sgrSeq.push(0)
|
|
275
|
+
}
|
|
276
|
+
} else {
|
|
277
|
+
if (flagsChanged) {
|
|
278
|
+
if (!!cell.isInverse() !== !!oldCell.isInverse()) {
|
|
279
|
+
sgrSeq.push(cell.isInverse() ? 7 : 27)
|
|
280
|
+
}
|
|
281
|
+
if (!!cell.isBold() !== !!oldCell.isBold()) {
|
|
282
|
+
sgrSeq.push(cell.isBold() ? 1 : 22)
|
|
283
|
+
}
|
|
284
|
+
if (!!cell.isUnderline() !== !!oldCell.isUnderline()) {
|
|
285
|
+
sgrSeq.push(cell.isUnderline() ? 4 : 24)
|
|
286
|
+
}
|
|
287
|
+
if (!!cell.isBlink() !== !!oldCell.isBlink()) {
|
|
288
|
+
sgrSeq.push(cell.isBlink() ? 5 : 25)
|
|
289
|
+
}
|
|
290
|
+
if (!!cell.isInvisible() !== !!oldCell.isInvisible()) {
|
|
291
|
+
sgrSeq.push(cell.isInvisible() ? 8 : 28)
|
|
292
|
+
}
|
|
293
|
+
if (!!cell.isItalic() !== !!oldCell.isItalic()) {
|
|
294
|
+
sgrSeq.push(cell.isItalic() ? 3 : 23)
|
|
295
|
+
}
|
|
296
|
+
if (!!cell.isDim() !== !!oldCell.isDim()) {
|
|
297
|
+
sgrSeq.push(cell.isDim() ? 2 : 22)
|
|
298
|
+
}
|
|
299
|
+
if (!!cell.isStrikethrough() !== !!oldCell.isStrikethrough()) {
|
|
300
|
+
sgrSeq.push(cell.isStrikethrough() ? 9 : 29)
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (fgChanged) {
|
|
304
|
+
const color = cell.getFgColor()
|
|
305
|
+
const mode = cell.getFgColorMode()
|
|
306
|
+
if (mode === 2 || mode === 3 || mode === -1) {
|
|
307
|
+
sgrSeq.push(38, 2, (color >>> 16) & 0xff, (color >>> 8) & 0xff, color & 0xff)
|
|
308
|
+
} else if (mode === 1) {
|
|
309
|
+
// Palette
|
|
310
|
+
if (color >= 16) {
|
|
311
|
+
sgrSeq.push(38, 5, color)
|
|
312
|
+
} else {
|
|
313
|
+
sgrSeq.push(color & 8 ? 90 + (color & 7) : 30 + (color & 7))
|
|
314
|
+
}
|
|
315
|
+
} else {
|
|
316
|
+
sgrSeq.push(39)
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
if (bgChanged) {
|
|
320
|
+
const color = cell.getBgColor()
|
|
321
|
+
const mode = cell.getBgColorMode()
|
|
322
|
+
if (mode === 2 || mode === 3 || mode === -1) {
|
|
323
|
+
sgrSeq.push(48, 2, (color >>> 16) & 0xff, (color >>> 8) & 0xff, color & 0xff)
|
|
324
|
+
} else if (mode === 1) {
|
|
325
|
+
// Palette
|
|
326
|
+
if (color >= 16) {
|
|
327
|
+
sgrSeq.push(48, 5, color)
|
|
328
|
+
} else {
|
|
329
|
+
sgrSeq.push(color & 8 ? 100 + (color & 7) : 40 + (color & 7))
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
sgrSeq.push(49)
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return sgrSeq
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private _isAttributeDefault(cell: IBufferCell): boolean {
|
|
342
|
+
const mode = cell.getFgColorMode()
|
|
343
|
+
const bgMode = cell.getBgColorMode()
|
|
344
|
+
|
|
345
|
+
if (mode === 0 && bgMode === 0) {
|
|
346
|
+
return (
|
|
347
|
+
!cell.isBold() &&
|
|
348
|
+
!cell.isItalic() &&
|
|
349
|
+
!cell.isUnderline() &&
|
|
350
|
+
!cell.isBlink() &&
|
|
351
|
+
!cell.isInverse() &&
|
|
352
|
+
!cell.isInvisible() &&
|
|
353
|
+
!cell.isDim() &&
|
|
354
|
+
!cell.isStrikethrough()
|
|
355
|
+
)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const fgColor = cell.getFgColor()
|
|
359
|
+
const bgColor = cell.getBgColor()
|
|
360
|
+
const nullCell = this._buffer.getNullCell()
|
|
361
|
+
const nullFg = nullCell.getFgColor()
|
|
362
|
+
const nullBg = nullCell.getBgColor()
|
|
363
|
+
|
|
364
|
+
return (
|
|
365
|
+
fgColor === nullFg &&
|
|
366
|
+
bgColor === nullBg &&
|
|
367
|
+
!cell.isBold() &&
|
|
368
|
+
!cell.isItalic() &&
|
|
369
|
+
!cell.isUnderline() &&
|
|
370
|
+
!cell.isBlink() &&
|
|
371
|
+
!cell.isInverse() &&
|
|
372
|
+
!cell.isInvisible() &&
|
|
373
|
+
!cell.isDim() &&
|
|
374
|
+
!cell.isStrikethrough()
|
|
375
|
+
)
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
protected _nextCell(cell: IBufferCell, _oldCell: IBufferCell, row: number, col: number): void {
|
|
379
|
+
const isPlaceHolderCell = cell.getWidth() === 0
|
|
380
|
+
|
|
381
|
+
if (isPlaceHolderCell) {
|
|
382
|
+
return
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const codepoint = cell.getCode()
|
|
386
|
+
const isInvalidCodepoint = codepoint > 0x10ffff || (codepoint >= 0xd800 && codepoint <= 0xdfff)
|
|
387
|
+
const isGarbage = isInvalidCodepoint || (codepoint >= 0xf000 && cell.getWidth() === 1)
|
|
388
|
+
const isEmptyCell = codepoint === 0 || cell.getChars() === "" || isGarbage
|
|
389
|
+
|
|
390
|
+
const sgrSeq = this._diffStyle(cell, this._cursorStyle)
|
|
391
|
+
|
|
392
|
+
const styleChanged = isEmptyCell ? !equalBg(this._cursorStyle, cell) : sgrSeq.length > 0
|
|
393
|
+
|
|
394
|
+
if (styleChanged) {
|
|
395
|
+
if (this._nullCellCount > 0) {
|
|
396
|
+
this._currentRow += " ".repeat(this._nullCellCount)
|
|
397
|
+
this._nullCellCount = 0
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
this._lastContentCursorRow = this._lastCursorRow = row
|
|
401
|
+
this._lastContentCursorCol = this._lastCursorCol = col
|
|
402
|
+
|
|
403
|
+
this._currentRow += `\u001b[${sgrSeq.join(";")}m`
|
|
404
|
+
|
|
405
|
+
const line = this._buffer.getLine(row)
|
|
406
|
+
const cellFromLine = line?.getCell(col)
|
|
407
|
+
if (cellFromLine) {
|
|
408
|
+
this._cursorStyle = cellFromLine
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (isEmptyCell) {
|
|
413
|
+
this._nullCellCount += cell.getWidth()
|
|
414
|
+
} else {
|
|
415
|
+
if (this._nullCellCount > 0) {
|
|
416
|
+
this._currentRow += " ".repeat(this._nullCellCount)
|
|
417
|
+
this._nullCellCount = 0
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
this._currentRow += cell.getChars()
|
|
421
|
+
|
|
422
|
+
this._lastContentCursorRow = this._lastCursorRow = row
|
|
423
|
+
this._lastContentCursorCol = this._lastCursorCol = col + cell.getWidth()
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
protected _serializeString(excludeFinalCursorPosition?: boolean): string {
|
|
428
|
+
let rowEnd = this._allRows.length
|
|
429
|
+
|
|
430
|
+
if (this._buffer.length - this._firstRow <= this._terminal.rows) {
|
|
431
|
+
rowEnd = this._lastContentCursorRow + 1 - this._firstRow
|
|
432
|
+
this._lastCursorCol = this._lastContentCursorCol
|
|
433
|
+
this._lastCursorRow = this._lastContentCursorRow
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
let content = ""
|
|
437
|
+
|
|
438
|
+
for (let i = 0; i < rowEnd; i++) {
|
|
439
|
+
content += this._allRows[i]
|
|
440
|
+
if (i + 1 < rowEnd) {
|
|
441
|
+
content += this._allRowSeparators[i]
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (!excludeFinalCursorPosition) {
|
|
446
|
+
const absoluteCursorRow = (this._buffer.baseY ?? 0) + this._buffer.cursorY
|
|
447
|
+
const cursorRow = constrain(absoluteCursorRow - this._firstRow + 1, 1, Number.MAX_SAFE_INTEGER)
|
|
448
|
+
const cursorCol = this._buffer.cursorX + 1
|
|
449
|
+
content += `\u001b[${cursorRow};${cursorCol}H`
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return content
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// ============================================================================
|
|
457
|
+
// SerializeAddon Class
|
|
458
|
+
// ============================================================================
|
|
459
|
+
|
|
460
|
+
export class SerializeAddon implements ITerminalAddon {
|
|
461
|
+
private _terminal?: ITerminalCore
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Activate the addon (called by Terminal.loadAddon)
|
|
465
|
+
*/
|
|
466
|
+
public activate(terminal: ITerminalCore): void {
|
|
467
|
+
this._terminal = terminal
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Dispose the addon and clean up resources
|
|
472
|
+
*/
|
|
473
|
+
public dispose(): void {
|
|
474
|
+
this._terminal = undefined
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Serializes terminal rows into a string that can be written back to the
|
|
479
|
+
* terminal to restore the state. The cursor will also be positioned to the
|
|
480
|
+
* correct cell.
|
|
481
|
+
*
|
|
482
|
+
* @param options Custom options to allow control over what gets serialized.
|
|
483
|
+
*/
|
|
484
|
+
public serialize(options?: ISerializeOptions): string {
|
|
485
|
+
if (!this._terminal) {
|
|
486
|
+
throw new Error("Cannot use addon until it has been loaded")
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const terminal = this._terminal as any
|
|
490
|
+
const buffer = terminal.buffer
|
|
491
|
+
|
|
492
|
+
if (!buffer) {
|
|
493
|
+
return ""
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const normalBuffer = buffer.normal || buffer.active
|
|
497
|
+
const altBuffer = buffer.alternate
|
|
498
|
+
|
|
499
|
+
if (!normalBuffer) {
|
|
500
|
+
return ""
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
let content = options?.range
|
|
504
|
+
? this._serializeBufferByRange(normalBuffer, options.range, true)
|
|
505
|
+
: this._serializeBufferByScrollback(normalBuffer, options?.scrollback)
|
|
506
|
+
|
|
507
|
+
if (!options?.excludeAltBuffer && buffer.active?.type === "alternate" && altBuffer) {
|
|
508
|
+
const alternateContent = this._serializeBufferByScrollback(altBuffer, undefined)
|
|
509
|
+
content += `\u001b[?1049h\u001b[H${alternateContent}`
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
return content
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Serializes terminal content as plain text (no escape sequences)
|
|
517
|
+
* @param options Custom options to allow control over what gets serialized.
|
|
518
|
+
*/
|
|
519
|
+
public serializeAsText(options?: { scrollback?: number; trimWhitespace?: boolean }): string {
|
|
520
|
+
if (!this._terminal) {
|
|
521
|
+
throw new Error("Cannot use addon until it has been loaded")
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const terminal = this._terminal as any
|
|
525
|
+
const buffer = terminal.buffer
|
|
526
|
+
|
|
527
|
+
if (!buffer) {
|
|
528
|
+
return ""
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const activeBuffer = buffer.active || buffer.normal
|
|
532
|
+
if (!activeBuffer) {
|
|
533
|
+
return ""
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const maxRows = activeBuffer.length
|
|
537
|
+
const scrollback = options?.scrollback
|
|
538
|
+
const correctRows = scrollback === undefined ? maxRows : constrain(scrollback + this._terminal.rows, 0, maxRows)
|
|
539
|
+
|
|
540
|
+
const startRow = maxRows - correctRows
|
|
541
|
+
const endRow = maxRows - 1
|
|
542
|
+
const lines: string[] = []
|
|
543
|
+
|
|
544
|
+
for (let row = startRow; row <= endRow; row++) {
|
|
545
|
+
const line = activeBuffer.getLine(row)
|
|
546
|
+
if (line) {
|
|
547
|
+
const text = line.translateToString(options?.trimWhitespace ?? true)
|
|
548
|
+
lines.push(text)
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Trim trailing empty lines if requested
|
|
553
|
+
if (options?.trimWhitespace) {
|
|
554
|
+
while (lines.length > 0 && lines[lines.length - 1] === "") {
|
|
555
|
+
lines.pop()
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
return lines.join("\n")
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
private _serializeBufferByScrollback(buffer: IBuffer, scrollback?: number): string {
|
|
563
|
+
const maxRows = buffer.length
|
|
564
|
+
const rows = this._terminal?.rows ?? 24
|
|
565
|
+
const correctRows = scrollback === undefined ? maxRows : constrain(scrollback + rows, 0, maxRows)
|
|
566
|
+
return this._serializeBufferByRange(
|
|
567
|
+
buffer,
|
|
568
|
+
{
|
|
569
|
+
start: maxRows - correctRows,
|
|
570
|
+
end: maxRows - 1,
|
|
571
|
+
},
|
|
572
|
+
false,
|
|
573
|
+
)
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
private _serializeBufferByRange(
|
|
577
|
+
buffer: IBuffer,
|
|
578
|
+
range: ISerializeRange,
|
|
579
|
+
excludeFinalCursorPosition: boolean,
|
|
580
|
+
): string {
|
|
581
|
+
const handler = new StringSerializeHandler(buffer, this._terminal!)
|
|
582
|
+
const cols = this._terminal?.cols ?? 80
|
|
583
|
+
return handler.serialize(
|
|
584
|
+
{
|
|
585
|
+
start: { x: 0, y: range.start },
|
|
586
|
+
end: { x: cols, y: range.end },
|
|
587
|
+
},
|
|
588
|
+
excludeFinalCursorPosition,
|
|
589
|
+
)
|
|
590
|
+
}
|
|
591
|
+
}
|