@deckbuilderkit/terminal 0.0.0-stage → 0.1.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/package.json +47 -4
- package/src/TerminalPane.tsx +590 -0
- package/src/app-keys.ts +10 -0
- package/src/appearance.ts +56 -0
- package/src/client.ts +63 -0
- package/src/index.ts +13 -0
- package/src/keys.ts +48 -0
- package/src/paste.ts +64 -0
- package/src/protocol.ts +146 -0
- package/src/schemes.ts +145 -0
- package/src/server/index.ts +8 -0
- package/src/server/integration/bash.sh +62 -0
- package/src/server/integration/fish.fish +21 -0
- package/src/server/integration/pwsh.ps1 +40 -0
- package/src/server/integration/scripts.d.ts +16 -0
- package/src/server/integration/zsh-forward.zsh +7 -0
- package/src/server/integration/zshrc.zsh +22 -0
- package/src/server/integration.ts +107 -0
- package/src/server/launchers.ts +70 -0
- package/src/server/profiles.ts +40 -0
- package/src/server/pty.ts +75 -0
- package/src/server/routes.ts +232 -0
- package/src/server/sessions.ts +318 -0
- package/src/server/shells.ts +94 -0
- package/src/shell-integration.ts +46 -0
- package/src/terminal.css +76 -0
- package/src/terminalOptions.ts +13 -0
- package/src/theme.ts +44 -0
- package/src/themes.ts +49 -0
- package/src/view.ts +12 -0
- package/src/xterm-view.ts +424 -0
- package/README.md +0 -3
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
import { ClipboardAddon, type ClipboardSelectionType } from '@xterm/addon-clipboard';
|
|
2
|
+
import { FitAddon } from '@xterm/addon-fit';
|
|
3
|
+
import { SearchAddon } from '@xterm/addon-search';
|
|
4
|
+
import { Unicode11Addon } from '@xterm/addon-unicode11';
|
|
5
|
+
import { WebLinksAddon } from '@xterm/addon-web-links';
|
|
6
|
+
import { WebglAddon } from '@xterm/addon-webgl';
|
|
7
|
+
import { Terminal, type FontWeight, type IDecoration, type IMarker, type ITheme } from '@xterm/xterm';
|
|
8
|
+
import '@xterm/xterm/css/xterm.css';
|
|
9
|
+
import { t } from '@deckbuilderkit/ui';
|
|
10
|
+
import { trimCopied, type PasteWarning } from './paste.ts';
|
|
11
|
+
import type { ColorScheme } from './schemes.ts';
|
|
12
|
+
import { parseOsc133, parseOsc7 } from './shell-integration.ts';
|
|
13
|
+
import { DEFAULT_WORD_SEPARATORS } from './terminalOptions.ts';
|
|
14
|
+
import { readCssToken } from './theme.ts';
|
|
15
|
+
import { themeOf } from './themes.ts';
|
|
16
|
+
import type { TerminalView } from './view.ts';
|
|
17
|
+
|
|
18
|
+
export type TerminalFontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900';
|
|
19
|
+
|
|
20
|
+
/** Look and behavior preferences; a missing field keeps the built-in default. Applied live by `setOptions`. */
|
|
21
|
+
export interface TerminalOptions {
|
|
22
|
+
/** Empty follows the `--font-mono` token. */
|
|
23
|
+
fontFamily?: string;
|
|
24
|
+
fontSize?: number;
|
|
25
|
+
cursorStyle?: 'block' | 'bar' | 'underline';
|
|
26
|
+
cursorBlink?: boolean;
|
|
27
|
+
scrollback?: number;
|
|
28
|
+
copyOnSelect?: boolean;
|
|
29
|
+
rightClickPaste?: boolean;
|
|
30
|
+
lineHeight?: number;
|
|
31
|
+
letterSpacing?: number;
|
|
32
|
+
fontWeight?: TerminalFontWeight;
|
|
33
|
+
fontWeightBold?: TerminalFontWeight;
|
|
34
|
+
/** A preset of `themes.ts`; 'app' follows the app palette. */
|
|
35
|
+
theme?: string;
|
|
36
|
+
minimumContrastRatio?: number;
|
|
37
|
+
/** CSS ligatures; they need the DOM renderer, so `gpuAcceleration: 'auto'` then skips WebGL. */
|
|
38
|
+
ligatures?: boolean;
|
|
39
|
+
gpuAcceleration?: 'auto' | 'on' | 'off';
|
|
40
|
+
/** What a bell does in the pane: 'sound'/'both' beep (the tab marker is the app's). */
|
|
41
|
+
bellStyle?: 'tab' | 'sound' | 'both' | 'none';
|
|
42
|
+
wordSeparators?: string;
|
|
43
|
+
scrollSensitivity?: number;
|
|
44
|
+
fastScrollSensitivity?: number;
|
|
45
|
+
altIsMeta?: boolean;
|
|
46
|
+
/** Gutter dots at commands reported by shell integration (OSC 133). */
|
|
47
|
+
commandDecorations?: boolean;
|
|
48
|
+
/** Custom color schemes `theme` may name. */
|
|
49
|
+
schemes?: ColorScheme[];
|
|
50
|
+
/** Space around the text in pixels (0 to 48, default 8). */
|
|
51
|
+
padding?: number;
|
|
52
|
+
scrollbar?: 'visible' | 'hover' | 'hidden';
|
|
53
|
+
/** Percent a pane without the focus is dimmed in a split (0 to 80); applied by the pane. */
|
|
54
|
+
dimUnfocused?: number;
|
|
55
|
+
/** When a paste asks first; applied by the pane. */
|
|
56
|
+
pasteWarning?: PasteWarning;
|
|
57
|
+
/** Characters from which a paste counts as large. */
|
|
58
|
+
pasteLargeSize?: number;
|
|
59
|
+
/** Copied lines lose their trailing spaces (default true). */
|
|
60
|
+
trimCopy?: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
export interface XtermViewOptions {
|
|
65
|
+
fontSize?: number;
|
|
66
|
+
options?: TerminalOptions;
|
|
67
|
+
/** A preview: no input, no fitting; it renders at the size `resize` sets. */
|
|
68
|
+
readOnly?: boolean;
|
|
69
|
+
/** Called on every key event before xterm; false means xterm ignores it (it bubbles on). */
|
|
70
|
+
onKey?: (e: KeyboardEvent) => boolean;
|
|
71
|
+
/** The shell reported its folder (OSC 7). */
|
|
72
|
+
onCwd?: (cwd: string) => void;
|
|
73
|
+
onBell?: () => void;
|
|
74
|
+
/** Shell integration: a command started (true) or finished (false, with its exit code). */
|
|
75
|
+
onCommand?: (running: boolean, code?: number | null) => void;
|
|
76
|
+
/** Called on every paste (keyboard, right-click); true means it took the text and xterm does not paste it. */
|
|
77
|
+
onPaste?: (text: string) => boolean;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export type ScrollKind = 'lineUp' | 'lineDown' | 'pageUp' | 'pageDown' | 'top' | 'bottom';
|
|
81
|
+
|
|
82
|
+
/** A prompt reported by OSC 133, and the gutter dot of its command. */
|
|
83
|
+
interface Prompt {
|
|
84
|
+
marker: IMarker;
|
|
85
|
+
started: boolean;
|
|
86
|
+
done: boolean;
|
|
87
|
+
code: number | null;
|
|
88
|
+
deco?: IDecoration;
|
|
89
|
+
el?: HTMLElement;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface FindOptions {
|
|
93
|
+
caseSensitive?: boolean;
|
|
94
|
+
regex?: boolean;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Match highlights; the addon only takes #RRGGBB. */
|
|
98
|
+
const FIND_DECORATIONS = {
|
|
99
|
+
matchBackground: '#5a4fa0',
|
|
100
|
+
matchBorder: '#5a4fa0',
|
|
101
|
+
matchOverviewRuler: '#9b8cff',
|
|
102
|
+
activeMatchBackground: '#c9a227',
|
|
103
|
+
activeMatchBorder: '#e6c34a',
|
|
104
|
+
activeMatchColorOverviewRuler: '#e6c34a',
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export interface XtermView extends TerminalView {
|
|
108
|
+
fit(): void;
|
|
109
|
+
focus(): void;
|
|
110
|
+
setTheme(theme: ITheme): void;
|
|
111
|
+
setOptions(options: TerminalOptions): void;
|
|
112
|
+
hasSelection(): boolean;
|
|
113
|
+
getSelection(): string;
|
|
114
|
+
/** The current size in cells. */
|
|
115
|
+
size(): { cols: number; rows: number };
|
|
116
|
+
/** Clears the screen and scrollback before a replay. */
|
|
117
|
+
reset(): void;
|
|
118
|
+
/** Selects and scrolls to the next (or previous) match of `term`; false when none. */
|
|
119
|
+
findNext(term: string, o?: FindOptions): boolean;
|
|
120
|
+
findPrevious(term: string, o?: FindOptions): boolean;
|
|
121
|
+
/** Reports the active match index (-1 when none) and the count after each search or output. */
|
|
122
|
+
onFindResults(cb: (index: number, count: number) => void): void;
|
|
123
|
+
/** Removes the highlights and the match selection. */
|
|
124
|
+
clearFind(): void;
|
|
125
|
+
/** Scrolls to the previous (-1) or next (1) prompt that shell integration marked; false when none. */
|
|
126
|
+
scrollToPrompt(dir: 1 | -1): boolean;
|
|
127
|
+
/** Pastes `text` as typed by the user (bracketed when the program asked for it). */
|
|
128
|
+
paste(text: string): void;
|
|
129
|
+
/** The program turned bracketed paste mode on. */
|
|
130
|
+
bracketedPaste(): boolean;
|
|
131
|
+
/** Clears the screen and scrollback, keeping the prompt line. */
|
|
132
|
+
clear(): void;
|
|
133
|
+
scroll(kind: ScrollKind): void;
|
|
134
|
+
/** The xterm root element, measured to scale a preview. */
|
|
135
|
+
element(): HTMLElement;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Opens http(s) links in the browser; any other scheme (OSC 8 included) is ignored. */
|
|
139
|
+
function openLink(uri: string) {
|
|
140
|
+
if (/^https?:\/\//i.test(uri)) window.open(uri, '_blank', 'noopener');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function createXtermView(el: HTMLElement, opts: XtermViewOptions = {}): XtermView {
|
|
144
|
+
let themeId = 'app';
|
|
145
|
+
let schemes: ColorScheme[] | undefined;
|
|
146
|
+
const tokenTheme = () => {
|
|
147
|
+
const theme = themeOf(themeId, readCssToken(), schemes);
|
|
148
|
+
if (theme.background) el.style.setProperty('--terminal-bg', theme.background);
|
|
149
|
+
else el.style.removeProperty('--terminal-bg');
|
|
150
|
+
return theme;
|
|
151
|
+
};
|
|
152
|
+
const term = new Terminal({
|
|
153
|
+
allowProposedApi: true, // the unicode addon's `unicode.activeVersion`
|
|
154
|
+
fontFamily: readCssToken()('--font-mono').trim() || 'monospace',
|
|
155
|
+
fontSize: opts.fontSize ?? 13,
|
|
156
|
+
disableStdin: !!opts.readOnly,
|
|
157
|
+
cursorBlink: !opts.readOnly,
|
|
158
|
+
scrollback: 5000,
|
|
159
|
+
theme: tokenTheme(),
|
|
160
|
+
linkHandler: { activate: (_e, uri) => openLink(uri), allowNonHttpProtocols: false },
|
|
161
|
+
});
|
|
162
|
+
const fitAddon = new FitAddon();
|
|
163
|
+
term.loadAddon(fitAddon);
|
|
164
|
+
const searchAddon = new SearchAddon();
|
|
165
|
+
term.loadAddon(searchAddon);
|
|
166
|
+
term.loadAddon(new Unicode11Addon());
|
|
167
|
+
term.unicode.activeVersion = '11';
|
|
168
|
+
term.loadAddon(new WebLinksAddon((_e, uri) => openLink(uri)));
|
|
169
|
+
if (!opts.readOnly) {
|
|
170
|
+
// OSC 52: programs may write the clipboard, never read it.
|
|
171
|
+
term.loadAddon(
|
|
172
|
+
new ClipboardAddon(undefined, {
|
|
173
|
+
readText: (_selection: ClipboardSelectionType) => '',
|
|
174
|
+
writeText: async (_selection: ClipboardSelectionType, text: string) => {
|
|
175
|
+
await navigator.clipboard?.writeText(text).catch(() => {});
|
|
176
|
+
},
|
|
177
|
+
}),
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
if (opts.onKey) term.attachCustomKeyEventHandler(opts.onKey);
|
|
181
|
+
term.open(el);
|
|
182
|
+
|
|
183
|
+
const fit = () => {
|
|
184
|
+
if (opts.readOnly || !el.isConnected || el.clientWidth === 0 || el.clientHeight === 0) return;
|
|
185
|
+
try {
|
|
186
|
+
fitAddon.fit();
|
|
187
|
+
} catch {
|
|
188
|
+
// not rendered yet
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
const resizeObserver = new ResizeObserver(() => fit());
|
|
192
|
+
resizeObserver.observe(el);
|
|
193
|
+
// Palette switches change the tokens on <html>.
|
|
194
|
+
const themeObserver = new MutationObserver(() => {
|
|
195
|
+
term.options.theme = tokenTheme();
|
|
196
|
+
});
|
|
197
|
+
themeObserver.observe(document.documentElement, { attributes: true });
|
|
198
|
+
|
|
199
|
+
// WebGL renderer: 'auto' gives up for good after a failure or a lost context; 'on' tries again on the next options change.
|
|
200
|
+
let webgl: WebglAddon | null = null;
|
|
201
|
+
let webglFailed = false;
|
|
202
|
+
const dropWebgl = () => {
|
|
203
|
+
webgl?.dispose();
|
|
204
|
+
webgl = null;
|
|
205
|
+
};
|
|
206
|
+
const setRenderer = (mode: 'auto' | 'on' | 'off', ligatures: boolean) => {
|
|
207
|
+
const want = mode === 'on' || (mode === 'auto' && !ligatures && !webglFailed);
|
|
208
|
+
if (!want) return dropWebgl();
|
|
209
|
+
if (webgl) return;
|
|
210
|
+
try {
|
|
211
|
+
const addon = new WebglAddon();
|
|
212
|
+
addon.onContextLoss(() => {
|
|
213
|
+
webglFailed = true;
|
|
214
|
+
dropWebgl();
|
|
215
|
+
});
|
|
216
|
+
term.loadAddon(addon);
|
|
217
|
+
webgl = addon;
|
|
218
|
+
} catch {
|
|
219
|
+
webglFailed = true;
|
|
220
|
+
webgl = null;
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
// Shell integration (OSC 133): a marker per prompt, a gutter dot per command.
|
|
225
|
+
const prompts: Prompt[] = [];
|
|
226
|
+
let decorate = false;
|
|
227
|
+
const paint = (p: Prompt) => {
|
|
228
|
+
if (!p.el) return;
|
|
229
|
+
p.el.classList.add('terminal-mark');
|
|
230
|
+
p.el.classList.toggle('running', !p.done);
|
|
231
|
+
p.el.classList.toggle('ok', p.done && p.code === 0);
|
|
232
|
+
p.el.classList.toggle('err', p.done && p.code !== null && p.code !== 0);
|
|
233
|
+
p.el.title = !p.done ? t('terminal.mark.running') : p.code === null ? t('terminal.mark.done') : t('terminal.mark.exit', { code: p.code });
|
|
234
|
+
};
|
|
235
|
+
const decorateOne = (p: Prompt) => {
|
|
236
|
+
if (!decorate || p.deco || !p.started || p.marker.isDisposed) return;
|
|
237
|
+
p.deco = term.registerDecoration({ marker: p.marker, x: 0, width: 1 });
|
|
238
|
+
p.deco?.onRender((elem) => {
|
|
239
|
+
p.el = elem;
|
|
240
|
+
paint(p);
|
|
241
|
+
});
|
|
242
|
+
};
|
|
243
|
+
const setDecorations = (on: boolean) => {
|
|
244
|
+
if (on === decorate) return;
|
|
245
|
+
decorate = on;
|
|
246
|
+
for (const p of prompts) {
|
|
247
|
+
if (on) decorateOne(p);
|
|
248
|
+
else {
|
|
249
|
+
p.deco?.dispose();
|
|
250
|
+
p.deco = undefined;
|
|
251
|
+
p.el = undefined;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
const current = (): Prompt => {
|
|
256
|
+
const last = prompts[prompts.length - 1];
|
|
257
|
+
if (last && !last.done && !last.marker.isDisposed) return last;
|
|
258
|
+
const marker = term.registerMarker(0);
|
|
259
|
+
const p: Prompt = { marker, started: false, done: false, code: null };
|
|
260
|
+
if (marker) prompts.push(p);
|
|
261
|
+
return p;
|
|
262
|
+
};
|
|
263
|
+
const oscSubs = [
|
|
264
|
+
term.parser.registerOscHandler(133, (data) => {
|
|
265
|
+
const mark = parseOsc133(data);
|
|
266
|
+
if (!mark) return true;
|
|
267
|
+
if (mark.kind === 'A') {
|
|
268
|
+
const last = prompts[prompts.length - 1];
|
|
269
|
+
if (last && !last.started) {
|
|
270
|
+
last.marker.dispose();
|
|
271
|
+
prompts.pop();
|
|
272
|
+
}
|
|
273
|
+
current();
|
|
274
|
+
} else if (mark.kind === 'C') {
|
|
275
|
+
const p = current();
|
|
276
|
+
p.started = true;
|
|
277
|
+
decorateOne(p);
|
|
278
|
+
opts.onCommand?.(true);
|
|
279
|
+
} else if (mark.kind === 'D') {
|
|
280
|
+
const p = prompts[prompts.length - 1];
|
|
281
|
+
if (p?.started && !p.done) {
|
|
282
|
+
p.done = true;
|
|
283
|
+
p.code = mark.code;
|
|
284
|
+
paint(p);
|
|
285
|
+
opts.onCommand?.(false, mark.code);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return true;
|
|
289
|
+
}),
|
|
290
|
+
term.parser.registerOscHandler(7, (data) => {
|
|
291
|
+
const cwd = parseOsc7(data);
|
|
292
|
+
if (cwd) opts.onCwd?.(cwd);
|
|
293
|
+
return false;
|
|
294
|
+
}),
|
|
295
|
+
term.onBell(() => opts.onBell?.()),
|
|
296
|
+
];
|
|
297
|
+
const scrollToPrompt = (dir: 1 | -1) => {
|
|
298
|
+
const lines = prompts.filter((p) => !p.marker.isDisposed).map((p) => p.marker.line);
|
|
299
|
+
const top = term.buffer.active.viewportY;
|
|
300
|
+
const target = dir < 0 ? [...lines].reverse().find((l) => l < top) : lines.find((l) => l > top);
|
|
301
|
+
if (target === undefined) {
|
|
302
|
+
if (dir > 0) term.scrollToBottom();
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
term.scrollToLine(target);
|
|
306
|
+
return true;
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
let copyOnSelect = false;
|
|
310
|
+
let rightClickPaste = false;
|
|
311
|
+
let trimCopy = true;
|
|
312
|
+
const selection = () => (trimCopy ? trimCopied(term.getSelection()) : term.getSelection());
|
|
313
|
+
const setOptions = (o: TerminalOptions) => {
|
|
314
|
+
term.options.fontFamily = o.fontFamily?.trim() || readCssToken()('--font-mono').trim() || 'monospace';
|
|
315
|
+
term.options.fontSize = o.fontSize ?? opts.fontSize ?? 13;
|
|
316
|
+
term.options.cursorStyle = o.cursorStyle ?? 'block';
|
|
317
|
+
term.options.cursorBlink = (o.cursorBlink ?? true) && !opts.readOnly;
|
|
318
|
+
term.options.scrollback = o.scrollback ?? 5000;
|
|
319
|
+
term.options.lineHeight = o.lineHeight ?? 1;
|
|
320
|
+
term.options.letterSpacing = o.letterSpacing ?? 0;
|
|
321
|
+
term.options.fontWeight = (o.fontWeight ?? 'normal') as FontWeight;
|
|
322
|
+
term.options.fontWeightBold = (o.fontWeightBold ?? 'bold') as FontWeight;
|
|
323
|
+
term.options.minimumContrastRatio = o.minimumContrastRatio ?? 1;
|
|
324
|
+
term.options.wordSeparator = o.wordSeparators ?? DEFAULT_WORD_SEPARATORS;
|
|
325
|
+
term.options.scrollSensitivity = o.scrollSensitivity ?? 1;
|
|
326
|
+
term.options.fastScrollSensitivity = o.fastScrollSensitivity ?? 5;
|
|
327
|
+
term.options.macOptionIsMeta = !!o.altIsMeta;
|
|
328
|
+
if ((o.theme ?? 'app') !== themeId || o.schemes !== schemes) {
|
|
329
|
+
themeId = o.theme ?? 'app';
|
|
330
|
+
schemes = o.schemes;
|
|
331
|
+
term.options.theme = tokenTheme();
|
|
332
|
+
}
|
|
333
|
+
if (!opts.readOnly) el.style.setProperty('--terminal-padding', `${Math.max(0, Math.min(48, o.padding ?? 8))}px`);
|
|
334
|
+
el.classList.toggle('scrollbar-hover', o.scrollbar === 'hover');
|
|
335
|
+
el.classList.toggle('scrollbar-hidden', o.scrollbar === 'hidden');
|
|
336
|
+
trimCopy = o.trimCopy ?? true;
|
|
337
|
+
el.classList.toggle('ligatures', !!o.ligatures);
|
|
338
|
+
setRenderer(o.gpuAcceleration ?? 'auto', !!o.ligatures);
|
|
339
|
+
setDecorations(o.commandDecorations ?? true);
|
|
340
|
+
copyOnSelect = !!o.copyOnSelect;
|
|
341
|
+
rightClickPaste = !!o.rightClickPaste;
|
|
342
|
+
fit();
|
|
343
|
+
};
|
|
344
|
+
if (opts.options) setOptions(opts.options);
|
|
345
|
+
else {
|
|
346
|
+
setRenderer('auto', false);
|
|
347
|
+
setDecorations(true);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const subs: { dispose(): void }[] = [...oscSubs];
|
|
351
|
+
if (!opts.readOnly) {
|
|
352
|
+
subs.push(
|
|
353
|
+
term.onSelectionChange(() => {
|
|
354
|
+
if (copyOnSelect && term.hasSelection()) void navigator.clipboard?.writeText(selection()).catch(() => {});
|
|
355
|
+
}),
|
|
356
|
+
);
|
|
357
|
+
const onContextMenu = (e: MouseEvent) => {
|
|
358
|
+
if (!rightClickPaste) return;
|
|
359
|
+
e.preventDefault();
|
|
360
|
+
void navigator.clipboard?.readText().then((text) => text && !opts.onPaste?.(text) && term.paste(text), () => {});
|
|
361
|
+
};
|
|
362
|
+
el.addEventListener('contextmenu', onContextMenu);
|
|
363
|
+
// Capture: before xterm's own paste handler on its textarea.
|
|
364
|
+
const onPasteEvent = (e: ClipboardEvent) => {
|
|
365
|
+
const text = e.clipboardData?.getData('text/plain');
|
|
366
|
+
if (!text || !opts.onPaste?.(text)) return;
|
|
367
|
+
e.preventDefault();
|
|
368
|
+
e.stopImmediatePropagation();
|
|
369
|
+
};
|
|
370
|
+
el.addEventListener('paste', onPasteEvent, true);
|
|
371
|
+
subs.push({
|
|
372
|
+
dispose: () => {
|
|
373
|
+
el.removeEventListener('contextmenu', onContextMenu);
|
|
374
|
+
el.removeEventListener('paste', onPasteEvent, true);
|
|
375
|
+
},
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
return {
|
|
379
|
+
write: (data) => term.write(data),
|
|
380
|
+
resize: (cols, rows) => {
|
|
381
|
+
if (cols !== term.cols || rows !== term.rows) term.resize(cols, rows);
|
|
382
|
+
},
|
|
383
|
+
onInput: (cb) => void subs.push(term.onData(cb)),
|
|
384
|
+
onResize: (cb) => void subs.push(term.onResize(({ cols, rows }) => cb(cols, rows))),
|
|
385
|
+
fit,
|
|
386
|
+
focus: () => term.focus(),
|
|
387
|
+
setTheme: (theme) => {
|
|
388
|
+
term.options.theme = theme;
|
|
389
|
+
},
|
|
390
|
+
setOptions,
|
|
391
|
+
hasSelection: () => term.hasSelection(),
|
|
392
|
+
getSelection: selection,
|
|
393
|
+
size: () => ({ cols: term.cols, rows: term.rows }),
|
|
394
|
+
reset: () => {
|
|
395
|
+
for (const p of prompts.splice(0)) {
|
|
396
|
+
p.deco?.dispose();
|
|
397
|
+
p.marker.dispose();
|
|
398
|
+
}
|
|
399
|
+
term.reset();
|
|
400
|
+
},
|
|
401
|
+
scrollToPrompt,
|
|
402
|
+
paste: (text) => term.paste(text),
|
|
403
|
+
bracketedPaste: () => term.modes.bracketedPasteMode,
|
|
404
|
+
clear: () => term.clear(),
|
|
405
|
+
scroll: (kind) => {
|
|
406
|
+
if (kind === 'lineUp' || kind === 'lineDown') term.scrollLines(kind === 'lineUp' ? -1 : 1);
|
|
407
|
+
else if (kind === 'pageUp' || kind === 'pageDown') term.scrollPages(kind === 'pageUp' ? -1 : 1);
|
|
408
|
+
else if (kind === 'top') term.scrollToTop();
|
|
409
|
+
else term.scrollToBottom();
|
|
410
|
+
},
|
|
411
|
+
findNext: (q, o) => searchAddon.findNext(q, { ...o, decorations: FIND_DECORATIONS }),
|
|
412
|
+
findPrevious: (q, o) => searchAddon.findPrevious(q, { ...o, decorations: FIND_DECORATIONS }),
|
|
413
|
+
onFindResults: (cb) => void subs.push(searchAddon.onDidChangeResults(({ resultIndex, resultCount }) => cb(resultIndex, resultCount))),
|
|
414
|
+
clearFind: () => searchAddon.clearDecorations(),
|
|
415
|
+
element: () => term.element ?? el,
|
|
416
|
+
dispose: () => {
|
|
417
|
+
resizeObserver.disconnect();
|
|
418
|
+
themeObserver.disconnect();
|
|
419
|
+
for (const s of subs) s.dispose();
|
|
420
|
+
dropWebgl();
|
|
421
|
+
term.dispose();
|
|
422
|
+
},
|
|
423
|
+
};
|
|
424
|
+
}
|
package/README.md
DELETED