Files
xterm.js/demo/client/client.ts
T
mgabor3141 9feb963cc9 fix: defer viewport DOM sync during synchronized output
Defer Viewport DOM scroll updates while DEC 2026 synchronized output is
active, then flush once rendering resumes so DOM and canvas update
atomically.

Also update demo sizing to avoid relying on removed internal viewport
scrollBarWidth.
2026-03-12 18:51:38 +01:00

663 lines
27 KiB
TypeScript

/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*
* This file is the entry point for browserify.
*/
// HACK: Playwright/WebKit on Windows does not support WebAssembly https://stackoverflow.com/q/62311688/1156119
import type { ImageAddon as ImageAddonType } from '@xterm/addon-image';
let ImageAddon: typeof ImageAddonType | undefined; // eslint-disable-line @typescript-eslint/naming-convention
if ('WebAssembly' in window) {
const imageAddon = require('@xterm/addon-image');
ImageAddon = imageAddon.ImageAddon;
}
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';
import { WebglWindow } from './components/window/webglWindow';
import { OptionsWindow } from './components/window/optionsWindow';
import { StyleWindow } from './components/window/styleWindow';
import { TestWindow } from './components/window/testWindow';
import { VtWindow } from './components/window/vtWindow';
import { ClipboardAddon } from '@xterm/addon-clipboard';
import { FitAddon } from '@xterm/addon-fit';
import { LigaturesAddon } from '@xterm/addon-ligatures';
import { ProgressAddon } from '@xterm/addon-progress';
import { SearchAddon, ISearchOptions } from '@xterm/addon-search';
import { SerializeAddon } from '@xterm/addon-serialize';
import { WebFontsAddon } from '@xterm/addon-web-fonts';
import { WebLinksAddon } from '@xterm/addon-web-links';
import { WebglAddon } from '@xterm/addon-webgl';
import { Unicode11Addon } from '@xterm/addon-unicode11';
import { UnicodeGraphemesAddon } from '@xterm/addon-unicode-graphemes';
import { AddonCollection, type AddonType, type IDemoAddon } from './types';
export interface IWindowWithTerminal extends Window {
term: typeof Terminal;
Terminal: typeof Terminal;
AttachAddon?: typeof AttachAddon;
ClipboardAddon?: typeof ClipboardAddon;
FitAddon?: typeof FitAddon;
ImageAddon?: typeof ImageAddon;
ProgressAddon?: typeof ProgressAddon;
SearchAddon?: typeof SearchAddon;
SerializeAddon?: typeof SerializeAddon;
WebLinksAddon?: typeof WebLinksAddon;
WebglAddon?: typeof WebglAddon;
Unicode11Addon?: typeof Unicode11Addon;
UnicodeGraphemesAddon?: typeof UnicodeGraphemesAddon;
LigaturesAddon?: typeof LigaturesAddon;
}
declare let window: IWindowWithTerminal;
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;
let addonWebglWindow: WebglWindow;
let optionsWindow: OptionsWindow;
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 },
progress: { name: 'progress', ctor: ProgressAddon, canChange: true },
search: { name: 'search', ctor: SearchAddon, canChange: true },
serialize: { name: 'serialize', ctor: SerializeAddon, canChange: true },
webFonts: { name: 'webFonts', ctor: WebFontsAddon, canChange: true },
webLinks: { name: 'webLinks', ctor: WebLinksAddon, canChange: true },
webgl: { name: 'webgl', ctor: WebglAddon, canChange: true },
unicode11: { name: 'unicode11', ctor: Unicode11Addon, canChange: true },
unicodeGraphemes: { name: 'unicodeGraphemes', ctor: UnicodeGraphemesAddon, canChange: true },
ligatures: { name: 'ligatures', ctor: LigaturesAddon, canChange: true }
};
let terminalContainer = document.getElementById('terminal-container');
let actionElements: {
findNext: HTMLInputElement;
findPrevious: HTMLInputElement;
findResults: HTMLElement;
};
let paddingElement: HTMLInputElement;
const xtermjsTheme = {
foreground: '#F8F8F8',
background: '#2D2E2C',
selectionBackground: '#5DA5D533',
selectionInactiveBackground: '#555555AA',
black: '#1E1E1D',
brightBlack: '#262625',
red: '#CE5C5C',
brightRed: '#FF7272',
green: '#5BCC5B',
brightGreen: '#72FF72',
yellow: '#CCCC5B',
brightYellow: '#FFFF72',
blue: '#5D5DD3',
brightBlue: '#7279FF',
magenta: '#BC5ED1',
brightMagenta: '#E572FF',
cyan: '#5DA5D5',
brightCyan: '#72F0FF',
white: '#F8F8F8',
brightWhite: '#FFFFFF'
} satisfies ITheme;
function setPadding(): void {
term!.element!.style.padding = parseInt(paddingElement.value, 10).toString() + 'px';
addons.fit.instance!.fit();
}
function getSearchOptions(): ISearchOptions {
return {
regex: (document.getElementById('regex') as HTMLInputElement).checked,
wholeWord: (document.getElementById('whole-word') as HTMLInputElement).checked,
caseSensitive: (document.getElementById('case-sensitive') as HTMLInputElement).checked,
decorations: (document.getElementById('highlight-all-matches') as HTMLInputElement).checked ? {
matchBackground: '#232422',
matchBorder: '#555753',
matchOverviewRuler: '#555753',
activeMatchBackground: '#ef2929',
activeMatchBorder: '#ffffff',
activeMatchColorOverviewRuler: '#ef2929'
} : undefined
};
}
const disposeRecreateButtonHandler: () => void = () => {
// If the terminal exists dispose of it, otherwise recreate it
if (term) {
term.dispose();
term = null;
(window as any).term = null;
socket = null;
addons.attach.instance = undefined;
addons.clipboard.instance = undefined;
addons.fit.instance = undefined;
addons.image.instance = undefined;
addons.search.instance = undefined;
addons.serialize.instance = undefined;
addons.unicode11.instance = undefined;
addons.unicodeGraphemes.instance = undefined;
addons.ligatures.instance = undefined;
addons.webLinks.instance = undefined;
addons.webgl.instance = undefined;
document.getElementById('dispose')!.innerHTML = 'Recreate Terminal';
} else {
createTerminal();
document.getElementById('dispose')!.innerHTML = 'Dispose terminal';
}
};
const createNewWindowButtonHandler: () => void = () => {
if (term) {
disposeRecreateButtonHandler();
}
const win = window.open()!;
terminalContainer = win.document.createElement('div');
terminalContainer.id = 'terminal-container';
win.document.body.appendChild(terminalContainer);
// Stylesheets are needed to get the terminal in the popout window to render
// correctly. We also need to wait for them to load before creating the
// terminal, otherwise we will not compute the correct metrics when rendering.
let pendingStylesheets = 0;
for (const linkNode of document.querySelectorAll('head link[rel=stylesheet]')) {
const newLink = document.createElement('link');
newLink.rel = 'stylesheet';
newLink.href = (linkNode as HTMLLinkElement).href;
win.document.head.appendChild(newLink);
pendingStylesheets++;
newLink.addEventListener('load', () => {
pendingStylesheets--;
if (pendingStylesheets === 0) {
createTerminal();
}
});
}
};
if (document.location.pathname === '/test') {
window.Terminal = Terminal;
window.AttachAddon = AttachAddon;
window.ClipboardAddon = ClipboardAddon;
window.FitAddon = FitAddon;
window.ImageAddon = ImageAddon;
window.ProgressAddon = ProgressAddon;
window.SearchAddon = SearchAddon;
window.SerializeAddon = SerializeAddon;
window.Unicode11Addon = Unicode11Addon;
window.UnicodeGraphemesAddon = UnicodeGraphemesAddon;
window.LigaturesAddon = LigaturesAddon;
window.WebLinksAddon = WebLinksAddon;
window.WebglAddon = WebglAddon;
} else {
const typedTerm = createTerminal();
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));
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 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,
findPrevious: addonSearchWindow.findPreviousInput,
findResults: addonSearchWindow.findResultsSpan
};
controlBar.activateDefaultTab();
// TODO: Most of below should be encapsulated within windows
paddingElement = styleWindow.paddingElement;
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-web-fonts', true);
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());
e.preventDefault();
}
});
addDomListener(actionElements.findNext, 'input', (e) => {
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());
e.preventDefault();
}
});
addDomListener(actionElements.findPrevious, 'input', (e) => {
addons.search.instance!.findPrevious(actionElements.findPrevious.value, getSearchOptions());
});
addDomListener(actionElements.findNext, 'blur', (e) => {
addons.search.instance!.clearActiveDecoration();
});
addDomListener(actionElements.findPrevious, 'blur', (e) => {
addons.search.instance!.clearActiveDecoration();
});
}
function createTerminal(): Terminal {
// Clean terminal
while (terminalContainer!.children.length) {
terminalContainer!.removeChild(terminalContainer!.children[0]);
}
const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0;
term = new Terminal({
scrollbar: { showScrollbar: true },
allowProposedApi: true,
windowsPty: isWindows ? {
// In a real scenario, these values should be verified on the backend
backend: 'conpty',
buildNumber: 22621
} : undefined,
fontFamily: '"Fira Code", monospace, "Powerline Extra Symbols"',
theme: { ...xtermjsTheme }
} as ITerminalOptions);
// Load addons
const typedTerm = term as Terminal;
addons.search.instance = new SearchAddon();
addons.serialize.instance = new SerializeAddon();
addons.fit.instance = new FitAddon();
addons.image.instance = new ImageAddon!();
addons.progress.instance = new ProgressAddon();
addons.unicodeGraphemes.instance = new UnicodeGraphemesAddon();
addons.clipboard.instance = new ClipboardAddon();
try { // try to start with webgl renderer (might throw on older safari/webkit)
addons.webgl.instance = new WebglAddon();
} catch (e) {
console.warn(e);
}
addons.webLinks.instance = new WebLinksAddon();
addons.webFonts.instance = new WebFontsAddon();
typedTerm.loadAddon(addons.fit.instance);
typedTerm.loadAddon(addons.image.instance);
typedTerm.loadAddon(addons.progress.instance);
typedTerm.loadAddon(addons.search.instance);
typedTerm.loadAddon(addons.serialize.instance);
typedTerm.loadAddon(addons.unicodeGraphemes.instance);
typedTerm.loadAddon(addons.webLinks.instance);
typedTerm.loadAddon(addons.webFonts.instance);
typedTerm.loadAddon(addons.clipboard.instance);
(window as any).term = term; // Expose `term` to window for debugging purposes
term.onResize((size: { cols: number, rows: number }) => {
if (!pid) {
return;
}
const cols = size.cols;
const rows = size.rows;
const pixelWidth = Math.round(term!.dimensions?.css?.canvas?.width ?? 0);
const pixelHeight = Math.round(term!.dimensions?.css?.canvas?.height ?? 0);
const url = '/terminals/' + pid + '/size?cols=' + cols + '&rows=' + rows + '&pixelWidth=' + pixelWidth + '&pixelHeight=' + pixelHeight;
fetch(url, { method: 'POST' });
});
protocol = (location.protocol === 'https:') ? 'wss://' : 'ws://';
socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/terminals/';
addons.fit.instance!.fit();
if (addons.webgl.instance) {
try {
typedTerm.loadAddon(addons.webgl.instance);
term.open(terminalContainer!);
} catch (e) {
console.warn('error during loading webgl addon:', e);
addons.webgl.instance.dispose();
addons.webgl.instance = undefined;
}
}
if (!typedTerm.element) {
// webgl loading failed for some reason, attach with DOM renderer
term.open(terminalContainer!);
}
term.focus();
updateTerminalContainerBackground();
const resizeObserver = new ResizeObserver(entries => {
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();
}
});
resizeObserver.observe(terminalContainer!);
window.addEventListener('resize', () => {
terminalContainer!.style.width = document.body.clientWidth + 'px';
});
// fit is called within a setTimeout, cols and rows need this.
setTimeout(async () => {
optionsWindow.initOptions(addDomListener);
// Set terminal size again to set the specific dimensions on the demo
updateTerminalSize();
const useRealTerminal = document.getElementById('use-real-terminal');
if (useRealTerminal instanceof HTMLInputElement && !useRealTerminal.checked) {
runFakeTerminal();
} 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 processId = await res.text();
pid = processId;
socketURL += processId;
socket = new WebSocket(socketURL);
socket.onopen = runRealTerminal;
socket.onclose = runFakeTerminal;
socket.onerror = runFakeTerminal;
}
}, 0);
return typedTerm;
}
function runRealTerminal(): void {
addons.attach.instance = new AttachAddon(socket!);
term!.loadAddon(addons.attach.instance);
(term as any)._initialized = true;
initAddons(term!);
}
function runFakeTerminal(): void {
if ((term as any)._initialized) {
return;
}
(term as any)._initialized = true;
initAddons(term!);
(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 as any).prompt();
term!.onKey((e: { key: string, domEvent: KeyboardEvent }) => {
const ev = e.domEvent;
const printable = !ev.altKey && !ev.ctrlKey && !ev.metaKey;
if (ev.keyCode === 13) {
(term as any).prompt();
} else if (ev.keyCode === 8) {
// Do not delete the prompt
if ((term as any)._core.buffer.x > 2) {
term!.write('\b \b');
}
} else if (printable) {
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';
} else {
terminalContainer!.style.background = term!.options.theme?.background ?? '#000000';
}
}
function initAddons(term: Terminal): void {
const fragment = document.createDocumentFragment();
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));
}, 500);
}
function preDisposeWebgl(): void {
controlBar.setTabVisible('addon-webgl', false);
if (addons.webgl.instance!.textureAtlas) {
addons.webgl.instance!.textureAtlas.remove();
}
}
(Object.keys(addons) as AddonType[]).forEach(name => {
const addon = addons[name];
const checkbox = document.createElement('input') as HTMLInputElement;
checkbox.type = 'checkbox';
checkbox.checked = !!addon.instance;
if (!addon.canChange) {
checkbox.disabled = true;
}
if (name === 'unicode11' && checkbox.checked) {
term.unicode.activeVersion = '11';
}
if (name === 'unicodeGraphemes' && checkbox.checked) {
term.unicode.activeVersion = '15-graphemes';
}
if (name === 'search' && checkbox.checked) {
addons[name].instance!.onDidChangeResults(e => updateFindResults(e));
}
addDomListener(checkbox, 'change', () => {
if (name === 'image') {
if (checkbox.checked) {
const ctorOptionsJson = document.querySelector<HTMLTextAreaElement>('#image-options')!.value;
addon.instance = ctorOptionsJson
? new addons[name].ctor(JSON.parse(ctorOptionsJson))
: new addons[name].ctor();
term.loadAddon(addon.instance);
controlBar.setTabVisible('addon-image', true);
} else {
controlBar.setTabVisible('addon-image', false);
addon.instance!.dispose();
addon.instance = undefined;
}
return;
}
if (checkbox.checked) {
// HACK: Manually remove addons that cannot be changes
addon.instance = new (addon as IDemoAddon<Exclude<AddonType, 'attach'>>).ctor();
try {
term.loadAddon(addon.instance);
if (name === 'webgl') {
postInitWebgl();
} else if (name === 'unicode11') {
term.unicode.activeVersion = '11';
} else if (name === 'unicodeGraphemes') {
term.unicode.activeVersion = '15-graphemes';
} else if (name === 'search') {
controlBar.setTabVisible('addon-search', true);
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 {
addon.instance = undefined;
checkbox.checked = false;
checkbox.disabled = true;
}
} else {
if (name === 'webgl') {
preDisposeWebgl();
} else if (name === 'unicode11' || name === 'unicodeGraphemes') {
term.unicode.activeVersion = '6';
} else if (name === 'search') {
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;
}
if (name === 'ligatures') {
// Recreate webgl when ligatures are toggled so texture atlas picks up any font feature
// settings changes
if (addons.webgl.instance) {
preDisposeWebgl();
addons.webgl.instance.dispose();
const customGlyphsCheckbox = document.getElementById('webgl-custom-glyphs') as HTMLInputElement;
addons.webgl.instance = new addons.webgl.ctor({ customGlyphs: customGlyphsCheckbox?.checked ?? true });
term.loadAddon(addons.webgl.instance);
postInitWebgl();
}
}
});
const label = document.createElement('label');
label.classList.add('addon');
if (!addon.canChange) {
label.title = 'This addon is needed for the demo to operate';
}
label.appendChild(checkbox);
label.appendChild(document.createTextNode(name));
const wrapper = document.createElement('div');
wrapper.classList.add('addon');
wrapper.appendChild(label);
// Add customGlyphs sub-checkbox for webgl addon
if (name === 'webgl') {
const customGlyphsCheckbox = document.createElement('input') as HTMLInputElement;
customGlyphsCheckbox.type = 'checkbox';
customGlyphsCheckbox.checked = true; // Default to enabled
customGlyphsCheckbox.id = 'webgl-custom-glyphs';
addDomListener(customGlyphsCheckbox, 'change', () => {
if (addons.webgl.instance) {
preDisposeWebgl();
addons.webgl.instance.dispose();
addons.webgl.instance = new addons.webgl.ctor({ customGlyphs: customGlyphsCheckbox.checked });
term.loadAddon(addons.webgl.instance);
postInitWebgl();
}
});
const customGlyphsLabel = document.createElement('label');
customGlyphsLabel.classList.add('addon');
customGlyphsLabel.style.display = 'block';
customGlyphsLabel.style.marginLeft = '20px';
customGlyphsLabel.appendChild(customGlyphsCheckbox);
customGlyphsLabel.appendChild(document.createTextNode('customGlyphs'));
wrapper.appendChild(customGlyphsLabel);
}
fragment.appendChild(wrapper);
});
const container = addonsWindow.addonsContainer;
container.innerHTML = '';
container.appendChild(fragment);
}
function updateFindResults(e: { resultIndex: number, resultCount: number } | undefined): void {
let content: string;
if (e === undefined) {
content = 'undefined';
} else {
content = `index: ${e.resultIndex}, count: ${e.resultCount}`;
}
actionElements.findResults.textContent = content;
}
function addDomListener(element: HTMLElement, type: string, handler: (...args: any[]) => any): void {
element.addEventListener(type, handler);
(term as any)._core._register({ dispose: () => element.removeEventListener(type, handler) });
}
function updateTerminalSize(): void {
const showScrollbar = term!.options.scrollbar?.showScrollbar ?? true;
const scrollBarWidth = (term!.options.scrollback === 0 || !showScrollbar)
? 0
: (term!.options.scrollbar?.width ?? 14);
const width = optionsWindow.autoResize ? '100%'
: ((term as any).dimensions.css.canvas.width + scrollBarWidth).toString() + 'px';
const height = optionsWindow.autoResize ? '100%'
: ((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) => {
function getBox(width: number, height: number): any {
return {
string: '+',
style: 'font-size: 1px; padding: ' + Math.floor(height / 2) + 'px ' + Math.floor(width / 2) + 'px; line-height: ' + height + 'px;'
};
}
if (source instanceof HTMLCanvasElement) {
source = source.getContext('2d')!.getImageData(0, 0, source.width, source.height)!;
}
const canvas = document.createElement('canvas');
canvas.width = source.width;
canvas.height = source.height;
const ctx = canvas.getContext('2d')!;
ctx.putImageData(source, 0, 0);
const sw = source.width * scale;
const sh = source.height * scale;
const dim = getBox(sw, sh);
console.log(
`Image: ${source.width} x ${source.height}\n%c${dim.string}`,
`${dim.style}background: url(${canvas.toDataURL()}); background-size: ${sw}px ${sh}px; background-repeat: no-repeat; color: transparent;`
);
console.groupCollapsed('Zoomed');
console.log(
`%c${dim.string}`,
`${getBox(sw * 10, sh * 10).style}background: url(${canvas.toDataURL()}); background-size: ${sw * 10}px ${sh * 10}px; background-repeat: no-repeat; color: transparent; image-rendering: pixelated;-ms-interpolation-mode: nearest-neighbor;`
);
console.groupEnd();
};