Merge branch 'master' into anthonykim1/scaffoldKittyAddon
This commit is contained in:
+98
-80
@@ -16,9 +16,12 @@ if ('WebAssembly' in window) {
|
||||
import { Terminal, ITerminalOptions, type ITheme } from '@xterm/xterm';
|
||||
import { AttachAddon } from '@xterm/addon-attach';
|
||||
import { AddonImageWindow } from './components/window/addonImageWindow';
|
||||
import { AddonLigaturesWindow } from './components/window/addonLigaturesWindow';
|
||||
import { AddonProgressWindow } from './components/window/addonProgressWindow';
|
||||
import { AddonSearchWindow } from './components/window/addonSearchWindow';
|
||||
import { AddonSerializeWindow } from './components/window/addonSerializeWindow';
|
||||
import { AddonWebFontsWindow } from './components/window/addonWebFontsWindow';
|
||||
import { AddonWebLinksWindow } from './components/window/addonWebLinksWindow';
|
||||
import { AddonsWindow } from './components/window/addonsWindow';
|
||||
import { CellInspectorWindow } from './components/window/cellInspectorWindow';
|
||||
import { ControlBar } from './components/controlBar';
|
||||
@@ -58,11 +61,11 @@ export interface IWindowWithTerminal extends Window {
|
||||
}
|
||||
declare let window: IWindowWithTerminal;
|
||||
|
||||
let term;
|
||||
let protocol;
|
||||
let socketURL;
|
||||
let socket;
|
||||
let pid;
|
||||
let term: Terminal | null;
|
||||
let protocol: string;
|
||||
let socketURL: string;
|
||||
let socket: WebSocket | null;
|
||||
let pid: string;
|
||||
let controlBar: ControlBar;
|
||||
let addonsWindow: AddonsWindow;
|
||||
let addonSearchWindow: AddonSearchWindow;
|
||||
@@ -73,7 +76,7 @@ const addons: AddonCollection = {
|
||||
attach: { name: 'attach', ctor: AttachAddon, canChange: false },
|
||||
clipboard: { name: 'clipboard', ctor: ClipboardAddon, canChange: true },
|
||||
fit: { name: 'fit', ctor: FitAddon, canChange: false },
|
||||
image: { name: 'image', ctor: ImageAddon, canChange: true },
|
||||
image: { name: 'image', ctor: ImageAddon!, canChange: true },
|
||||
progress: { name: 'progress', ctor: ProgressAddon, canChange: true },
|
||||
search: { name: 'search', ctor: SearchAddon, canChange: true },
|
||||
serialize: { name: 'serialize', ctor: SerializeAddon, canChange: true },
|
||||
@@ -116,8 +119,8 @@ const xtermjsTheme = {
|
||||
brightWhite: '#FFFFFF'
|
||||
} satisfies ITheme;
|
||||
function setPadding(): void {
|
||||
term.element.style.padding = parseInt(paddingElement.value, 10).toString() + 'px';
|
||||
addons.fit.instance.fit();
|
||||
term!.element!.style.padding = parseInt(paddingElement.value, 10).toString() + 'px';
|
||||
addons.fit.instance!.fit();
|
||||
}
|
||||
|
||||
function getSearchOptions(): ISearchOptions {
|
||||
@@ -141,7 +144,7 @@ const disposeRecreateButtonHandler: () => void = () => {
|
||||
if (term) {
|
||||
term.dispose();
|
||||
term = null;
|
||||
window.term = null;
|
||||
(window as any).term = null;
|
||||
socket = null;
|
||||
addons.attach.instance = undefined;
|
||||
addons.clipboard.instance = undefined;
|
||||
@@ -154,10 +157,10 @@ const disposeRecreateButtonHandler: () => void = () => {
|
||||
addons.ligatures.instance = undefined;
|
||||
addons.webLinks.instance = undefined;
|
||||
addons.webgl.instance = undefined;
|
||||
document.getElementById('dispose').innerHTML = 'Recreate Terminal';
|
||||
document.getElementById('dispose')!.innerHTML = 'Recreate Terminal';
|
||||
} else {
|
||||
createTerminal();
|
||||
document.getElementById('dispose').innerHTML = 'Dispose terminal';
|
||||
document.getElementById('dispose')!.innerHTML = 'Dispose terminal';
|
||||
}
|
||||
};
|
||||
|
||||
@@ -165,7 +168,7 @@ const createNewWindowButtonHandler: () => void = () => {
|
||||
if (term) {
|
||||
disposeRecreateButtonHandler();
|
||||
}
|
||||
const win = window.open();
|
||||
const win = window.open()!;
|
||||
terminalContainer = win.document.createElement('div');
|
||||
terminalContainer.id = 'terminal-container';
|
||||
win.document.body.appendChild(terminalContainer);
|
||||
@@ -207,17 +210,20 @@ if (document.location.pathname === '/test') {
|
||||
} else {
|
||||
const typedTerm = createTerminal();
|
||||
|
||||
controlBar = new ControlBar(document.getElementById('sidebar'), document.querySelector('.banner-tabs'), []);
|
||||
controlBar = new ControlBar(document.getElementById('sidebar')!, document.querySelector('.banner-tabs')!, []);
|
||||
optionsWindow = controlBar.registerWindow(new OptionsWindow(typedTerm, addons, { updateTerminalSize, updateTerminalContainerBackground }));
|
||||
const styleWindow = controlBar.registerWindow(new StyleWindow(typedTerm, addons));
|
||||
controlBar.registerWindow(new CellInspectorWindow(typedTerm, addons));
|
||||
controlBar.registerWindow(new VtWindow(typedTerm, addons));
|
||||
addonsWindow = controlBar.registerWindow(new AddonsWindow(typedTerm, addons));
|
||||
addonSearchWindow = controlBar.registerWindow(new AddonSearchWindow(typedTerm, addons), { afterId: 'addons', hidden: true, italics: true });
|
||||
controlBar.registerWindow(new AddonImageWindow(typedTerm, addons), { afterId: 'addons', hidden: true, italics: true });
|
||||
controlBar.registerWindow(new AddonLigaturesWindow(typedTerm, addons), { afterId: 'addon-image', hidden: true, italics: true });
|
||||
controlBar.registerWindow(new AddonProgressWindow(typedTerm, addons), { afterId: 'addon-ligatures', hidden: true, italics: true });
|
||||
addonSearchWindow = controlBar.registerWindow(new AddonSearchWindow(typedTerm, addons), { afterId: 'addon-progress', hidden: true, italics: true });
|
||||
controlBar.registerWindow(new AddonSerializeWindow(typedTerm, addons), { afterId: 'addon-search', hidden: true, italics: true });
|
||||
controlBar.registerWindow(new AddonImageWindow(typedTerm, addons), { afterId: 'addon-serialize', hidden: true, italics: true });
|
||||
controlBar.registerWindow(new AddonWebFontsWindow(typedTerm, addons), { afterId: 'addon-image', hidden: true, italics: true });
|
||||
addonWebglWindow = controlBar.registerWindow(new WebglWindow(typedTerm, addons), { afterId: 'addon-web-fonts', hidden: true, italics: true });
|
||||
controlBar.registerWindow(new AddonWebFontsWindow(typedTerm, addons), { afterId: 'addon-serialize', hidden: true, italics: true });
|
||||
controlBar.registerWindow(new AddonWebLinksWindow(typedTerm, addons), { afterId: 'addon-web-fonts', hidden: true, italics: true });
|
||||
addonWebglWindow = controlBar.registerWindow(new WebglWindow(typedTerm, addons), { afterId: 'addon-web-links', hidden: true, italics: true });
|
||||
controlBar.registerWindow(new TestWindow(typedTerm, addons, { disposeRecreateButtonHandler, createNewWindowButtonHandler }), { afterId: 'options' });
|
||||
actionElements = {
|
||||
findNext: addonSearchWindow.findNextInput,
|
||||
@@ -229,48 +235,51 @@ if (document.location.pathname === '/test') {
|
||||
// TODO: Most of below should be encapsulated within windows
|
||||
paddingElement = styleWindow.paddingElement;
|
||||
|
||||
controlBar.setTabVisible('addon-webgl', true);
|
||||
controlBar.setTabVisible('addon-image', !!addons.image.instance);
|
||||
controlBar.setTabVisible('addon-ligatures', !!addons.ligatures.instance);
|
||||
controlBar.setTabVisible('addon-progress', !!addons.progress.instance);
|
||||
controlBar.setTabVisible('addon-search', true);
|
||||
controlBar.setTabVisible('addon-serialize', true);
|
||||
controlBar.setTabVisible('addon-image', true);
|
||||
controlBar.setTabVisible('addon-web-fonts', true);
|
||||
addonWebglWindow.setTextureAtlas(addons.webgl.instance.textureAtlas);
|
||||
addons.webgl.instance.onChangeTextureAtlas(e => addonWebglWindow.setTextureAtlas(e));
|
||||
addons.webgl.instance.onAddTextureAtlasCanvas(e => addonWebglWindow.appendTextureAtlas(e));
|
||||
addons.webgl.instance.onRemoveTextureAtlasCanvas(e => addonWebglWindow.removeTextureAtlas(e));
|
||||
controlBar.setTabVisible('addon-web-links', !!addons.webLinks.instance);
|
||||
controlBar.setTabVisible('addon-webgl', true);
|
||||
addonWebglWindow.setTextureAtlas(addons.webgl.instance!.textureAtlas!);
|
||||
addons.webgl.instance!.onChangeTextureAtlas(e => addonWebglWindow.setTextureAtlas(e));
|
||||
addons.webgl.instance!.onAddTextureAtlasCanvas(e => addonWebglWindow.appendTextureAtlas(e));
|
||||
addons.webgl.instance!.onRemoveTextureAtlasCanvas(e => addonWebglWindow.removeTextureAtlas(e));
|
||||
|
||||
paddingElement.value = '0';
|
||||
addDomListener(paddingElement, 'change', setPadding);
|
||||
addDomListener(actionElements.findNext, 'keydown', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
addons.search.instance.findNext(actionElements.findNext.value, getSearchOptions());
|
||||
addons.search.instance!.findNext(actionElements.findNext.value, getSearchOptions());
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
addDomListener(actionElements.findNext, 'input', (e) => {
|
||||
addons.search.instance.findNext(actionElements.findNext.value, getSearchOptions());
|
||||
addons.search.instance!.findNext(actionElements.findNext.value, getSearchOptions());
|
||||
});
|
||||
addDomListener(actionElements.findPrevious, 'keydown', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
addons.search.instance.findPrevious(actionElements.findPrevious.value, getSearchOptions());
|
||||
addons.search.instance!.findPrevious(actionElements.findPrevious.value, getSearchOptions());
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
addDomListener(actionElements.findPrevious, 'input', (e) => {
|
||||
addons.search.instance.findPrevious(actionElements.findPrevious.value, getSearchOptions());
|
||||
addons.search.instance!.findPrevious(actionElements.findPrevious.value, getSearchOptions());
|
||||
});
|
||||
addDomListener(actionElements.findNext, 'blur', (e) => {
|
||||
addons.search.instance.clearActiveDecoration();
|
||||
addons.search.instance!.clearActiveDecoration();
|
||||
});
|
||||
addDomListener(actionElements.findPrevious, 'blur', (e) => {
|
||||
addons.search.instance.clearActiveDecoration();
|
||||
addons.search.instance!.clearActiveDecoration();
|
||||
});
|
||||
}
|
||||
|
||||
function createTerminal(): Terminal {
|
||||
// Clean terminal
|
||||
while (terminalContainer.children.length) {
|
||||
terminalContainer.removeChild(terminalContainer.children[0]);
|
||||
while (terminalContainer!.children.length) {
|
||||
terminalContainer!.removeChild(terminalContainer!.children[0]);
|
||||
}
|
||||
|
||||
const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0;
|
||||
@@ -282,10 +291,7 @@ function createTerminal(): Terminal {
|
||||
buildNumber: 22621
|
||||
} : undefined,
|
||||
fontFamily: '"Fira Code", monospace, "Powerline Extra Symbols"',
|
||||
theme: { ...xtermjsTheme },
|
||||
vtExtensions: {
|
||||
win32InputMode: isWindows
|
||||
}
|
||||
theme: { ...xtermjsTheme }
|
||||
} as ITerminalOptions);
|
||||
|
||||
// Load addons
|
||||
@@ -293,7 +299,7 @@ function createTerminal(): Terminal {
|
||||
addons.search.instance = new SearchAddon();
|
||||
addons.serialize.instance = new SerializeAddon();
|
||||
addons.fit.instance = new FitAddon();
|
||||
addons.image.instance = new ImageAddon();
|
||||
addons.image.instance = new ImageAddon!();
|
||||
addons.progress.instance = new ProgressAddon();
|
||||
addons.unicodeGraphemes.instance = new UnicodeGraphemesAddon();
|
||||
addons.clipboard.instance = new ClipboardAddon();
|
||||
@@ -314,7 +320,7 @@ function createTerminal(): Terminal {
|
||||
typedTerm.loadAddon(addons.webFonts.instance);
|
||||
typedTerm.loadAddon(addons.clipboard.instance);
|
||||
|
||||
window.term = term; // Expose `term` to window for debugging purposes
|
||||
(window as any).term = term; // Expose `term` to window for debugging purposes
|
||||
term.onResize((size: { cols: number, rows: number }) => {
|
||||
if (!pid) {
|
||||
return;
|
||||
@@ -335,7 +341,7 @@ function createTerminal(): Terminal {
|
||||
if (addons.webgl.instance) {
|
||||
try {
|
||||
typedTerm.loadAddon(addons.webgl.instance);
|
||||
term.open(terminalContainer);
|
||||
term.open(terminalContainer!);
|
||||
} catch (e) {
|
||||
console.warn('error during loading webgl addon:', e);
|
||||
addons.webgl.instance.dispose();
|
||||
@@ -344,7 +350,7 @@ function createTerminal(): Terminal {
|
||||
}
|
||||
if (!typedTerm.element) {
|
||||
// webgl loading failed for some reason, attach with DOM renderer
|
||||
term.open(terminalContainer);
|
||||
term.open(terminalContainer!);
|
||||
}
|
||||
|
||||
term.focus();
|
||||
@@ -354,13 +360,13 @@ function createTerminal(): Terminal {
|
||||
if (optionsWindow.autoResize) {
|
||||
// In general this should be debounced to avoid excessive work on the main
|
||||
// thread by firing the expensive resize action repeatedly
|
||||
addons.fit.instance.fit();
|
||||
addons.fit.instance!.fit();
|
||||
}
|
||||
});
|
||||
resizeObserver.observe(terminalContainer);
|
||||
resizeObserver.observe(terminalContainer!);
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
terminalContainer.style.width = document.body.clientWidth + 'px';
|
||||
terminalContainer!.style.width = document.body.clientWidth + 'px';
|
||||
});
|
||||
|
||||
// fit is called within a setTimeout, cols and rows need this.
|
||||
@@ -376,7 +382,7 @@ function createTerminal(): Terminal {
|
||||
} else {
|
||||
const pixelWidth = Math.round(term.dimensions?.css?.canvas?.width ?? 0);
|
||||
const pixelHeight = Math.round(term.dimensions?.css?.canvas?.height ?? 0);
|
||||
const res = await fetch('/terminals?cols=' + term.cols + '&rows=' + term.rows + '&pixelWidth=' + pixelWidth + '&pixelHeight=' + pixelHeight, { method: 'POST' });
|
||||
const res = await fetch('/terminals?cols=' + term!.cols + '&rows=' + term!.rows + '&pixelWidth=' + pixelWidth + '&pixelHeight=' + pixelHeight, { method: 'POST' });
|
||||
const processId = await res.text();
|
||||
pid = processId;
|
||||
socketURL += processId;
|
||||
@@ -391,52 +397,52 @@ function createTerminal(): Terminal {
|
||||
}
|
||||
|
||||
function runRealTerminal(): void {
|
||||
addons.attach.instance = new AttachAddon(socket);
|
||||
term.loadAddon(addons.attach.instance);
|
||||
term._initialized = true;
|
||||
initAddons(term);
|
||||
addons.attach.instance = new AttachAddon(socket!);
|
||||
term!.loadAddon(addons.attach.instance);
|
||||
(term as any)._initialized = true;
|
||||
initAddons(term!);
|
||||
}
|
||||
|
||||
function runFakeTerminal(): void {
|
||||
if (term._initialized) {
|
||||
if ((term as any)._initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
term._initialized = true;
|
||||
initAddons(term);
|
||||
(term as any)._initialized = true;
|
||||
initAddons(term!);
|
||||
|
||||
term.prompt = () => {
|
||||
term.write('\r\n$ ');
|
||||
(term as any).prompt = () => {
|
||||
term!.write('\r\n$ ');
|
||||
};
|
||||
|
||||
term.writeln('Welcome to xterm.js');
|
||||
term.writeln('This is a local terminal emulation, without a real terminal in the back-end.');
|
||||
term.writeln('Type some keys and commands to play around.');
|
||||
term.writeln('');
|
||||
term.prompt();
|
||||
term!.writeln('Welcome to xterm.js');
|
||||
term!.writeln('This is a local terminal emulation, without a real terminal in the back-end.');
|
||||
term!.writeln('Type some keys and commands to play around.');
|
||||
term!.writeln('');
|
||||
(term as any).prompt();
|
||||
|
||||
term.onKey((e: { key: string, domEvent: KeyboardEvent }) => {
|
||||
term!.onKey((e: { key: string, domEvent: KeyboardEvent }) => {
|
||||
const ev = e.domEvent;
|
||||
const printable = !ev.altKey && !ev.ctrlKey && !ev.metaKey;
|
||||
|
||||
if (ev.keyCode === 13) {
|
||||
term.prompt();
|
||||
(term as any).prompt();
|
||||
} else if (ev.keyCode === 8) {
|
||||
// Do not delete the prompt
|
||||
if (term._core.buffer.x > 2) {
|
||||
term.write('\b \b');
|
||||
if ((term as any)._core.buffer.x > 2) {
|
||||
term!.write('\b \b');
|
||||
}
|
||||
} else if (printable) {
|
||||
term.write(e.key);
|
||||
term!.write(e.key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateTerminalContainerBackground(): void {
|
||||
if (term.options.allowTransparency) {
|
||||
terminalContainer.style.background = 'repeating-conic-gradient(#000000 0% 25%, #101010 0% 50%) 50% / 20px 20px';
|
||||
if (term!.options.allowTransparency) {
|
||||
terminalContainer!.style.background = 'repeating-conic-gradient(#000000 0% 25%, #101010 0% 50%) 50% / 20px 20px';
|
||||
} else {
|
||||
terminalContainer.style.background = term.options.theme?.background ?? '#000000';
|
||||
terminalContainer!.style.background = term!.options.theme?.background ?? '#000000';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -446,19 +452,19 @@ function initAddons(term: Terminal): void {
|
||||
function postInitWebgl(): void {
|
||||
controlBar.setTabVisible('addon-webgl', true);
|
||||
setTimeout(() => {
|
||||
addonWebglWindow.setTextureAtlas(addons.webgl.instance.textureAtlas);
|
||||
addons.webgl.instance.onChangeTextureAtlas(e => addonWebglWindow.setTextureAtlas(e));
|
||||
addons.webgl.instance.onAddTextureAtlasCanvas(e => addonWebglWindow.appendTextureAtlas(e));
|
||||
addonWebglWindow.setTextureAtlas(addons.webgl.instance!.textureAtlas!);
|
||||
addons.webgl.instance!.onChangeTextureAtlas(e => addonWebglWindow.setTextureAtlas(e));
|
||||
addons.webgl.instance!.onAddTextureAtlasCanvas(e => addonWebglWindow.appendTextureAtlas(e));
|
||||
}, 500);
|
||||
}
|
||||
function preDisposeWebgl(): void {
|
||||
controlBar.setTabVisible('addon-webgl', false);
|
||||
if (addons.webgl.instance.textureAtlas) {
|
||||
addons.webgl.instance.textureAtlas.remove();
|
||||
if (addons.webgl.instance!.textureAtlas) {
|
||||
addons.webgl.instance!.textureAtlas.remove();
|
||||
}
|
||||
}
|
||||
|
||||
Object.keys(addons).forEach((name: AddonType) => {
|
||||
(Object.keys(addons) as AddonType[]).forEach(name => {
|
||||
const addon = addons[name];
|
||||
const checkbox = document.createElement('input') as HTMLInputElement;
|
||||
checkbox.type = 'checkbox';
|
||||
@@ -473,12 +479,12 @@ function initAddons(term: Terminal): void {
|
||||
term.unicode.activeVersion = '15-graphemes';
|
||||
}
|
||||
if (name === 'search' && checkbox.checked) {
|
||||
addons[name].instance.onDidChangeResults(e => updateFindResults(e));
|
||||
addons[name].instance!.onDidChangeResults(e => updateFindResults(e));
|
||||
}
|
||||
addDomListener(checkbox, 'change', () => {
|
||||
if (name === 'image') {
|
||||
if (checkbox.checked) {
|
||||
const ctorOptionsJson = document.querySelector<HTMLTextAreaElement>('#image-options').value;
|
||||
const ctorOptionsJson = document.querySelector<HTMLTextAreaElement>('#image-options')!.value;
|
||||
addon.instance = ctorOptionsJson
|
||||
? new addons[name].ctor(JSON.parse(ctorOptionsJson))
|
||||
: new addons[name].ctor();
|
||||
@@ -504,9 +510,15 @@ function initAddons(term: Terminal): void {
|
||||
term.unicode.activeVersion = '15-graphemes';
|
||||
} else if (name === 'search') {
|
||||
controlBar.setTabVisible('addon-search', true);
|
||||
addons[name].instance.onDidChangeResults(e => updateFindResults(e));
|
||||
addons[name].instance!.onDidChangeResults(e => updateFindResults(e));
|
||||
} else if (name === 'serialize') {
|
||||
controlBar.setTabVisible('addon-serialize', true);
|
||||
} else if (name === 'ligatures') {
|
||||
controlBar.setTabVisible('addon-ligatures', true);
|
||||
} else if (name === 'progress') {
|
||||
controlBar.setTabVisible('addon-progress', true);
|
||||
} else if (name === 'webLinks') {
|
||||
controlBar.setTabVisible('addon-web-links', true);
|
||||
}
|
||||
}
|
||||
catch {
|
||||
@@ -523,6 +535,12 @@ function initAddons(term: Terminal): void {
|
||||
controlBar.setTabVisible('addon-search', false);
|
||||
} else if (name === 'serialize') {
|
||||
controlBar.setTabVisible('addon-serialize', false);
|
||||
} else if (name === 'ligatures') {
|
||||
controlBar.setTabVisible('addon-ligatures', false);
|
||||
} else if (name === 'progress') {
|
||||
controlBar.setTabVisible('addon-progress', false);
|
||||
} else if (name === 'webLinks') {
|
||||
controlBar.setTabVisible('addon-web-links', false);
|
||||
}
|
||||
addon.instance!.dispose();
|
||||
addon.instance = undefined;
|
||||
@@ -594,17 +612,17 @@ function updateFindResults(e: { resultIndex: number, resultCount: number } | und
|
||||
|
||||
function addDomListener(element: HTMLElement, type: string, handler: (...args: any[]) => any): void {
|
||||
element.addEventListener(type, handler);
|
||||
term._core._register({ dispose: () => element.removeEventListener(type, handler) });
|
||||
(term as any)._core._register({ dispose: () => element.removeEventListener(type, handler) });
|
||||
}
|
||||
|
||||
function updateTerminalSize(): void {
|
||||
const width = optionsWindow.autoResize ? '100%'
|
||||
: (term.dimensions.css.canvas.width + term._core.viewport.scrollBarWidth).toString() + 'px';
|
||||
: ((term as any).dimensions.css.canvas.width + (term as any)._core.viewport.scrollBarWidth).toString() + 'px';
|
||||
const height = optionsWindow.autoResize ? '100%'
|
||||
: (term.dimensions.css.canvas.height).toString() + 'px';
|
||||
terminalContainer.style.width = width;
|
||||
terminalContainer.style.height = height;
|
||||
addons.fit.instance.fit();
|
||||
: ((term as any).dimensions.css.canvas.height).toString() + 'px';
|
||||
terminalContainer!.style.width = width;
|
||||
terminalContainer!.style.height = height;
|
||||
addons.fit.instance!.fit();
|
||||
}
|
||||
|
||||
(console as any).image = (source: ImageData | HTMLCanvasElement, scale: number = 1) => {
|
||||
|
||||
@@ -5,14 +5,15 @@
|
||||
|
||||
import { BaseWindow } from './baseWindow';
|
||||
import type { IControlWindow } from '../controlBar';
|
||||
import type { IImageAddonOptions } from '@xterm/addon-image';
|
||||
|
||||
export class AddonImageWindow extends BaseWindow implements IControlWindow {
|
||||
public readonly id = 'addon-image';
|
||||
public readonly label = 'image';
|
||||
|
||||
private _imageStorageLimitInput: HTMLInputElement;
|
||||
private _imageShowPlaceholderCheckbox: HTMLInputElement;
|
||||
private _imageOptionsTextarea: HTMLTextAreaElement;
|
||||
private _imageStorageLimitInput!: HTMLInputElement;
|
||||
private _imageShowPlaceholderCheckbox!: HTMLInputElement;
|
||||
private _imageOptionsTextarea!: HTMLTextAreaElement;
|
||||
|
||||
public build(container: HTMLElement): void {
|
||||
// Storage limit
|
||||
@@ -46,6 +47,20 @@ export class AddonImageWindow extends BaseWindow implements IControlWindow {
|
||||
this._imageOptionsTextarea.rows = 12;
|
||||
optionsLabel.appendChild(this._imageOptionsTextarea);
|
||||
container.appendChild(optionsLabel);
|
||||
|
||||
container.appendChild(document.createElement('br'));
|
||||
container.appendChild(document.createElement('br'));
|
||||
|
||||
const dl = document.createElement('dl');
|
||||
const dt = document.createElement('dt');
|
||||
dt.textContent = 'Image Test';
|
||||
dl.appendChild(dt);
|
||||
this._addDdWithButton(dl, 'image-demo1', 'snake (sixel)');
|
||||
this._addDdWithButton(dl, 'image-demo2', 'oranges (sixel)');
|
||||
this._addDdWithButton(dl, 'image-demo3', 'palette (iip)');
|
||||
container.appendChild(dl);
|
||||
|
||||
this._initImageAddonExposed();
|
||||
}
|
||||
|
||||
public get imageStorageLimitInput(): HTMLInputElement {
|
||||
@@ -59,4 +74,93 @@ export class AddonImageWindow extends BaseWindow implements IControlWindow {
|
||||
public get imageOptionsTextarea(): HTMLTextAreaElement {
|
||||
return this._imageOptionsTextarea;
|
||||
}
|
||||
|
||||
private _addDdWithButton(dl: HTMLElement, id: string, label: string): void {
|
||||
const dd = document.createElement('dd');
|
||||
const button = document.createElement('button');
|
||||
button.id = id;
|
||||
button.textContent = label;
|
||||
dd.appendChild(button);
|
||||
dl.appendChild(dd);
|
||||
}
|
||||
|
||||
private _initImageAddonExposed(): void {
|
||||
const imageAddon = this._addons.image.instance!;
|
||||
const defaultOptions: IImageAddonOptions = (imageAddon as any)._defaultOpts;
|
||||
const limitStorageElement = document.querySelector<HTMLInputElement>('#image-storagelimit')!;
|
||||
limitStorageElement.valueAsNumber = imageAddon.storageLimit;
|
||||
this._addDomListener(limitStorageElement, 'change', () => {
|
||||
try {
|
||||
imageAddon.storageLimit = limitStorageElement.valueAsNumber;
|
||||
limitStorageElement.valueAsNumber = imageAddon.storageLimit;
|
||||
console.log('changed storageLimit to', imageAddon.storageLimit);
|
||||
} catch (e) {
|
||||
limitStorageElement.valueAsNumber = imageAddon.storageLimit;
|
||||
console.log('storageLimit at', imageAddon.storageLimit);
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
const showPlaceholderElement = document.querySelector<HTMLInputElement>('#image-showplaceholder')!;
|
||||
showPlaceholderElement.checked = imageAddon.showPlaceholder;
|
||||
this._addDomListener(showPlaceholderElement, 'change', () => {
|
||||
imageAddon.showPlaceholder = showPlaceholderElement.checked;
|
||||
});
|
||||
const ctorOptionsElement = document.querySelector<HTMLTextAreaElement>('#image-options')!;
|
||||
ctorOptionsElement.value = JSON.stringify(defaultOptions, null, 2);
|
||||
|
||||
const sixelDemo = (url: string) => () => fetch(url)
|
||||
.then(resp => resp.arrayBuffer())
|
||||
.then(buffer => {
|
||||
this._terminal.write('\r\n');
|
||||
this._terminal.write(new Uint8Array(buffer));
|
||||
});
|
||||
|
||||
const iipDemo = (url: string) => () => fetch(url)
|
||||
.then(resp => resp.arrayBuffer())
|
||||
.then(buffer => {
|
||||
const data = new Uint8Array(buffer);
|
||||
let sdata = '';
|
||||
for (let i = 0; i < data.length; ++i) sdata += String.fromCharCode(data[i]);
|
||||
this._terminal.write('\r\n');
|
||||
this._terminal.write(`\x1b]1337;File=inline=1;size=${data.length}:${btoa(sdata)}\x1b\\`);
|
||||
});
|
||||
|
||||
document.getElementById('image-demo1')!.addEventListener('click',
|
||||
sixelDemo('https://raw.githubusercontent.com/saitoha/libsixel/master/images/snake.six'));
|
||||
document.getElementById('image-demo2')!.addEventListener('click',
|
||||
sixelDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/testfiles/test2.sixel'));
|
||||
document.getElementById('image-demo3')!.addEventListener('click',
|
||||
iipDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/palette.png'));
|
||||
|
||||
// demo for image retrieval API
|
||||
this._terminal.element!.addEventListener('click', (ev: MouseEvent) => {
|
||||
if (!ev.ctrlKey || !imageAddon) return;
|
||||
|
||||
// TODO...
|
||||
// if (ev.altKey) {
|
||||
// const sel = term.getSelectionPosition();
|
||||
// if (sel) {
|
||||
// addons.image.instance
|
||||
// .extractCanvasAtBufferRange(term.getSelectionPosition())
|
||||
// ?.toBlob(data => window.open(URL.createObjectURL(data), '_blank'));
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
const pos = (this._terminal as any)._core._mouseService!.getCoords(ev, (this._terminal as any)._core.screenElement!, this._terminal.cols, this._terminal.rows);
|
||||
const x = pos[0] - 1;
|
||||
const y = pos[1] - 1;
|
||||
const canvas = ev.shiftKey
|
||||
// ctrl+shift+click: get single tile
|
||||
? imageAddon.extractTileAtBufferCell(x, this._terminal.buffer.active.viewportY + y)
|
||||
// ctrl+click: get original image
|
||||
: imageAddon.getImageAtBufferCell(x, this._terminal.buffer.active.viewportY + y);
|
||||
canvas?.toBlob(data => data && window.open(URL.createObjectURL(data), '_blank'));
|
||||
});
|
||||
}
|
||||
|
||||
private _addDomListener(element: HTMLElement, type: string, handler: (...args: any[]) => any): void {
|
||||
element.addEventListener(type, handler);
|
||||
(this._terminal as any)._core._register({ dispose: () => element.removeEventListener(type, handler) });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) 2026 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { BaseWindow } from './baseWindow';
|
||||
import type { IControlWindow } from '../controlBar';
|
||||
|
||||
export class AddonLigaturesWindow extends BaseWindow implements IControlWindow {
|
||||
public readonly id = 'addon-ligatures';
|
||||
public readonly label = 'ligatures';
|
||||
|
||||
public build(container: HTMLElement): void {
|
||||
const dl = document.createElement('dl');
|
||||
const dt = document.createElement('dt');
|
||||
dt.textContent = 'Ligatures Addon';
|
||||
dl.appendChild(dt);
|
||||
|
||||
const dd = document.createElement('dd');
|
||||
const button = document.createElement('button');
|
||||
button.id = 'ligatures-test';
|
||||
button.textContent = 'Common ligatures';
|
||||
button.title = 'Write common ligatures sequences';
|
||||
button.addEventListener('click', () => this._ligaturesTest());
|
||||
dd.appendChild(button);
|
||||
dl.appendChild(dd);
|
||||
|
||||
container.appendChild(dl);
|
||||
}
|
||||
|
||||
private _ligaturesTest(): void {
|
||||
this._terminal.write([
|
||||
'',
|
||||
'-<< -< -<- <-- <--- <<- <- -> ->> --> ---> ->- >- >>-',
|
||||
'=<< =< =<= <== <=== <<= <= => =>> ==> ===> =>= >= >>=',
|
||||
'<-> <--> <---> <----> <=> <==> <===> <====> :: ::: __',
|
||||
'<~~ </ </> /> ~~> == != /= ~= <> === !== !=== =/= =!=',
|
||||
'<: := *= *+ <* <*> *> <| <|> |> <. <.> .> +* =* =: :>',
|
||||
'(* *) /* */ [| |] {| |} ++ +++ \/ /\ |- -| <!-- <!---',
|
||||
'==== ===== ====== ======= ======== =========',
|
||||
'---- ----- ------ ------- -------- ---------'
|
||||
].join('\r\n'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* Copyright (c) 2026 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { BaseWindow } from './baseWindow';
|
||||
import type { IControlWindow } from '../controlBar';
|
||||
import type { IProgressState } from '@xterm/addon-progress';
|
||||
|
||||
export class AddonProgressWindow extends BaseWindow implements IControlWindow {
|
||||
public readonly id = 'addon-progress';
|
||||
public readonly label = 'progress';
|
||||
|
||||
public build(container: HTMLElement): void {
|
||||
const dl = document.createElement('dl');
|
||||
const dt = document.createElement('dt');
|
||||
dt.textContent = 'Progress Addon';
|
||||
dl.appendChild(dt);
|
||||
|
||||
this._addDdWithButton(dl, 'progress-run', 'full set run', '');
|
||||
this._addDdWithButton(dl, 'progress-0', 'state 0: remove', '');
|
||||
this._addDdWithButton(dl, 'progress-1', 'state 1: set 20%', '');
|
||||
this._addDdWithButton(dl, 'progress-2', 'state 2: error', '');
|
||||
this._addDdWithButton(dl, 'progress-3', 'state 3: indeterminate', '');
|
||||
this._addDdWithButton(dl, 'progress-4', 'state 4: pause', '');
|
||||
|
||||
const progressDd = document.createElement('dd');
|
||||
const progressDiv = document.createElement('div');
|
||||
progressDiv.id = 'progress-progress';
|
||||
const progressPercent = document.createElement('div');
|
||||
progressPercent.id = 'progress-percent';
|
||||
const progressIndeterminate = document.createElement('div');
|
||||
progressIndeterminate.id = 'progress-indeterminate';
|
||||
progressDiv.appendChild(progressPercent);
|
||||
progressDiv.appendChild(progressIndeterminate);
|
||||
progressDd.appendChild(progressDiv);
|
||||
dl.appendChild(progressDd);
|
||||
|
||||
const stateDd = document.createElement('dd');
|
||||
const stateDiv = document.createElement('div');
|
||||
stateDiv.id = 'progress-state';
|
||||
stateDiv.textContent = 'State:';
|
||||
stateDd.appendChild(stateDiv);
|
||||
dl.appendChild(stateDd);
|
||||
|
||||
container.appendChild(dl);
|
||||
|
||||
this._initProgress();
|
||||
this._addProgressStyles(container);
|
||||
}
|
||||
|
||||
private _addDdWithButton(dl: HTMLElement, id: string, label: string, title: string): void {
|
||||
const dd = document.createElement('dd');
|
||||
const button = document.createElement('button');
|
||||
button.id = id;
|
||||
button.textContent = label;
|
||||
if (title) {
|
||||
button.title = title;
|
||||
}
|
||||
dd.appendChild(button);
|
||||
dl.appendChild(dd);
|
||||
}
|
||||
|
||||
private _initProgress(): void {
|
||||
const states = { 0: 'remove', 1: 'set', 2: 'error', 3: 'indeterminate', 4: 'pause' };
|
||||
const colors = { 0: '', 1: 'green', 2: 'red', 3: '', 4: 'yellow' };
|
||||
|
||||
function progressHandler({ state, value }: IProgressState): void {
|
||||
// Simulate windows taskbar hack by windows terminal:
|
||||
// Since the taskbar has no means to indicate error/pause state other than by coloring
|
||||
// the current progress, we move 0 to 10% and distribute higher values in the remaining 90 %
|
||||
// NOTE: This is most likely not what you want to do for other progress indicators,
|
||||
// that have a proper visual state for error/paused.
|
||||
value = Math.min(10 + value * 0.9, 100);
|
||||
document.getElementById('progress-percent')!.style.width = `${value}%`;
|
||||
document.getElementById('progress-percent')!.style.backgroundColor = colors[state];
|
||||
document.getElementById('progress-state')!.innerText = `State: ${states[state]}`;
|
||||
|
||||
document.getElementById('progress-percent')!.style.display = state === 3 ? 'none' : 'block';
|
||||
document.getElementById('progress-indeterminate')!.style.display = state === 3 ? 'block' : 'none';
|
||||
}
|
||||
|
||||
const progressAddon = this._addons.progress.instance!;
|
||||
progressAddon.onChange(progressHandler);
|
||||
|
||||
const initialProgress = progressAddon.progress;
|
||||
progressHandler(initialProgress);
|
||||
|
||||
document.getElementById('progress-run')!.addEventListener('click', async () => {
|
||||
this._terminal.write('\x1b]9;4;0\x1b\\');
|
||||
for (let i = 0; i <= 100; i += 5) {
|
||||
this._terminal.write(`\x1b]9;4;1;${i}\x1b\\`);
|
||||
await new Promise(res => setTimeout(res, 200));
|
||||
}
|
||||
});
|
||||
document.getElementById('progress-0')!.addEventListener('click', () => this._terminal.write('\x1b]9;4;0\x1b\\'));
|
||||
document.getElementById('progress-1')!.addEventListener('click', () => this._terminal.write('\x1b]9;4;1;20\x1b\\'));
|
||||
document.getElementById('progress-2')!.addEventListener('click', () => this._terminal.write('\x1b]9;4;2\x1b\\'));
|
||||
document.getElementById('progress-3')!.addEventListener('click', () => this._terminal.write('\x1b]9;4;3\x1b\\'));
|
||||
document.getElementById('progress-4')!.addEventListener('click', () => this._terminal.write('\x1b]9;4;4\x1b\\'));
|
||||
}
|
||||
|
||||
private _addProgressStyles(container: HTMLElement): void {
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
#progress-progress {
|
||||
border: 1px solid black;
|
||||
height: 10px;
|
||||
}
|
||||
#progress-percent {
|
||||
height: 100%;
|
||||
}
|
||||
#progress-indeterminate {
|
||||
display: none;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
#progress-indeterminate:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0px;
|
||||
width: 50px;
|
||||
height: 10px;
|
||||
background: blue;
|
||||
animation: ballbns 1s ease-in-out infinite alternate;
|
||||
}
|
||||
@keyframes ballbns {
|
||||
0% { left: 0; transform: translateX(0%); }
|
||||
100% { left: 100%; transform: translateX(-100%); }
|
||||
}
|
||||
`;
|
||||
container.appendChild(style);
|
||||
}
|
||||
}
|
||||
@@ -10,13 +10,13 @@ export class AddonSearchWindow extends BaseWindow implements IControlWindow {
|
||||
public readonly id = 'addon-search';
|
||||
public readonly label = 'search';
|
||||
|
||||
private _findNextInput: HTMLInputElement;
|
||||
private _findPreviousInput: HTMLInputElement;
|
||||
private _findResultsSpan: HTMLElement;
|
||||
private _regexCheckbox: HTMLInputElement;
|
||||
private _caseSensitiveCheckbox: HTMLInputElement;
|
||||
private _wholeWordCheckbox: HTMLInputElement;
|
||||
private _highlightAllMatchesCheckbox: HTMLInputElement;
|
||||
private _findNextInput!: HTMLInputElement;
|
||||
private _findPreviousInput!: HTMLInputElement;
|
||||
private _findResultsSpan!: HTMLElement;
|
||||
private _regexCheckbox!: HTMLInputElement;
|
||||
private _caseSensitiveCheckbox!: HTMLInputElement;
|
||||
private _wholeWordCheckbox!: HTMLInputElement;
|
||||
private _highlightAllMatchesCheckbox!: HTMLInputElement;
|
||||
|
||||
public build(container: HTMLElement): void {
|
||||
const wrapper = document.createElement('div');
|
||||
|
||||
@@ -10,10 +10,10 @@ export class AddonSerializeWindow extends BaseWindow implements IControlWindow {
|
||||
public readonly id = 'addon-serialize';
|
||||
public readonly label = 'serialize';
|
||||
|
||||
private _serializeOutputPre: HTMLPreElement;
|
||||
private _htmlSerializeOutputPre: HTMLPreElement;
|
||||
private _htmlSerializeOutputResult: HTMLElement;
|
||||
private _writeToTerminalCheckbox: HTMLInputElement;
|
||||
private _serializeOutputPre!: HTMLPreElement;
|
||||
private _htmlSerializeOutputPre!: HTMLPreElement;
|
||||
private _htmlSerializeOutputResult!: HTMLElement;
|
||||
private _writeToTerminalCheckbox!: HTMLInputElement;
|
||||
|
||||
public build(container: HTMLElement): void {
|
||||
const wrapper = document.createElement('div');
|
||||
@@ -64,7 +64,7 @@ export class AddonSerializeWindow extends BaseWindow implements IControlWindow {
|
||||
}
|
||||
|
||||
private _serializeButtonHandler(): void {
|
||||
const output = this._addons.serialize.instance.serialize();
|
||||
const output = this._addons.serialize.instance!.serialize();
|
||||
const outputString = JSON.stringify(output);
|
||||
|
||||
this._serializeOutputPre.innerText = outputString;
|
||||
@@ -75,7 +75,7 @@ export class AddonSerializeWindow extends BaseWindow implements IControlWindow {
|
||||
}
|
||||
|
||||
private _htmlSerializeButtonHandler(): void {
|
||||
const output = this._addons.serialize.instance.serializeAsHTML();
|
||||
const output = this._addons.serialize.instance!.serializeAsHTML();
|
||||
this._htmlSerializeOutputPre.innerText = output;
|
||||
|
||||
// Deprecated, but the most supported for now.
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) 2026 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { BaseWindow } from './baseWindow';
|
||||
import type { IControlWindow } from '../controlBar';
|
||||
|
||||
export class AddonWebLinksWindow extends BaseWindow implements IControlWindow {
|
||||
public readonly id = 'addon-web-links';
|
||||
public readonly label = 'web-links';
|
||||
|
||||
public build(container: HTMLElement): void {
|
||||
const dl = document.createElement('dl');
|
||||
const dt = document.createElement('dt');
|
||||
dt.textContent = 'Weblinks Addon';
|
||||
dl.appendChild(dt);
|
||||
|
||||
const dd = document.createElement('dd');
|
||||
const button = document.createElement('button');
|
||||
button.id = 'weblinks-test';
|
||||
button.textContent = 'Test URLs';
|
||||
button.title = 'Various url conditions from demo data, hover&click to test';
|
||||
button.addEventListener('click', () => this._testWeblinks());
|
||||
dd.appendChild(button);
|
||||
dl.appendChild(dd);
|
||||
|
||||
container.appendChild(dl);
|
||||
}
|
||||
|
||||
private _testWeblinks(): void {
|
||||
const linkExamples = `
|
||||
aaa http://example.com aaa http://example.com aaa
|
||||
\uFFE5\uFFE5\uFFE5 http://example.com aaa http://example.com aaa
|
||||
aaa http://example.com \uFFE5\uFFE5\uFFE5 http://example.com aaa
|
||||
\uFFE5\uFFE5\uFFE5 http://example.com \uFFE5\uFFE5\uFFE5 http://example.com aaa
|
||||
aaa https://ko.wikipedia.org/wiki/\uC704\uD0A4\uBC31\uACFC:\uB300\uBB38 aaa https://ko.wikipedia.org/wiki/\uC704\uD0A4\uBC31\uACFC:\uB300\uBB38 aaa
|
||||
\uFFE5\uFFE5\uFFE5 https://ko.wikipedia.org/wiki/\uC704\uD0A4\uBC31\uACFC:\uB300\uBB38 aaa https://ko.wikipedia.org/wiki/\uC704\uD0A4\uBC31\uACFC:\uB300\uBB38 \uFFE5\uFFE5\uFFE5
|
||||
aaa http://test:password@example.com/some_path aaa
|
||||
brackets enclosed:
|
||||
aaa [http://example.de] aaa
|
||||
aaa (http://example.de) aaa
|
||||
aaa <http://example.de> aaa
|
||||
aaa {http://example.de} aaa
|
||||
ipv6 https://[::1]/with/some?vars=and&a#hash aaa
|
||||
stop at final '.': This is a sentence with an url to http://example.com.
|
||||
stop at final '?': Is this the right url http://example.com/?
|
||||
stop at final '?': Maybe this one http://example.com/with?arguments=false?
|
||||
`;
|
||||
this._terminal.write(linkExamples.split('\n').join('\r\n'));
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ export class AddonsWindow extends BaseWindow implements IControlWindow {
|
||||
public readonly id = 'addons';
|
||||
public readonly label = 'Addons';
|
||||
|
||||
private _addonsContainer: HTMLElement;
|
||||
private _addonsContainer!: HTMLElement;
|
||||
|
||||
public build(container: HTMLElement): void {
|
||||
// Description
|
||||
|
||||
@@ -30,14 +30,14 @@ export class CellInspectorWindow extends BaseWindow implements IControlWindow {
|
||||
public readonly id = 'cell-inspector';
|
||||
public readonly label = 'Cell Inspector';
|
||||
|
||||
private _container: HTMLElement;
|
||||
private _positionEl: HTMLElement;
|
||||
private _charEl: HTMLElement;
|
||||
private _codeEl: HTMLElement;
|
||||
private _widthEl: HTMLElement;
|
||||
private _fgEl: HTMLElement;
|
||||
private _bgEl: HTMLElement;
|
||||
private _attrsEl: HTMLElement;
|
||||
private _container!: HTMLElement;
|
||||
private _positionEl!: HTMLElement;
|
||||
private _charEl!: HTMLElement;
|
||||
private _codeEl!: HTMLElement;
|
||||
private _widthEl!: HTMLElement;
|
||||
private _fgEl!: HTMLElement;
|
||||
private _bgEl!: HTMLElement;
|
||||
private _attrsEl!: HTMLElement;
|
||||
|
||||
public build(container: HTMLElement): void {
|
||||
this._container = container;
|
||||
|
||||
@@ -10,7 +10,7 @@ export class GpuWindow extends BaseWindow implements IControlWindow {
|
||||
public readonly id = 'gpu';
|
||||
public readonly label = 'WebGL';
|
||||
|
||||
private _textureAtlasContainer: HTMLElement;
|
||||
private _textureAtlasContainer!: HTMLElement;
|
||||
|
||||
public build(container: HTMLElement): void {
|
||||
const zoomCheckbox = document.createElement('input');
|
||||
|
||||
@@ -80,8 +80,8 @@ export class OptionsWindow extends BaseWindow implements IControlWindow {
|
||||
public readonly id = 'options';
|
||||
public readonly label = 'Options';
|
||||
|
||||
private _container: HTMLElement;
|
||||
private _optionsContainer: HTMLElement;
|
||||
private _container!: HTMLElement;
|
||||
private _optionsContainer!: HTMLElement;
|
||||
private _autoResize: boolean = true;
|
||||
|
||||
constructor(
|
||||
@@ -109,7 +109,6 @@ export class OptionsWindow extends BaseWindow implements IControlWindow {
|
||||
|
||||
public initOptions(addDomListener: (el: HTMLElement, type: string, handler: (...args: any[]) => any) => void): void {
|
||||
const blacklistedOptions = [
|
||||
'cancelEvents',
|
||||
'convertEol',
|
||||
'termName',
|
||||
'cols', 'rows',
|
||||
@@ -143,7 +142,7 @@ export class OptionsWindow extends BaseWindow implements IControlWindow {
|
||||
const booleanOptions: string[] = [];
|
||||
const numberOptions: string[] = [];
|
||||
options.filter(o => blacklistedOptions.indexOf(o) === -1).forEach(o => {
|
||||
switch (typeof this._terminal.options[o]) {
|
||||
switch (typeof (this._terminal.options as Record<string, unknown>)[o]) {
|
||||
case 'boolean':
|
||||
booleanOptions.push(o);
|
||||
break;
|
||||
@@ -160,25 +159,25 @@ export class OptionsWindow extends BaseWindow implements IControlWindow {
|
||||
let html = '';
|
||||
html += '<div class="option-group">';
|
||||
booleanOptions.forEach(o => {
|
||||
html += `<div class="option"><label><input id="opt-${o}" type="checkbox" ${this._terminal.options[o] ? 'checked' : ''}/> ${o}</label></div>`;
|
||||
html += `<div class="option"><label><input id="opt-${o}" type="checkbox" ${(this._terminal.options as Record<string, unknown>)[o] ? 'checked' : ''}/> ${o}</label></div>`;
|
||||
});
|
||||
nestedBooleanOptions.forEach(({ label, parent, prop }) => {
|
||||
const checked = this._terminal.options[parent]?.[prop] ?? false;
|
||||
const checked = (this._terminal.options as Record<string, Record<string, unknown> | undefined>)[parent]?.[prop] ?? false;
|
||||
html += `<div class="option"><label><input id="opt-${label.replace('.', '-')}" type="checkbox" ${checked ? 'checked' : ''}/> ${label}</label></div>`;
|
||||
});
|
||||
html += '</div><div class="option-group">';
|
||||
numberOptions.forEach(o => {
|
||||
html += `<div class="option"><label>${o} <input id="opt-${o}" type="number" value="${this._terminal.options[o] ?? ''}" step="${o === 'lineHeight' || o === 'scrollSensitivity' ? '0.1' : '1'}"/></label></div>`;
|
||||
html += `<div class="option"><label>${o} <input id="opt-${o}" type="number" value="${(this._terminal.options as Record<string, unknown>)[o] ?? ''}" step="${o === 'lineHeight' || o === 'scrollSensitivity' ? '0.1' : '1'}"/></label></div>`;
|
||||
});
|
||||
html += '</div><div class="option-group">';
|
||||
Object.keys(stringOptions).forEach(o => {
|
||||
if (o === 'colsRows') {
|
||||
html += `<div class="option"><label>size (<var>cols</var><code>x</code><var>rows</var> or <code>auto</code>) <input id="opt-${o}" type="text" value="auto"/></label></div>`;
|
||||
} else if (stringOptions[o]) {
|
||||
const selectedOption = o === 'theme' ? 'xtermjs' : this._terminal.options[o];
|
||||
const selectedOption = o === 'theme' ? 'xtermjs' : (this._terminal.options as Record<string, unknown>)[o];
|
||||
html += `<div class="option"><label>${o} <select id="opt-${o}">${stringOptions[o]!.map(v => `<option ${v === selectedOption ? 'selected' : ''}>${v}</option>`).join('')}</select></label></div>`;
|
||||
} else {
|
||||
html += `<div class="option"><label>${o} <input id="opt-${o}" type="text" value="${this._terminal.options[o]}"/></label></div>`;
|
||||
html += `<div class="option"><label>${o} <input id="opt-${o}" type="text" value="${(this._terminal.options as Record<string, unknown>)[o]}"/></label></div>`;
|
||||
}
|
||||
});
|
||||
html += '</div>';
|
||||
@@ -190,7 +189,7 @@ export class OptionsWindow extends BaseWindow implements IControlWindow {
|
||||
const input = document.getElementById(`opt-${o}`) as HTMLInputElement;
|
||||
addDomListener(input, 'change', () => {
|
||||
console.log('change', o, input.checked);
|
||||
this._terminal.options[o] = input.checked;
|
||||
(this._terminal.options as Record<string, unknown>)[o] = input.checked;
|
||||
if (o ==='allowTransparency') {
|
||||
this._terminal.options.theme = this._getTheme();
|
||||
this._handlers.updateTerminalContainerBackground();
|
||||
@@ -201,7 +200,7 @@ export class OptionsWindow extends BaseWindow implements IControlWindow {
|
||||
const input = document.getElementById(`opt-${label.replace('.', '-')}`) as HTMLInputElement;
|
||||
addDomListener(input, 'change', () => {
|
||||
console.log('change', label, input.checked);
|
||||
this._terminal.options[parent] = { ...this._terminal.options[parent], [prop]: input.checked };
|
||||
(this._terminal.options as Record<string, unknown>)[parent] = { ...(this._terminal.options as Record<string, Record<string, unknown> | undefined>)[parent], [prop]: input.checked };
|
||||
});
|
||||
});
|
||||
numberOptions.forEach(o => {
|
||||
@@ -216,7 +215,7 @@ export class OptionsWindow extends BaseWindow implements IControlWindow {
|
||||
this._terminal.options.scrollback = parseInt(input.value);
|
||||
setTimeout(() => this._handlers.updateTerminalSize(), 5);
|
||||
} else {
|
||||
this._terminal.options[o] = parseInt(input.value);
|
||||
(this._terminal.options as Record<string, unknown>)[o] = parseInt(input.value);
|
||||
}
|
||||
this._handlers.updateTerminalSize();
|
||||
});
|
||||
@@ -225,7 +224,7 @@ export class OptionsWindow extends BaseWindow implements IControlWindow {
|
||||
const input = document.getElementById(`opt-${o}`) as HTMLInputElement;
|
||||
addDomListener(input, 'change', () => {
|
||||
console.log('change', o, input.value);
|
||||
let value: any = input.value;
|
||||
let value: unknown = input.value;
|
||||
if (o === 'colsRows') {
|
||||
const m = input.value.match(/^([0-9]+)x([0-9]+)$/);
|
||||
if (m) {
|
||||
@@ -239,7 +238,7 @@ export class OptionsWindow extends BaseWindow implements IControlWindow {
|
||||
} else if (o === 'theme') {
|
||||
value = this._getTheme();
|
||||
}
|
||||
this._terminal.options[o] = value;
|
||||
(this._terminal.options as Record<string, unknown>)[o] = value;
|
||||
if (o === 'theme') {
|
||||
this._handlers.updateTerminalContainerBackground();
|
||||
}
|
||||
@@ -259,11 +258,11 @@ export class OptionsWindow extends BaseWindow implements IControlWindow {
|
||||
}
|
||||
|
||||
private _getTheme(): ITheme {
|
||||
const input = document.querySelector<HTMLInputElement>('#opt-theme');
|
||||
let theme: ITheme;
|
||||
const input = document.querySelector<HTMLInputElement>('#opt-theme')!;
|
||||
let theme: ITheme = {};
|
||||
switch (input.value) {
|
||||
case 'default':
|
||||
theme = undefined;
|
||||
theme = {};
|
||||
break;
|
||||
case 'xtermjs':
|
||||
theme = { ...xtermjsTheme };
|
||||
|
||||
@@ -10,7 +10,7 @@ export class StyleWindow extends BaseWindow implements IControlWindow {
|
||||
public readonly id = 'style';
|
||||
public readonly label = 'Style';
|
||||
|
||||
private _paddingElement: HTMLInputElement;
|
||||
private _paddingElement!: HTMLInputElement;
|
||||
|
||||
public build(container: HTMLElement): void {
|
||||
const wrapper = document.createElement('div');
|
||||
|
||||
@@ -8,8 +8,6 @@ import type { IControlWindow } from '../controlBar';
|
||||
import { BaseWindow } from './baseWindow';
|
||||
import type { IDisposable, Terminal } from '@xterm/xterm';
|
||||
import type { AddonCollection } from 'types';
|
||||
import type { IProgressState } from '@xterm/addon-progress';
|
||||
import type { IImageAddonOptions } from '@xterm/addon-image';
|
||||
|
||||
export class TestWindow extends BaseWindow implements IControlWindow {
|
||||
public readonly id = 'test';
|
||||
@@ -66,61 +64,13 @@ export class TestWindow extends BaseWindow implements IControlWindow {
|
||||
this._addDdWithButton(dl, 'add-overview-ruler', 'Add Overview Ruler', 'Add an overview ruler to the terminal', () => addOverviewRuler(this._terminal));
|
||||
this._addDdWithButton(dl, 'decoration-stress-test', 'Stress Test', 'Toggle between adding and removing a decoration to each line', () => decorationStressTest(this._terminal));
|
||||
|
||||
// Ligatures Addon section
|
||||
this._addDt(dl, 'Ligatures Addon');
|
||||
this._addDdWithButton(dl, 'ligatures-test', 'Common ligatures', 'Write common ligatures sequences', () => ligaturesTest(this._terminal));
|
||||
|
||||
// Weblinks Addon section
|
||||
this._addDt(dl, 'Weblinks Addon');
|
||||
this._addDdWithButton(dl, 'weblinks-test', 'Test URLs', 'Various url conditions from demo data, hover&click to test', () => testWeblinks(this._terminal));
|
||||
|
||||
// Image Test section
|
||||
this._addDt(dl, 'Image Test');
|
||||
this._addDdWithButton(dl, 'image-demo1', 'snake (sixel)', '');
|
||||
this._addDdWithButton(dl, 'image-demo2', 'oranges (sixel)', '');
|
||||
this._addDdWithButton(dl, 'image-demo3', 'palette (iip)', '');
|
||||
|
||||
// Events Test section
|
||||
this._addDt(dl, 'Events Test');
|
||||
this._addDdWithButton(dl, 'event-focus', 'focus', '', () => this._terminal.focus());
|
||||
this._addDdWithButton(dl, 'event-blur', 'blur', '', () => this._terminal.blur());
|
||||
|
||||
// Progress Addon section
|
||||
this._addDt(dl, 'Progress Addon');
|
||||
this._addDdWithButton(dl, 'progress-run', 'full set run', '');
|
||||
this._addDdWithButton(dl, 'progress-0', 'state 0: remove', '');
|
||||
this._addDdWithButton(dl, 'progress-1', 'state 1: set 20%', '');
|
||||
this._addDdWithButton(dl, 'progress-2', 'state 2: error', '');
|
||||
this._addDdWithButton(dl, 'progress-3', 'state 3: indeterminate', '');
|
||||
this._addDdWithButton(dl, 'progress-4', 'state 4: pause', '');
|
||||
|
||||
// Progress bar
|
||||
const progressDd = document.createElement('dd');
|
||||
const progressDiv = document.createElement('div');
|
||||
progressDiv.id = 'progress-progress';
|
||||
const progressPercent = document.createElement('div');
|
||||
progressPercent.id = 'progress-percent';
|
||||
const progressIndeterminate = document.createElement('div');
|
||||
progressIndeterminate.id = 'progress-indeterminate';
|
||||
progressDiv.appendChild(progressPercent);
|
||||
progressDiv.appendChild(progressIndeterminate);
|
||||
progressDd.appendChild(progressDiv);
|
||||
dl.appendChild(progressDd);
|
||||
|
||||
// Progress state
|
||||
const stateDd = document.createElement('dd');
|
||||
const stateDiv = document.createElement('div');
|
||||
stateDiv.id = 'progress-state';
|
||||
stateDiv.textContent = 'State:';
|
||||
stateDd.appendChild(stateDiv);
|
||||
dl.appendChild(stateDd);
|
||||
|
||||
wrapper.appendChild(dl);
|
||||
container.appendChild(wrapper);
|
||||
|
||||
initProgress(this._terminal, this._addons);
|
||||
this._addProgressStyles(container);
|
||||
initImageAddonExposed(this._terminal, this._addons);
|
||||
}
|
||||
|
||||
private _addDt(dl: HTMLElement, text: string): void {
|
||||
@@ -139,7 +89,9 @@ export class TestWindow extends BaseWindow implements IControlWindow {
|
||||
}
|
||||
dd.appendChild(button);
|
||||
dl.appendChild(dd);
|
||||
button.addEventListener('click', handler);
|
||||
if (handler) {
|
||||
button.addEventListener('click', handler);
|
||||
}
|
||||
}
|
||||
|
||||
private _addDdWithCheckbox(dl: HTMLElement, id: string, label: string, title: string, checked: boolean): void {
|
||||
@@ -159,38 +111,6 @@ export class TestWindow extends BaseWindow implements IControlWindow {
|
||||
dl.appendChild(dd);
|
||||
}
|
||||
|
||||
private _addProgressStyles(container: HTMLElement): void {
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
#progress-progress {
|
||||
border: 1px solid black;
|
||||
height: 10px;
|
||||
}
|
||||
#progress-percent {
|
||||
height: 100%;
|
||||
}
|
||||
#progress-indeterminate {
|
||||
display: none;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
#progress-indeterminate:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0px;
|
||||
width: 50px;
|
||||
height: 10px;
|
||||
background: blue;
|
||||
animation: ballbns 1s ease-in-out infinite alternate;
|
||||
}
|
||||
@keyframes ballbns {
|
||||
0% { left: 0; transform: translateX(0%); }
|
||||
100% { left: 100%; transform: translateX(-100%); }
|
||||
}
|
||||
`;
|
||||
container.appendChild(style);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -465,7 +385,7 @@ function customGlyphAlignmentHandler(term: Terminal): void {
|
||||
while (fillChars.length > 0) {
|
||||
const batch = fillChars.splice(0, 10);
|
||||
for (const fillChar of batch) {
|
||||
term.write(`${fillChar.codePointAt(0).toString(16).toUpperCase().padEnd(5, ' ')} `);
|
||||
term.write(`${fillChar.codePointAt(0)!.toString(16).toUpperCase().padEnd(5, ' ')} `);
|
||||
}
|
||||
term.write('\n\r');
|
||||
for (let i = 0; i < 3; i++) {
|
||||
@@ -751,42 +671,6 @@ Test BG-colored Erase (BCE):
|
||||
term.write(data.split('\n').join('\r\n'));
|
||||
}
|
||||
|
||||
function ligaturesTest(term: Terminal): void {
|
||||
term.write([
|
||||
'',
|
||||
'-<< -< -<- <-- <--- <<- <- -> ->> --> ---> ->- >- >>-',
|
||||
'=<< =< =<= <== <=== <<= <= => =>> ==> ===> =>= >= >>=',
|
||||
'<-> <--> <---> <----> <=> <==> <===> <====> :: ::: __',
|
||||
'<~~ </ </> /> ~~> == != /= ~= <> === !== !=== =/= =!=',
|
||||
'<: := *= *+ <* <*> *> <| <|> |> <. <.> .> +* =* =: :>',
|
||||
'(* *) /* */ [| |] {| |} ++ +++ \/ /\ |- -| <!-- <!---',
|
||||
'==== ===== ====== ======= ======== =========',
|
||||
'---- ----- ------ ------- -------- ---------'
|
||||
].join('\r\n'));
|
||||
}
|
||||
|
||||
function testWeblinks(term: Terminal): void {
|
||||
const linkExamples = `
|
||||
aaa http://example.com aaa http://example.com aaa
|
||||
¥¥¥ http://example.com aaa http://example.com aaa
|
||||
aaa http://example.com ¥¥¥ http://example.com aaa
|
||||
¥¥¥ http://example.com ¥¥¥ http://example.com aaa
|
||||
aaa https://ko.wikipedia.org/wiki/위키백과:대문 aaa https://ko.wikipedia.org/wiki/위키백과:대문 aaa
|
||||
¥¥¥ https://ko.wikipedia.org/wiki/위키백과:대문 aaa https://ko.wikipedia.org/wiki/위키백과:대문 ¥¥¥
|
||||
aaa http://test:password@example.com/some_path aaa
|
||||
brackets enclosed:
|
||||
aaa [http://example.de] aaa
|
||||
aaa (http://example.de) aaa
|
||||
aaa <http://example.de> aaa
|
||||
aaa {http://example.de} aaa
|
||||
ipv6 https://[::1]/with/some?vars=and&a#hash aaa
|
||||
stop at final '.': This is a sentence with an url to http://example.com.
|
||||
stop at final '?': Is this the right url http://example.com/?
|
||||
stop at final '?': Maybe this one http://example.com/with?arguments=false?
|
||||
`;
|
||||
term.write(linkExamples.split('\n').join('\r\n'));
|
||||
}
|
||||
|
||||
function loadTest(term: Terminal, addons: AddonCollection): void {
|
||||
const rendererName = addons.webgl.instance ? 'webgl' : 'dom';
|
||||
const testData = [];
|
||||
@@ -864,7 +748,7 @@ function addDecoration(term: Terminal, dim: number = 1): void {
|
||||
foregroundColor: '#00FE00',
|
||||
overviewRulerOptions: { color: '#ef292980', position: 'left' }
|
||||
});
|
||||
decoration.onRender((e: HTMLElement) => {
|
||||
decoration?.onRender((e: HTMLElement) => {
|
||||
e.style.right = '100%';
|
||||
e.style.backgroundColor = '#ef292980';
|
||||
});
|
||||
@@ -896,133 +780,17 @@ function decorationStressTest(term: Terminal): void {
|
||||
for (const x of [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]) {
|
||||
for (let y = 0; y < term.buffer.active.length; y++) {
|
||||
const cursorOffsetY = y - cursorY;
|
||||
decorationStressTestDecorations.push(term.registerDecoration({
|
||||
const decoration = term.registerDecoration({
|
||||
marker: term.registerMarker(cursorOffsetY),
|
||||
x,
|
||||
width: 4,
|
||||
backgroundColor: '#FF0000',
|
||||
overviewRulerOptions: { color: '#FF0000' }
|
||||
}));
|
||||
});
|
||||
if (decoration) {
|
||||
decorationStressTestDecorations.push(decoration);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function initProgress(term: Terminal, addons: AddonCollection): void {
|
||||
const STATES = { 0: 'remove', 1: 'set', 2: 'error', 3: 'indeterminate', 4: 'pause' };
|
||||
const COLORS = { 0: '', 1: 'green', 2: 'red', 3: '', 4: 'yellow' };
|
||||
|
||||
function progressHandler({ state, value }: IProgressState): void {
|
||||
// Simulate windows taskbar hack by windows terminal:
|
||||
// Since the taskbar has no means to indicate error/pause state other than by coloring
|
||||
// the current progress, we move 0 to 10% and distribute higher values in the remaining 90 %
|
||||
// NOTE: This is most likely not what you want to do for other progress indicators,
|
||||
// that have a proper visual state for error/paused.
|
||||
value = Math.min(10 + value * 0.9, 100);
|
||||
document.getElementById('progress-percent').style.width = `${value}%`;
|
||||
document.getElementById('progress-percent').style.backgroundColor = COLORS[state];
|
||||
document.getElementById('progress-state').innerText = `State: ${STATES[state]}`;
|
||||
|
||||
document.getElementById('progress-percent').style.display = state === 3 ? 'none' : 'block';
|
||||
document.getElementById('progress-indeterminate').style.display = state === 3 ? 'block' : 'none';
|
||||
}
|
||||
|
||||
const progressAddon = addons.progress.instance;
|
||||
progressAddon.onChange(progressHandler);
|
||||
|
||||
// apply initial state once to make it visible on page load
|
||||
const initialProgress = progressAddon.progress;
|
||||
progressHandler(initialProgress);
|
||||
|
||||
document.getElementById('progress-run').addEventListener('click', async () => {
|
||||
term.write('\x1b]9;4;0\x1b\\');
|
||||
for (let i = 0; i <= 100; i += 5) {
|
||||
term.write(`\x1b]9;4;1;${i}\x1b\\`);
|
||||
await new Promise(res => setTimeout(res, 200));
|
||||
}
|
||||
});
|
||||
document.getElementById('progress-0').addEventListener('click', () => term.write('\x1b]9;4;0\x1b\\'));
|
||||
document.getElementById('progress-1').addEventListener('click', () => term.write('\x1b]9;4;1;20\x1b\\'));
|
||||
document.getElementById('progress-2').addEventListener('click', () => term.write('\x1b]9;4;2\x1b\\'));
|
||||
document.getElementById('progress-3').addEventListener('click', () => term.write('\x1b]9;4;3\x1b\\'));
|
||||
document.getElementById('progress-4').addEventListener('click', () => term.write('\x1b]9;4;4\x1b\\'));
|
||||
}
|
||||
|
||||
function initImageAddonExposed(term: Terminal, addons: AddonCollection): void {
|
||||
const DEFAULT_OPTIONS: IImageAddonOptions = (addons.image.instance as any)._defaultOpts;
|
||||
const limitStorageElement = document.querySelector<HTMLInputElement>('#image-storagelimit');
|
||||
limitStorageElement.valueAsNumber = addons.image.instance.storageLimit;
|
||||
addDomListener(term, limitStorageElement, 'change', () => {
|
||||
try {
|
||||
addons.image.instance.storageLimit = limitStorageElement.valueAsNumber;
|
||||
limitStorageElement.valueAsNumber = addons.image.instance.storageLimit;
|
||||
console.log('changed storageLimit to', addons.image.instance.storageLimit);
|
||||
} catch (e) {
|
||||
limitStorageElement.valueAsNumber = addons.image.instance.storageLimit;
|
||||
console.log('storageLimit at', addons.image.instance.storageLimit);
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
const showPlaceholderElement = document.querySelector<HTMLInputElement>('#image-showplaceholder');
|
||||
showPlaceholderElement.checked = addons.image.instance.showPlaceholder;
|
||||
addDomListener(term, showPlaceholderElement, 'change', () => {
|
||||
addons.image.instance.showPlaceholder = showPlaceholderElement.checked;
|
||||
});
|
||||
const ctorOptionsElement = document.querySelector<HTMLTextAreaElement>('#image-options');
|
||||
ctorOptionsElement.value = JSON.stringify(DEFAULT_OPTIONS, null, 2);
|
||||
|
||||
const sixelDemo = (url: string) => () => fetch(url)
|
||||
.then(resp => resp.arrayBuffer())
|
||||
.then(buffer => {
|
||||
term.write('\r\n');
|
||||
term.write(new Uint8Array(buffer));
|
||||
});
|
||||
|
||||
const iipDemo = (url: string) => () => fetch(url)
|
||||
.then(resp => resp.arrayBuffer())
|
||||
.then(buffer => {
|
||||
const data = new Uint8Array(buffer);
|
||||
let sdata = '';
|
||||
for (let i = 0; i < data.length; ++i) sdata += String.fromCharCode(data[i]);
|
||||
term.write('\r\n');
|
||||
term.write(`\x1b]1337;File=inline=1;size=${data.length}:${btoa(sdata)}\x1b\\`);
|
||||
});
|
||||
|
||||
document.getElementById('image-demo1').addEventListener('click',
|
||||
sixelDemo('https://raw.githubusercontent.com/saitoha/libsixel/master/images/snake.six'));
|
||||
document.getElementById('image-demo2').addEventListener('click',
|
||||
sixelDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/testfiles/test2.sixel'));
|
||||
document.getElementById('image-demo3').addEventListener('click',
|
||||
iipDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/palette.png'));
|
||||
|
||||
// demo for image retrieval API
|
||||
term.element.addEventListener('click', (ev: MouseEvent) => {
|
||||
if (!ev.ctrlKey || !addons.image.instance) return;
|
||||
|
||||
// TODO...
|
||||
// if (ev.altKey) {
|
||||
// const sel = term.getSelectionPosition();
|
||||
// if (sel) {
|
||||
// addons.image.instance
|
||||
// .extractCanvasAtBufferRange(term.getSelectionPosition())
|
||||
// ?.toBlob(data => window.open(URL.createObjectURL(data), '_blank'));
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
const pos = (term as any)._core._mouseService!.getCoords(ev, (term as any)._core.screenElement!, term.cols, term.rows);
|
||||
const x = pos[0] - 1;
|
||||
const y = pos[1] - 1;
|
||||
const canvas = ev.shiftKey
|
||||
// ctrl+shift+click: get single tile
|
||||
? addons.image.instance.extractTileAtBufferCell(x, term.buffer.active.viewportY + y)
|
||||
// ctrl+click: get original image
|
||||
: addons.image.instance.getImageAtBufferCell(x, term.buffer.active.viewportY + y);
|
||||
canvas?.toBlob(data => window.open(URL.createObjectURL(data), '_blank'));
|
||||
});
|
||||
}
|
||||
|
||||
function addDomListener(term: Terminal, element: HTMLElement, type: string, handler: (...args: any[]) => any): void {
|
||||
element.addEventListener(type, handler);
|
||||
(term as any)._core._register({ dispose: () => element.removeEventListener(type, handler) });
|
||||
}
|
||||
|
||||
@@ -5,14 +5,12 @@
|
||||
|
||||
import { BaseWindow } from './baseWindow';
|
||||
import type { IControlWindow } from '../controlBar';
|
||||
import type { Terminal } from '@xterm/xterm';
|
||||
|
||||
export class VtWindow extends BaseWindow implements IControlWindow {
|
||||
public readonly id = 'vt';
|
||||
public readonly label = 'VT';
|
||||
|
||||
private _container: HTMLElement;
|
||||
private _term: Terminal | undefined;
|
||||
private _container!: HTMLElement;
|
||||
|
||||
public build(container: HTMLElement): void {
|
||||
this._container = container;
|
||||
@@ -65,7 +63,7 @@ export class VtWindow extends BaseWindow implements IControlWindow {
|
||||
const writeCsiSplit = writeCsi.split('|');
|
||||
const prefix = writeCsiSplit.length === 2 ? writeCsiSplit[0] : '';
|
||||
const suffix = writeCsiSplit[writeCsiSplit.length - 1];
|
||||
element.addEventListener('click', () => this._term?.write(this._csi(`${prefix}${inputs.map(e => e.value).join(';')}${suffix}`)));
|
||||
element.addEventListener('click', () => this._terminal.write(this._csi(`${prefix}${inputs.map(e => e.value).join(';')}${suffix}`)));
|
||||
|
||||
const desc = document.createElement('span');
|
||||
desc.textContent = description;
|
||||
|
||||
@@ -10,7 +10,7 @@ export class WebglWindow extends BaseWindow implements IControlWindow {
|
||||
public readonly id = 'addon-webgl';
|
||||
public readonly label = 'webgl';
|
||||
|
||||
private _textureAtlasContainer: HTMLElement;
|
||||
private _textureAtlasContainer!: HTMLElement;
|
||||
|
||||
public build(container: HTMLElement): void {
|
||||
const zoomCheckbox = document.createElement('input');
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"rootDir": ".",
|
||||
"sourceMap": true,
|
||||
"baseUrl": ".",
|
||||
"strict": true,
|
||||
"paths": {
|
||||
"@xterm/addon-attach": ["../../addons/addon-attach"],
|
||||
"@xterm/addon-clipboard": ["../../addons/addon-clipboard"],
|
||||
|
||||
Reference in New Issue
Block a user