/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*
* This file is the entry point for browserify.
*/
///
/* eslint-disable no-restricted-syntax */
// HACK: Playwright/WebKit on Windows does not support WebAssembly https://stackoverflow.com/q/62311688/1156119
import type { ImageAddon as ImageAddonType, IImageAddonOptions } 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 IDisposable, type ITheme } from '@xterm/xterm';
import { AttachAddon } from '@xterm/addon-attach';
import { AddonsWindow } from './components/window/addonsWindow';
import { ControlBar } from './components/controlBar';
import { GpuWindow } from './components/window/gpuWindow';
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, IProgressState } from '@xterm/addon-progress';
import { SearchAddon, ISearchOptions } from '@xterm/addon-search';
import { SerializeAddon } from '@xterm/addon-serialize';
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 { writeUnicodeTable } from './unicodeTable';
export interface IWindowWithTerminal extends Window {
term: typeof Terminal;
Terminal: typeof Terminal;
AttachAddon?: typeof AttachAddon; // eslint-disable-line @typescript-eslint/naming-convention
ClipboardAddon?: typeof ClipboardAddon; // eslint-disable-line @typescript-eslint/naming-convention
FitAddon?: typeof FitAddon; // eslint-disable-line @typescript-eslint/naming-convention
ImageAddon?: typeof ImageAddon; // eslint-disable-line @typescript-eslint/naming-convention
ProgressAddon?: typeof ProgressAddon; // eslint-disable-line @typescript-eslint/naming-convention
SearchAddon?: typeof SearchAddon; // eslint-disable-line @typescript-eslint/naming-convention
SerializeAddon?: typeof SerializeAddon; // eslint-disable-line @typescript-eslint/naming-convention
WebLinksAddon?: typeof WebLinksAddon; // eslint-disable-line @typescript-eslint/naming-convention
WebglAddon?: typeof WebglAddon; // eslint-disable-line @typescript-eslint/naming-convention
Unicode11Addon?: typeof Unicode11Addon; // eslint-disable-line @typescript-eslint/naming-convention
UnicodeGraphemesAddon?: typeof UnicodeGraphemesAddon; // eslint-disable-line @typescript-eslint/naming-convention
LigaturesAddon?: typeof LigaturesAddon; // eslint-disable-line @typescript-eslint/naming-convention
}
declare let window: IWindowWithTerminal;
let term;
let protocol;
let socketURL;
let socket;
let pid;
let controlBar: ControlBar;
let addonsWindow: AddonsWindow;
let gpuWindow: GpuWindow;
let optionsWindow: OptionsWindow;
let styleWindow: StyleWindow;
let testWindow: TestWindow;
let vtWindow: VtWindow;
type AddonType = 'attach' | 'clipboard' | 'fit' | 'image' | 'progress' | 'search' | 'serialize' | 'unicode11' | 'unicodeGraphemes' | 'webLinks' | 'webgl' | 'ligatures';
interface IDemoAddon {
name: T;
canChange: boolean;
ctor: (
T extends 'attach' ? typeof AttachAddon :
T extends 'clipboard' ? typeof ClipboardAddon :
T extends 'fit' ? typeof FitAddon :
T extends 'image' ? typeof ImageAddonType :
T extends 'ligatures' ? typeof LigaturesAddon :
T extends 'progress' ? typeof ProgressAddon :
T extends 'search' ? typeof SearchAddon :
T extends 'serialize' ? typeof SerializeAddon :
T extends 'webLinks' ? typeof WebLinksAddon :
T extends 'unicode11' ? typeof Unicode11Addon :
T extends 'unicodeGraphemes' ? typeof UnicodeGraphemesAddon :
T extends 'webgl' ? typeof WebglAddon :
never
);
instance?: (
T extends 'attach' ? AttachAddon :
T extends 'clipboard' ? ClipboardAddon :
T extends 'fit' ? FitAddon :
T extends 'image' ? ImageAddonType :
T extends 'ligatures' ? LigaturesAddon :
T extends 'progress' ? ProgressAddon :
T extends 'search' ? SearchAddon :
T extends 'serialize' ? SerializeAddon :
T extends 'webLinks' ? WebLinksAddon :
T extends 'unicode11' ? Unicode11Addon :
T extends 'unicodeGraphemes' ? UnicodeGraphemesAddon :
T extends 'webgl' ? WebglAddon :
never
);
}
const addons: { [T in AddonType]: IDemoAddon } = {
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 },
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.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 {
controlBar = new ControlBar(document.getElementById('sidebar'), document.querySelector('.banner-tabs'), []);
addonsWindow = new AddonsWindow();
controlBar.registerWindow(addonsWindow);
actionElements = {
findNext: addonsWindow.findNextInput,
findPrevious: addonsWindow.findPreviousInput,
findResults: addonsWindow.findResultsSpan
};
gpuWindow = new GpuWindow();
controlBar.registerWindow(gpuWindow, { afterId: 'addons', hidden: true, smallTab: true });
optionsWindow = new OptionsWindow(updateTerminalSize, updateTerminalContainerBackground);
controlBar.registerWindow(optionsWindow);
styleWindow = new StyleWindow();
controlBar.registerWindow(styleWindow);
paddingElement = styleWindow.paddingElement;
testWindow = new TestWindow();
controlBar.registerWindow(testWindow);
vtWindow = new VtWindow();
controlBar.registerWindow(vtWindow);
controlBar.activateDefaultTab();
createTerminal();
document.getElementById('dispose').addEventListener('click', disposeRecreateButtonHandler);
document.getElementById('create-new-window').addEventListener('click', createNewWindowButtonHandler);
document.getElementById('serialize').addEventListener('click', serializeButtonHandler);
document.getElementById('htmlserialize').addEventListener('click', htmlSerializeButtonHandler);
document.getElementById('custom-glyph-alignment').addEventListener('click', customGlyphAlignmentHandler);
document.getElementById('custom-glyph-ranges').addEventListener('click', customGlyphRangesHandler);
document.getElementById('load-test').addEventListener('click', loadTest);
document.getElementById('load-test-long-lines').addEventListener('click', loadTestLongLines);
document.getElementById('print-cjk').addEventListener('click', addCjk);
document.getElementById('print-cjk-sgr').addEventListener('click', addCjkRandomSgr);
document.getElementById('powerline-symbol-test').addEventListener('click', powerlineSymbolTest);
document.getElementById('underline-test').addEventListener('click', underlineTest);
document.getElementById('ansi-colors').addEventListener('click', ansiColorsTest);
document.getElementById('osc-hyperlinks').addEventListener('click', addAnsiHyperlink);
document.getElementById('sgr-test').addEventListener('click', sgrTest);
document.getElementById('add-grapheme-clusters').addEventListener('click', addGraphemeClusters);
document.getElementById('add-decoration').addEventListener('click', addDecoration);
document.getElementById('add-overview-ruler').addEventListener('click', addOverviewRuler);
document.getElementById('decoration-stress-test').addEventListener('click', decorationStressTest);
document.getElementById('ligatures-test').addEventListener('click', ligaturesTest);
document.getElementById('weblinks-test').addEventListener('click', testWeblinks);
document.getElementById('bce').addEventListener('click', coloredErase);
initImageAddonExposed();
testEvents();
progressButtons();
}
function createTerminal(): void {
// Clean terminal
while (terminalContainer.children.length) {
terminalContainer.removeChild(terminalContainer.children[0]);
}
const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0;
term = new Terminal({
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();
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.clipboard.instance);
window.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 url = '/terminals/' + pid + '/size?cols=' + cols + '&rows=' + rows;
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);
controlBar.setTabVisible('gpu', true);
gpuWindow.setTextureAtlas(addons.webgl.instance.textureAtlas);
addons.webgl.instance.onChangeTextureAtlas(e => gpuWindow.setTextureAtlas(e));
addons.webgl.instance.onAddTextureAtlasCanvas(e => gpuWindow.appendTextureAtlas(e));
addons.webgl.instance.onRemoveTextureAtlasCanvas(e => gpuWindow.removeTextureAtlas(e));
} 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();
vtWindow?.initTerminal(term);
const resizeObserver = new ResizeObserver(entries => {
if (optionsWindow.autoResize) {
addons.fit.instance.fit();
}
});
resizeObserver.observe(terminalContainer);
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();
});
// fit is called within a setTimeout, cols and rows need this.
setTimeout(async () => {
optionsWindow.initOptions(term, addDomListener);
paddingElement.value = '0';
// 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 res = await fetch('/terminals?cols=' + term.cols + '&rows=' + term.rows, { 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);
}
function runRealTerminal(): void {
addons.attach.instance = new AttachAddon(socket);
term.loadAddon(addons.attach.instance);
term._initialized = true;
initAddons(term);
}
function runFakeTerminal(): void {
if (term._initialized) {
return;
}
term._initialized = true;
initAddons(term);
term.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.onKey((e: { key: string, domEvent: KeyboardEvent }) => {
const ev = e.domEvent;
const printable = !ev.altKey && !ev.ctrlKey && !ev.metaKey;
if (ev.keyCode === 13) {
term.prompt();
} else if (ev.keyCode === 8) {
// Do not delete the prompt
if (term._core.buffer.x > 2) {
term.write('\b \b');
}
} else if (printable) {
term.write(e.key);
}
});
}
function updateTerminalContainerBackground(): void {
const bg = term.options.theme?.background ?? '#000000';
terminalContainer.style.backgroundColor = bg;
}
function initAddons(term: Terminal): void {
const fragment = document.createDocumentFragment();
function postInitWebgl(): void {
controlBar.setTabVisible('gpu', true);
setTimeout(() => {
gpuWindow.setTextureAtlas(addons.webgl.instance.textureAtlas);
addons.webgl.instance.onChangeTextureAtlas(e => gpuWindow.setTextureAtlas(e));
addons.webgl.instance.onAddTextureAtlasCanvas(e => gpuWindow.appendTextureAtlas(e));
}, 500);
}
function preDisposeWebgl(): void {
controlBar.setTabVisible('gpu', false);
if (addons.webgl.instance.textureAtlas) {
addons.webgl.instance.textureAtlas.remove();
}
}
Object.keys(addons).forEach((name: AddonType) => {
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('#image-options').value;
addon.instance = ctorOptionsJson
? new addons[name].ctor(JSON.parse(ctorOptionsJson))
: new addons[name].ctor();
term.loadAddon(addon.instance);
} else {
addon.instance!.dispose();
addon.instance = undefined;
}
return;
}
if (checkbox.checked) {
// HACK: Manually remove addons that cannot be changes
addon.instance = new (addon as IDemoAddon>).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') {
addons[name].instance.onDidChangeResults(e => updateFindResults(e));
}
}
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';
}
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._core._register({ dispose: () => element.removeEventListener(type, handler) });
}
function updateTerminalSize(): void {
const width = optionsWindow.autoResize ? '100%'
: (term._core._renderService.dimensions.css.canvas.width + term._core.viewport.scrollBarWidth).toString() + 'px';
const height = optionsWindow.autoResize ? '100%'
: (term._core._renderService.dimensions.css.canvas.height).toString() + 'px';
terminalContainer.style.width = width;
terminalContainer.style.height = height;
addons.fit.instance.fit();
}
function serializeButtonHandler(): void {
const output = addons.serialize.instance.serialize();
const outputString = JSON.stringify(output);
document.getElementById('serialize-output').innerText = outputString;
if ((document.getElementById('write-to-terminal') as HTMLInputElement).checked) {
term.reset();
term.write(output);
}
}
function htmlSerializeButtonHandler(): void {
const output = addons.serialize.instance.serializeAsHTML();
document.getElementById('htmlserialize-output').innerText = output;
// Deprecated, but the most supported for now.
function listener(e: any): void {
e.clipboardData.setData('text/html', output);
e.preventDefault();
}
document.addEventListener('copy', listener);
document.execCommand('copy');
document.removeEventListener('copy', listener);
document.getElementById('htmlserialize-output-result').innerText = 'Copied to clipboard';
}
function customGlyphAlignmentHandler(): void {
term.write('\n\r');
term.write('\n\r');
term.write('Box styles: ┎┰┒┍┯┑╓╥╖╒╤╕ ┏┳┓┌┲┓┌┬┐┏┱┐\n\r');
term.write('┌─┬─┐ ┏━┳━┓ ╔═╦═╗ ┠╂┨┝┿┥╟╫╢╞╪╡ ┡╇┩├╊┫┢╈┪┣╉┤\n\r');
term.write('│ │ │ ┃ ┃ ┃ ║ ║ ║ ┖┸┚┕┷┙╙╨╜╘╧╛ └┴┘└┺┛┗┻┛┗┹┘\n\r');
term.write('├─┼─┤ ┣━╋━┫ ╠═╬═╣ ┏┱┐┌┲┓┌┬┐┌┬┐ ┏┳┓┌┮┓┌┬┐┏┭┐\n\r');
term.write('│ │ │ ┃ ┃ ┃ ║ ║ ║ ┡╃┤├╄┩├╆┪┢╅┤ ┞╀┦├┾┫┟╁┧┣┽┤\n\r');
term.write('└─┴─┘ ┗━┻━┛ ╚═╩═╝ └┴┘└┴┘└┺┛┗┹┘ └┴┘└┶┛┗┻┛┗┵┘\n\r');
term.write('\n\r');
term.write('Other:\n\r');
term.write('╭─╮ ╲ ╱ ╷╻╎╏┆┇┊┋ ╺╾╴ ╌╌╌ ┄┄┄ ┈┈┈\n\r');
term.write('│ │ ╳ ╽╿╎╏┆┇┊┋ ╶╼╸ ╍╍╍ ┅┅┅ ┉┉┉\n\r');
term.write('╰─╯ ╱ ╲ ╹╵╎╏┆┇┊┋\n\r');
term.write('\n\r');
term.write('Box drawing alignment tests:\x1b[31m █\n\r');
term.write(' ▉\n\r');
term.write(' ╔══╦══╗ ┌──┬──┐ ╭──┬──╮ ╭──┬──╮ ┏━━┳━━┓ ┎┒┏┑ ╷ ╻ ┏┯┓ ┌┰┐ ▊ ╱╲╱╲╳╳╳\n\r');
term.write(' ║┌─╨─┐║ │╔═╧═╗│ │╒═╪═╕│ │╓─╁─╖│ ┃┌─╂─┐┃ ┗╃╄┙ ╶┼╴╺╋╸┠┼┨ ┝╋┥ ▋ ╲╱╲╱╳╳╳\n\r');
term.write(' ║│╲ ╱│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╿ │┃ ┍╅╆┓ ╵ ╹ ┗┷┛ └┸┘ ▌ ╱╲╱╲╳╳╳\n\r');
term.write(' ╠╡ ╳ ╞╣ ├╢ ╟┤ ├┼─┼─┼┤ ├╫─╂─╫┤ ┣┿╾┼╼┿┫ ┕┛┖┚ ┌┄┄┐ ╎ ┏┅┅┓ ┋ ▍ ╲╱╲╱╳╳╳\n\r');
term.write(' ║│╱ ╲│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╽ │┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▎\n\r');
term.write(' ║└─╥─┘║ │╚═╤═╝│ │╘═╪═╛│ │╙─╀─╜│ ┃└─╂─┘┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▏\n\r');
term.write(' ╚══╩══╝ └──┴──┘ ╰──┴──╯ ╰──┴──╯ ┗━━┻━━┛ └╌╌┘ ╎ ┗╍╍┛ ┋ ▁▂▃▄▅▆▇█\n\r');
term.write('\x1b[0mBox drawing alignment tests:\x1b[32m █\n\r');
term.write(' ▉\n\r');
term.write(' ╔══╦══╗ ┌──┬──┐ ╭──┬──╮ ╭──┬──╮ ┏━━┳━━┓ ┎┒┏┑ ╷ ╻ ┏┯┓ ┌┰┐ ▊ ╱╲╱╲╳╳╳\n\r');
term.write(' ║┌─╨─┐║ │╔═╧═╗│ │╒═╪═╕│ │╓─╁─╖│ ┃┌─╂─┐┃ ┗╃╄┙ ╶┼╴╺╋╸┠┼┨ ┝╋┥ ▋ ╲╱╲╱╳╳╳\n\r');
term.write(' ║│╲ ╱│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╿ │┃ ┍╅╆┓ ╵ ╹ ┗┷┛ └┸┘ ▌ ╱╲╱╲╳╳╳\n\r');
term.write(' ╠╡ ╳ ╞╣ ├╢ ╟┤ ├┼─┼─┼┤ ├╫─╂─╫┤ ┣┿╾┼╼┿┫ ┕┛┖┚ ┌┄┄┐ ╎ ┏┅┅┓ ┋ ▍ ╲╱╲╱╳╳╳\n\r');
term.write(' ║│╱ ╲│║ │║ ║│ ││ │ ││ │║ ┃ ║│ ┃│ ╽ │┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▎\n\r');
term.write(' ║└─╥─┘║ │╚═╤═╝│ │╘═╪═╛│ │╙─╀─╜│ ┃└─╂─┘┃ ░░▒▒▓▓██ ┊ ┆ ╎ ╏ ┇ ┋ ▏\n\r');
term.write(' ╚══╩══╝ └──┴──┘ ╰──┴──╯ ╰──┴──╯ ┗━━┻━━┛ └╌╌┘ ╎ ┗╍╍┛ ┋ ▁▂▃▄▅▆▇█\n\r');
term.write('\x1b[0mSmooth mosaic terminal graphic characters alignment tests:\x1b[33m\n\r');
term.write(' 🭇🬼 🭈🬽 🭉🬾 🭊🬿 🭋🭀 🭁🭌 🭂🭍 🭃🭎 🭄🭏 🭅🭐 🭆🭑 🭨🭪 🭩 🭯 🭮🭬\n\r');
term.write(' 🭢🭗 🭣🭘 🭤🭙 🭥🭚 🭦🭛 🭒🭝 🭓🭞 🭔🭟 🭕🭠 🭖🭡 🭧🭜 🭫 🭭\n\r');
term.write(' 🭇🬼 🭉🬾 🭋🭀\n\r');
term.write(' 🭊🭁🭌🬿 🭈🭆🭂🭍🭑🬽 🭇🭄🭏🬼 🭃🭎 🭅🭐 🭨🭪\n\r');
term.write(' 🭥🭒🭝🭚 🭣🭧🭓🭞🭜🭘 🭢🭕🭠🭗 🭔🭟 🭖🭡 🭪🭨\n\r');
term.write(' 🭢🭗 🭤🭙 🭦🭛\n\r');
term.write('\x1b[0mCharacter cell diagonals (1FBA0-1FBAE) alignment tests:\x1b[34m\n\r');
term.write(' \u{1FBA3}\u{1FBA7}\u{1FBA2} \u{1FBA3}\u{1FBA8}\u{1FBA0} \u{1FBAD}\u{1FBA2} \u{1FBA3}\u{1FBAC} \u{1FBAE}\n\r');
term.write(' \u{1FBA3}\u{1FBA0} \u{1FBA1}\u{1FBA2} \u{1FBA1}\u{1FBA9}\u{1FBA2} \u{1FBA1}\u{1FBAA} \u{1FBAB}\u{1FBA0}\n\r');
term.write(' \u{1FBA4} \u{1FBA5}\n\r');
term.write(' \u{1FBA1}\u{1FBA2} \u{1FBA3}\u{1FBA0}\n\r');
term.write(' \u{1FBA1}\u{1FBA6}\u{1FBA0}\n\r');
term.write('\x1b[0mCharacter cell diagonals (1FBD0-1FBDF) alignment tests:\x1b[34m\n\r');
term.write(' \u{1FBD6}\u{1FBD4} \u{1FBD0}\u{1FBD1}\u{1FBD2}\u{1FBD3} \u{1FBDA} \u{1FBD9}\u{1FBDB} \u{1FBDE} \u{1FBDD}\u{1FBDF}\n\r');
term.write(' \u{1FBD7}\u{1FBD5} \u{1FBD2}\u{1FBD3}\u{1FBD0}\u{1FBD1} \u{1FBD8} \u{1FBDC}\n\r');
term.write(' \u{1FBD4}\u{1FBD6}\n\r');
term.write(' \u{1FBD5}\u{1FBD7}\n\r');
term.write('');
term.write('\x1b[0mComposite terminal graphics characters:\x1b[35m\n\r');
term.write('\u{1FBB2}\u{1FBB3} \u{1FBB9}\u{1FBBA} \u{1FBC1}\u{1FBC2}\u{1FBC3}\n\r');
term.write('\x1b[0mFill tests:\x1b[36m\n\r');
const fillChars = ['\u{2591}', '\u{2592}', '\u{2593}', '\u{1FB8C}', '\u{1FB8D}', '\u{1FB8E}', '\u{1FB8F}', '\u{1FB90}', '\u{1FB91}', '\u{1FB92}', '\u{1FB94}', '\u{1FB95}', '\u{1FB96}', '\u{1FB97}', '\u{1FB98}', '\u{1FB99}'];
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('\n\r');
for (let i = 0; i < 3; i++) {
for (const fillChar of batch) {
term.write(fillChar.repeat(5));
term.write(' ');
}
term.write('\n\r');
}
}
term.write('\x1b[0mPowerline alignment tests:\n\r');
const powerlineLeftChars = ['\u{E0B2}', '\u{E0B3}', '\u{E0B6}', '\u{E0B7}', '\u{E0BA}', '\u{E0BB}', '\u{E0BE}', '\u{E0BF}', '\u{E0C2}', '\u{E0C3}', '\u{E0C5}', '\u{E0C7}', '\u{E0CA}', '\u{E0D4}'];
const powerlineRightChars = ['\u{E0B0}', '\u{E0B1}', '\u{E0B4}', '\u{E0B5}', '\u{E0B8}', '\u{E0B9}', '\u{E0BC}', '\u{E0BD}', '\u{E0C0}', '\u{E0C1}', '\u{E0C4}', '\u{E0C6}', '\u{E0C8}', '\u{E0D2}', '\u{E0CC}', '\u{E0CD}', '\u{E0CE}', '\u{E0CF}', '\u{E0D0}', '\u{E0D1}'];
for (const char of powerlineLeftChars) {
term.write(`\x1b[31m${char}\x1b[0;41m \x1b[0m `);
}
term.write('\n\r');
for (const char of powerlineRightChars) {
term.write(`\x1b[41m \x1b[0;31m${char}\x1b[0m `);
}
term.write('\n\r');
term.write('\x1b[0mGit Branch Symbols alignment tests:\x1b[32m\n\r');
term.write(' \u{F5F7}\u{F5F7} \u{F5EE}\u{F5EF} \u{F5F6}\u{F5F7} \u{F5D6}\u{F5D0}\u{F5D7} \u{F5FC}\u{F5FC}\u{F5FE} \u{F5FD}\u{F609}\u{F5FF}\n\r');
term.write(' \u{F5DA}\u{F5DD} \u{F5F0}\u{F5F4}\u{F5F2} \u{F5FA}\u{F5FB} \u{F5D1} \u{F5D1} \u{F604}\u{F60C}\u{F606} \u{F605}\u{F60D}\u{F607}\n\r');
term.write(' \u{F5DB}\u{F5DE} \u{F5F1}\u{F5F5}\u{F5F3} \u{F5F8}\u{F5F9} \u{F5D8}\u{F5D0}\u{F5D9} \u{F600}\u{F60A}\u{F602} \u{F601}\u{F60B}\u{F603}\n\r');
term.write(' \u{F5DC}\u{F5DF} \u{F5F7}\u{F5F7}\u{F5F7}\u{F5F7}\u{F5F7}\u{F5F7}\u{F5F7}\n\r');
term.write('\u{F5F1}\u{F5E6}\u{F5E7}\u{F5F3} \u{F5D3}\u{F5D0}\u{F5E0}\u{F5E1}\u{F5E2}\u{F5E3}\u{F5E4}\u{F5E5}\u{F5E8}\u{F5E9}\u{F5EC}\u{F5ED}\u{F5D2}\n\r');
term.write('\u{F5F1}\u{F5EA}\u{F5EB}\u{F5F3} \u{F5F9}\u{F5F9}\u{F5F9} \u{F5F9}\u{F5F9}\u{F5F9}\u{F5F9}\n\r');
term.write(' \u{F5F9}\u{F5F9} \n\r');
term.write('\x1b[0m');
term.write('\n\r');
window.scrollTo(0, 0);
}
function customGlyphRangesHandler(): void {
// Box Drawing
// 2500-257F
// https://www.unicode.org/charts/PDF/U2500.pdf
writeUnicodeTable(term, 'Box Drawing', 0x2500, 0x257F, [
['Light and heavy solid lines', 0x2500, 0x2503],
['Light and heavy dashed lines', 0x2504, 0x250B],
['Light and heavy line box components', 0x250C, 0x254B],
['Light and heavy dashed lines', 0x254C, 0x254F],
['Double lines', 0x2550, 0x2551],
['Light and double line box components', 0x2552, 0x256C],
['Character cell arcs', 0x256D, 0x2570],
['Character cell diagonals', 0x2571, 0x2573],
['Light and heavy half lines', 0x2574, 0x257B],
['Mixed light and heavy lines', 0x257C, 0x257F],
]);
// Box Elements
// 2580-259F
// https://www.unicode.org/charts/PDF/U2580.pdf
writeUnicodeTable(term, 'Box Elements', 0x2580, 0x259F, [
['Block elements', 0x2580, 0x2590],
['Shade characters', 0x2591, 0x2593],
['Block elements', 0x2594, 0x2595],
['Terminal graphic characters', 0x2596, 0x259F],
]);
// Braille Patterns
// 2800-28FF
// https://www.unicode.org/charts/PDF/U2800.pdf
writeUnicodeTable(term, 'Braille patterns', 0x2800, 0x28FF, [
['Braille patterns', 0x2800, 0x28FF],
]);
// Powerline Symbols
// Range: E0A0–E0D4
// https://github.com/ryanoasis/nerd-fonts
writeUnicodeTable(term, 'Powerline Symbols', 0xE0A0, 0xE0D4, [
['Powerline symbols', 0xE0A0, 0xE0B3, [0xE0A4, 0xE0A5, 0xE0A6, 0xE0A7, 0xE0A8, 0xE0A9, 0xE0AA, 0xE0AB, 0xE0AC, 0xE0AD, 0xE0AE, 0xE0AF]],
['Powerline extra symbols', 0xE0B4, 0xE0D4, [0xE0C9, 0xE0CB, 0xE0D3]],
]);
// Git Branch Symbols
// F5D0-F60D
// https://github.com/xtermjs/xterm.js/issues/5477
writeUnicodeTable(term, 'Git Branch Symbols', 0xF5D0, 0xF5FB, [
['Straight lines', 0xF5D0, 0xF5D5],
['Curved lines', 0xF5D6, 0xF5D9],
['Branching lines', 0xF5DA, 0xF5ED],
['Nodes', 0xF5EE, 0xF5FB],
['Extended nodes', 0xF5FC, 0xF60D],
]);
// Symbols for Legacy Computing
// Range: 1FB00–1FBFF
// https://www.unicode.org/charts/PDF/U1FB00.pdf
writeUnicodeTable(term, 'Symbols for Legacy Computing', 0x1FB00, 0x1FBFF, [
['Block mosaic terminal graphic characters (Sextants)', 0x1FB00, 0x1FB3B],
['Smooth mosaic terminal graphic characters', 0x1FB3C, 0x1FB6F],
['Block elements', 0x1FB70, 0x1FB80],
['Window title bar', 0x1FB81, 0x1FB81],
['Block elements', 0x1FB82, 0x1FB8B],
['Rectangular shade characters', 0x1FB8C, 0x1FB94, [0x1FB93]],
['Fill characters', 0x1FB95, 0x1FB97],
['Diagonal fill characters', 0x1FB98, 0x1FB99],
['Smooth mosaic terminal graphic characters', 0x1FB9A, 0x1FB9B],
['Triangular shade characters', 0x1FB9C, 0x1FB9F],
['Character cell diagonals', 0x1FBA0, 0x1FBAE],
['Light solid line with stroke', 0x1FBAF, 0x1FBAF],
['Terminal graphic characters', 0x1FBB0, 0x1FBB3],
['Arrows', 0x1FBB4, 0x1FBB8],
['Terminal graphic characters', 0x1FBB9, 0x1FBBC],
['Negative terminal graphic characters', 0x1FBBD, 0x1FBBF],
['Terminal graphic characters', 0x1FBC0, 0x1FBCA],
['Terminal graphic characters', 0x1FBCB, 0x1FBCD],
['Block elements', 0x1FBCE, 0x1FBCF],
['Character cell diagonals', 0x1FBD0, 0x1FBDF],
['Geometrics shapes', 0x1FBE0, 0x1FBEF],
['Segmented digits', 0x1FBF0, 0x1FBF9],
['Terminal graphic character', 0x1FBFA, 0x1FBFA],
]);
}
function loadTest(): void {
const rendererName = addons.webgl.instance ? 'webgl' : 'dom';
const testData = [];
let byteCount = 0;
for (let i = 0; i < 50; i++) {
const count = 1 + Math.floor(Math.random() * 79);
byteCount += count + 2;
const data = new Uint8Array(count + 2);
data[0] = 0x0A; // \n
for (let i = 1; i < count + 1; i++) {
data[i] = 0x61 + Math.floor(Math.random() * (0x7A - 0x61));
}
// End each line with \r so the cursor remains constant, this is what ls/tree do and improves
// performance significantly due to the cursor DOM element not needing to change
data[data.length - 1] = 0x0D; // \r
testData.push(data);
}
const start = performance.now();
for (let i = 0; i < 1024; i++) {
for (const d of testData) {
term.write(d);
}
}
// Wait for all data to be parsed before evaluating time
term.write('', () => {
const time = Math.round(performance.now() - start);
const mbs = ((byteCount / 1024) * (1 / (time / 1000))).toFixed(2);
term.write(`\n\r\nWrote ${byteCount}kB in ${time}ms (${mbs}MB/s) using the (${rendererName} renderer)`);
// Send ^C to get a new prompt
term._core._onData.fire('\x03');
});
}
function loadTestLongLines(): void {
const rendererName = addons.webgl.instance ? 'webgl' : 'dom';
const testData = [];
let byteCount = 0;
for (let i = 0; i < 50; i++) {
const count = 1 + Math.floor(Math.random() * 500);
byteCount += count + 2;
const data = new Uint8Array(count + 2);
data[0] = 0x0A; // \n
for (let i = 1; i < count + 1; i++) {
data[i] = 0x61 + Math.floor(Math.random() * (0x7A - 0x61));
}
// End each line with \r so the cursor remains constant, this is what ls/tree do and improves
// performance significantly due to the cursor DOM element not needing to change
data[data.length - 1] = 0x0D; // \r
testData.push(data);
}
const start = performance.now();
for (let i = 0; i < 1024 * 50; i++) {
for (const d of testData) {
term.write(d);
}
}
// Wait for all data to be parsed before evaluating time
term.write('', () => {
const time = Math.round(performance.now() - start);
const mbs = ((byteCount / 1024) * (1 / (time / 1000))).toFixed(2);
term.write(`\n\r\nWrote ${byteCount}kB in ${time}ms (${mbs}MB/s) using the (${rendererName} renderer)`);
// Send ^C to get a new prompt
term._core._onData.fire('\x03');
});
}
function powerlineSymbolTest(): void {
function s(char: string): string {
return `${char} \x1b[7m${char}\x1b[0m `;
}
term.write('\n\n\r');
term.writeln('Standard powerline symbols:');
term.writeln(' 0 1 2 3 4 5 6 7 8 9 A B C D E F');
term.writeln(`0xA_ ${s('\ue0a0')}${s('\ue0a1')}${s('\ue0a2')}`);
term.writeln(`0xB_ ${s('\ue0b0')}${s('\ue0b1')}${s('\ue0b2')}${s('\ue0b3')}`);
term.writeln('');
term.writeln(
`\x1b[7m` +
` inverse \ue0b1 \x1b[0;40m\ue0b0` +
` 0 \ue0b1 \x1b[30;41m\ue0b0\x1b[39m` +
` 1 \ue0b1 \x1b[31;42m\ue0b0\x1b[39m` +
` 2 \ue0b1 \x1b[32;43m\ue0b0\x1b[39m` +
` 3 \ue0b1 \x1b[33;44m\ue0b0\x1b[39m` +
` 4 \ue0b1 \x1b[34;45m\ue0b0\x1b[39m` +
` 5 \ue0b1 \x1b[35;46m\ue0b0\x1b[39m` +
` 6 \ue0b1 \x1b[36;47m\ue0b0\x1b[30m` +
` 7 \ue0b1 \x1b[37;49m\ue0b0\x1b[0m`
);
term.writeln('');
term.writeln(
`\x1b[7m` +
` inverse \ue0b3 \x1b[0;7;40m\ue0b2\x1b[27m` +
` 0 \ue0b3 \x1b[7;30;41m\ue0b2\x1b[27;39m` +
` 1 \ue0b3 \x1b[7;31;42m\ue0b2\x1b[27;39m` +
` 2 \ue0b3 \x1b[7;32;43m\ue0b2\x1b[27;39m` +
` 3 \ue0b3 \x1b[7;33;44m\ue0b2\x1b[27;39m` +
` 4 \ue0b3 \x1b[7;34;45m\ue0b2\x1b[27;39m` +
` 5 \ue0b3 \x1b[7;35;46m\ue0b2\x1b[27;39m` +
` 6 \ue0b3 \x1b[7;36;47m\ue0b2\x1b[27;30m` +
` 7 \ue0b3 \x1b[7;37;49m\ue0b2\x1b[0m`
);
term.writeln('');
term.writeln(
`\x1b[7m` +
` inverse \ue0b5 \x1b[0;40m\ue0b4` +
` 0 \ue0b5 \x1b[30;41m\ue0b4\x1b[39m` +
` 1 \ue0b5 \x1b[31;42m\ue0b4\x1b[39m` +
` 2 \ue0b5 \x1b[32;43m\ue0b4\x1b[39m` +
` 3 \ue0b5 \x1b[33;44m\ue0b4\x1b[39m` +
` 4 \ue0b5 \x1b[34;45m\ue0b4\x1b[39m` +
` 5 \ue0b5 \x1b[35;46m\ue0b4\x1b[39m` +
` 6 \ue0b5 \x1b[36;47m\ue0b4\x1b[30m` +
` 7 \ue0b5 \x1b[37;49m\ue0b4\x1b[0m`
);
term.writeln('');
term.writeln(
`\x1b[7m` +
` inverse \ue0b7 \x1b[0;7;40m\ue0b6\x1b[27m` +
` 0 \ue0b7 \x1b[7;30;41m\ue0b6\x1b[27;39m` +
` 1 \ue0b7 \x1b[7;31;42m\ue0b6\x1b[27;39m` +
` 2 \ue0b7 \x1b[7;32;43m\ue0b6\x1b[27;39m` +
` 3 \ue0b7 \x1b[7;33;44m\ue0b6\x1b[27;39m` +
` 4 \ue0b7 \x1b[7;34;45m\ue0b6\x1b[27;39m` +
` 5 \ue0b7 \x1b[7;35;46m\ue0b6\x1b[27;39m` +
` 6 \ue0b7 \x1b[7;36;47m\ue0b6\x1b[27;30m` +
` 7 \ue0b7 \x1b[7;37;49m\ue0b6\x1b[0m`
);
term.writeln('');
term.writeln('Powerline extra symbols:');
term.writeln(' 0 1 2 3 4 5 6 7 8 9 A B C D E F');
term.writeln(`0xA_ ${s('\ue0a3')}`);
term.writeln(`0xB_ ${s('\ue0b4')}${s('\ue0b5')}${s('\ue0b6')}${s('\ue0b7')}${s('\ue0b8')}${s('\ue0b9')}${s('\ue0ba')}${s('\ue0bb')}${s('\ue0bc')}${s('\ue0bd')}${s('\ue0be')}${s('\ue0bf')}`);
term.writeln(`0xC_ ${s('\ue0c0')}${s('\ue0c1')}${s('\ue0c2')}${s('\ue0c3')}${s('\ue0c4')}${s('\ue0c5')}${s('\ue0c6')}${s('\ue0c7')}${s('\ue0c8')}${s('\ue0c9')}${s('\ue0ca')}${s('\ue0cb')}${s('\ue0cc')}${s('\ue0cd')}${s('\ue0be')}${s('\ue0bf')}`);
term.writeln(`0xD_ ${s('\ue0d0')}${s('\ue0d1')}${s('\ue0d2')} ${s('\ue0d4')}`);
term.writeln('');
term.writeln('Sample of nerd fonts icons:');
term.writeln(' nf-linux-apple (\\uF302) \uf302');
term.writeln('nf-mdi-github_face (\\uFbd9) \ufbd9');
}
function underlineTest(): void {
function u(style: number): string {
return `\x1b[4:${style}m`;
}
function c(color: string): string {
return `\x1b[58:${color}m`;
}
term.write('\n\n\r');
term.writeln('Underline styles:');
term.writeln('');
function showSequence(id: number, name: string): string {
let alphabet = '';
for (let i = 97; i < 123; i++) {
alphabet += String.fromCharCode(i);
}
let numbers = '';
for (let i = 0; i < 10; i++) {
numbers += i.toString();
}
return `${u(id)}4:${id}m - ${name}\x1b[4:0m`.padEnd(33, ' ') + `${u(id)}${alphabet} ${numbers} 汉语 한국어 👽\x1b[4:0m`;
}
term.writeln(showSequence(0, 'No underline'));
term.writeln(showSequence(1, 'Straight'));
term.writeln(showSequence(2, 'Double'));
term.writeln(showSequence(3, 'Curly'));
term.writeln(showSequence(4, 'Dotted'));
term.writeln(showSequence(5, 'Dashed'));
term.writeln('');
term.writeln(`Underline colors (256 color mode):`);
term.writeln('');
for (let i = 0; i < 256; i++) {
term.write((i !== 0 ? '\x1b[0m, ' : '') + u(1 + i % 5) + c('5:' + i) + i);
}
term.writeln(`\x1b[0m\n\n\rUnderline colors (true color mode):`);
term.writeln('');
for (let i = 0; i < 80; i++) {
const v = Math.round(i / 79 * 255);
term.write(u(1) + c(`2:0:${v}:${v}:${v}`) + (i < 4 ? 'grey'[i] : ' '));
}
term.write('\n\r');
for (let i = 0; i < 80; i++) {
const v = Math.round(i / 79 * 255);
term.write(u(1) + c(`2:0:${v}:${0}:${0}`) + (i < 3 ? 'red'[i] : ' '));
}
term.write('\n\r');
for (let i = 0; i < 80; i++) {
const v = Math.round(i / 79 * 255);
term.write(u(1) + c(`2:0:${0}:${v}:${0}`) + (i < 5 ? 'green'[i] : ' '));
}
term.write('\n\r');
for (let i = 0; i < 80; i++) {
const v = Math.round(i / 79 * 255);
term.write(u(1) + c(`2:0:${0}:${0}:${v}`) + (i < 4 ? 'blue'[i] : ' '));
}
term.write('\x1b[0m\n\r');
}
function ansiColorsTest(): void {
term.writeln(`\x1b[0m\n\n\rStandard colors: Bright colors:`);
for (let i = 0; i < 16; i++) {
term.write(`\x1b[48;5;${i}m ${i.toString().padEnd(2, ' ').padStart(3, ' ')} \x1b[0m`);
}
term.writeln(`\x1b[0m\n\n\rColors 17-231 from 256 palette:`);
for (let i = 0; i < 6; i++) {
const startId = 16 + i * 36;
const endId = 16 + (i + 1) * 36 - 1;
term.write(`${startId.toString().padStart(3, ' ')}-${endId.toString().padStart(3, ' ')} `);
for (let j = 0; j < 36; j++) {
const id = 16 + i * 36 + j;
term.write(`\x1b[48;5;${id}m${(id % 10).toString().padStart(2, ' ')}\x1b[0m`);
}
term.write(`\r\n`);
}
term.writeln(`\x1b[0m\n\rGreyscale from 256 palette:`);
term.write('232-255 ');
for (let i = 232; i < 256; i++) {
term.write(`\x1b[48;5;${i}m ${(i % 10)} \x1b[0m`);
}
}
function writeTestString(): string {
let alphabet = '';
for (let i = 97; i < 123; i++) {
alphabet += String.fromCharCode(i);
}
let numbers = '';
for (let i = 0; i < 10; i++) {
numbers += i.toString();
}
return `${alphabet} ${numbers} 汉语 한국어 👽`;
}
const testString = writeTestString();
function sgrTest(): void {
term.write('\n\n\r');
term.writeln(`Character Attributes (SGR, Select Graphic Rendition)`);
const entries: { ps: number, name: string }[] = [
{ ps: 0, name: 'Normal' },
{ ps: 1, name: 'Bold' },
{ ps: 2, name: 'Faint/dim' },
{ ps: 3, name: 'Italicized' },
{ ps: 4, name: 'Underlined' },
{ ps: 5, name: 'Blink' },
{ ps: 7, name: 'Inverse' },
{ ps: 8, name: 'Invisible' },
{ ps: 9, name: 'Crossed-out characters' },
{ ps: 21, name: 'Doubly-underlined' },
{ ps: 22, name: 'Normal' },
{ ps: 23, name: 'Not italicized' },
{ ps: 24, name: 'Not underlined' },
{ ps: 25, name: 'Steady (not blink)' },
{ ps: 27, name: 'Positive (not inverse)' },
{ ps: 28, name: 'Visible (not hidden)' },
{ ps: 29, name: 'Not crossed-out' },
{ ps: 30, name: 'Foreground Black' },
{ ps: 31, name: 'Foreground Red' },
{ ps: 32, name: 'Foreground Green' },
{ ps: 33, name: 'Foreground Yellow' },
{ ps: 34, name: 'Foreground Blue' },
{ ps: 35, name: 'Foreground Magenta' },
{ ps: 36, name: 'Foreground Cyan' },
{ ps: 37, name: 'Foreground White' },
{ ps: 39, name: 'Foreground default' },
{ ps: 40, name: 'Background Black' },
{ ps: 41, name: 'Background Red' },
{ ps: 42, name: 'Background Green' },
{ ps: 43, name: 'Background Yellow' },
{ ps: 44, name: 'Background Blue' },
{ ps: 45, name: 'Background Magenta' },
{ ps: 46, name: 'Background Cyan' },
{ ps: 47, name: 'Background White' },
{ ps: 49, name: 'Background default' },
{ ps: 53, name: 'Overlined' },
{ ps: 55, name: 'Not overlined' }
];
const maxNameLength = entries.reduce((p, c) => Math.max(c.name.length, p), 0);
for (const e of entries) {
term.writeln(`\x1b[0m\x1b[${e.ps}m ${e.ps.toString().padEnd(2, ' ')} ${e.name.padEnd(maxNameLength, ' ')} - ${testString}\x1b[0m`);
}
const entriesByPs: Map = new Map();
for (const e of entries) {
entriesByPs.set(e.ps, e.name);
}
const comboEntries: { ps: number[] }[] = [
{ ps: [1, 2, 3, 4, 5, 6, 7, 9] },
{ ps: [2, 41] },
{ ps: [4, 53] }
];
term.write('\n\n\r');
term.writeln(`Combinations`);
for (const e of comboEntries) {
const name = e.ps.map(e => entriesByPs.get(e)).join(', ');
term.writeln(`\x1b[0m\x1b[${e.ps.join(';')}m ${name}\n\r${testString}\x1b[0m`);
}
}
function addAnsiHyperlink(): void {
term.write('\n\n\r');
term.writeln(`Regular link with no id:`);
term.writeln('\x1b]8;;https://github.com\x07GitHub\x1b]8;;\x07');
term.writeln('\x1b]8;;https://xtermjs.org\x07https://xtermjs.org\x1b]8;;\x07\x1b[C<- null cell');
term.writeln(`\nAdjacent links:`);
term.writeln('\x1b]8;;https://github.com\x07GitHub\x1b]8;;https://xtermjs.org\x07\x1b[32mxterm.js\x1b[0m\x1b]8;;\x07');
term.writeln(`\nShared ID link (underline should be shared):`);
term.writeln('╔════╗');
term.writeln('║\x1b]8;id=testid;https://github.com\x07GitH\x1b]8;;\x07║');
term.writeln('║\x1b]8;id=testid;https://github.com\x07ub\x1b]8;;\x07 ║');
term.writeln('╚════╝');
term.writeln(`\nWrapped link with no ID (not necessarily meant to share underline):`);
term.writeln('╔════╗');
term.writeln('║ ║');
term.writeln('║ ║');
term.writeln('╚════╝');
term.write('\x1b[3A\x1b[1C\x1b]8;;https://xtermjs.org\x07xter\x1b[B\x1b[4Dm.js\x1b]8;;\x07\x1b[2B\x1b[5D');
}
/**
* Prints the 20977 characters from the CJK Unified Ideographs unicode block.
*/
function addCjk(): void {
term.write('\n\n\r');
for (let i = 0x4E00; i < 0x9FCC; i++) {
term.write(String.fromCharCode(i));
}
}
/**
* Prints the 20977 characters from the CJK Unified Ideographs unicode block with randomized styles.
*/
function addCjkRandomSgr(): void {
term.write('\n\n\r');
for (let i = 0x4E00; i < 0x9FCC; i++) {
term.write(`\x1b[${getRandomSgr()}m${String.fromCharCode(i)}\x1b[0m`);
}
}
const randomSgrAttributes = [
'1', '2', '3', '4', '5', '6', '7', '9',
'21', '22', '23', '24', '25', '26', '27', '28', '29',
'30', '31', '32', '33', '34', '35', '36', '37', '38', '39',
'40', '41', '42', '43', '44', '45', '46', '47', '48', '49'
];
function getRandomSgr(): string {
return randomSgrAttributes[Math.floor(Math.random() * randomSgrAttributes.length)];
}
function addGraphemeClusters(): void {
term.write('\n\n\r');
term.writeln('🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣 [Simple emoji v6: 10 cells, v15: 20 cells]');
term.writeln('\u{1F476}\u{1F3FF}\u{1F476} [baby with emoji modifier fitzpatrick type-6; baby]');
term.writeln('\u{1F469}\u200d\u{1f469}\u200d\u{1f466} [woman+zwj+woman+zwj+boy]');
term.writeln('\u{1F64B}\u{1F64B}\u{200D}\u{2642}\u{FE0F} [person/man raising hand]');
term.writeln('\u{1F3CB}\u{FE0F}=\u{1F3CB}\u{1F3FE}\u{200D}\u{2640}\u{FE0F} [person lifting weights emoji; woman lighting weights, medium dark]');
term.writeln('\u{1F469}\u{1F469}\u{200D}\u{1F393}\u{1F468}\u{1F3FF}\u{200D}\u{1F393} [woman; woman student; man student dark]');
term.writeln('\u{1f1f3}\u{1f1f4}_ [REGIONAL INDICATOR SYMBOL LETTER N and RI O]');
term.writeln('\u{1f1f3}_\u{1f1f4} {RI N; underscore; RI O]');
term.writeln('\u0061\u0301 [letter a with acute accent]');
term.writeln('\u1100\u1161\u11A8=\u1100\u1161= [Korean Jamo]');
term.writeln('\uAC00=\uD685= [Hangul syllables (pre-composed)]');
term.writeln('(\u26b0\ufe0e) [coffin with text_presentation]');
term.writeln('(\u26b0\ufe0f) [coffin with Emoji_presentation]');
term.writeln(' [Égalité (using separate acute) emoij_presentation]');
}
function addDecoration(): void {
term.options['overviewRuler'] = { width: 14 };
const marker = term.registerMarker(1);
const decoration = term.registerDecoration({
marker,
backgroundColor: '#00FF00',
foregroundColor: '#00FE00',
overviewRulerOptions: { color: '#ef292980', position: 'left' }
});
decoration.onRender((e: HTMLElement) => {
e.style.right = '100%';
e.style.backgroundColor = '#ef292980';
});
}
function addOverviewRuler(): void {
term.options['overviewRuler'] = { width: 14 };
term.registerDecoration({ marker: term.registerMarker(1), overviewRulerOptions: { color: '#ef2929' } });
term.registerDecoration({ marker: term.registerMarker(3), overviewRulerOptions: { color: '#8ae234' } });
term.registerDecoration({ marker: term.registerMarker(5), overviewRulerOptions: { color: '#729fcf' } });
term.registerDecoration({ marker: term.registerMarker(7), overviewRulerOptions: { color: '#ef2929', position: 'left' } });
term.registerDecoration({ marker: term.registerMarker(7), overviewRulerOptions: { color: '#8ae234', position: 'center' } });
term.registerDecoration({ marker: term.registerMarker(7), overviewRulerOptions: { color: '#729fcf', position: 'right' } });
term.registerDecoration({ marker: term.registerMarker(10), overviewRulerOptions: { color: '#8ae234', position: 'center' } });
term.registerDecoration({ marker: term.registerMarker(10), overviewRulerOptions: { color: '#ffffff80', position: 'full' } });
}
let decorationStressTestDecorations: IDisposable[] | undefined;
function decorationStressTest(): void {
if (decorationStressTestDecorations) {
for (const d of decorationStressTestDecorations) {
d.dispose();
}
decorationStressTestDecorations = undefined;
} else {
const t = term as Terminal;
const buffer = t.buffer.active;
const cursorY = buffer.baseY + buffer.cursorY;
decorationStressTestDecorations = [];
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 < t.buffer.active.length; y++) {
const cursorOffsetY = y - cursorY;
decorationStressTestDecorations.push(t.registerDecoration({
marker: t.registerMarker(cursorOffsetY),
x,
width: 4,
backgroundColor: '#FF0000',
overviewRulerOptions: { color: '#FF0000' }
}));
}
}
}
}
(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();
};
function ligaturesTest(): void {
term.write([
'',
'-<< -< -<- <-- <--- <<- <- -> ->> --> ---> ->- >- >>-',
'=<< =< =<= <== <=== <<= <= => =>> ==> ===> =>= >= >>=',
'<-> <--> <---> <----> <=> <==> <===> <====> :: ::: __',
'<~~ > /> ~~> == != /= ~= <> === !== !=== =/= =!=',
'<: := *= *+ <* <*> *> <| <|> |> <. <.> .> +* =* =: :>',
'(* *) /* */ [| |] {| |} ++ +++ \/ /\ |- -|