diff --git a/overwrite/addons/tsconfig.eslint.addons.json b/overwrite/addons/tsconfig.eslint.addons.json index bebfc54..dbcdfad 100644 --- a/overwrite/addons/tsconfig.eslint.addons.json +++ b/overwrite/addons/tsconfig.eslint.addons.json @@ -21,6 +21,7 @@ "include": [ "../typings/xterm.d.ts", "./**/src/*", + "./**/src/**/*", "./**/src-worker/*", "./**/test/*", "./**/benchmark/*" diff --git a/overwrite/demo/client.ts b/overwrite/demo/client.ts_copy similarity index 55% rename from overwrite/demo/client.ts rename to overwrite/demo/client.ts_copy index db9417f..951132c 100644 --- a/overwrite/demo/client.ts +++ b/overwrite/demo/client.ts_copy @@ -10,15 +10,17 @@ // Use tsc version (yarn watch) import { Terminal } from '../out/browser/public/Terminal'; import { AttachAddon } from '../addons/xterm-addon-attach/out/AttachAddon'; +import { CanvasAddon } from '../addons/xterm-addon-canvas/out/CanvasAddon'; import { FitAddon } from '../addons/xterm-addon-fit/out/FitAddon'; import { SearchAddon, ISearchOptions } from '../addons/xterm-addon-search/out/SearchAddon'; import { SerializeAddon } from '../addons/xterm-addon-serialize/out/SerializeAddon'; import { WebLinksAddon } from '../addons/xterm-addon-web-links/out/WebLinksAddon'; import { WebglAddon } from '../addons/xterm-addon-webgl/out/WebglAddon'; -import { ImageAddon, IImageAddonOptions } from '../addons/xterm-addon-image/out/ImageAddon'; import { Unicode11Addon } from '../addons/xterm-addon-unicode11/out/Unicode11Addon'; import { LigaturesAddon } from '../addons/xterm-addon-ligatures/out/LigaturesAddon'; +import { ImageAddon, IImageAddonOptions } from '../addons/xterm-addon-image/out/ImageAddon'; + // Use webpacked version (yarn package) // import { Terminal } from '../lib/xterm'; // import { AttachAddon } from 'xterm-addon-attach'; @@ -39,13 +41,13 @@ export interface IWindowWithTerminal extends Window { Terminal?: typeof TerminalType; AttachAddon?: typeof AttachAddon; FitAddon?: typeof FitAddon; - ImageAddon?: typeof ImageAddon; SearchAddon?: typeof SearchAddon; SerializeAddon?: typeof SerializeAddon; WebLinksAddon?: typeof WebLinksAddon; WebglAddon?: typeof WebglAddon; Unicode11Addon?: typeof Unicode11Addon; LigaturesAddon?: typeof LigaturesAddon; + ImageAddon?: typeof ImageAddon; } declare let window: IWindowWithTerminal; @@ -55,55 +57,81 @@ let socketURL; let socket; let pid; -type AddonType = 'attach' | 'fit' | 'image' | 'ligatures' | 'search' | 'serialize' | 'unicode11' | 'web-links' | 'webgl'; +type AddonType = 'attach' | 'canvas' | 'fit' | 'search' | 'serialize' | 'unicode11' | 'web-links' | 'webgl' | 'ligatures' | 'image'; interface IDemoAddon { name: T; canChange: boolean; ctor: T extends 'attach' ? typeof AttachAddon : + T extends 'canvas' ? typeof CanvasAddon : T extends 'fit' ? typeof FitAddon : - T extends 'image' ? typeof ImageAddon : T extends 'search' ? typeof SearchAddon : T extends 'serialize' ? typeof SerializeAddon : T extends 'web-links' ? typeof WebLinksAddon : T extends 'unicode11' ? typeof Unicode11Addon : T extends 'ligatures' ? typeof LigaturesAddon : + T extends 'image' ? typeof ImageAddon : typeof WebglAddon; - instance?: + instance?: T extends 'attach' ? AttachAddon : + T extends 'canvas' ? CanvasAddon : T extends 'fit' ? FitAddon : - T extends 'image' ? ImageAddon : T extends 'search' ? SearchAddon : T extends 'serialize' ? SerializeAddon : T extends 'web-links' ? WebLinksAddon : T extends 'webgl' ? WebglAddon : - T extends 'unicode11' ? Unicode11Addon : + T extends 'unicode11' ? typeof Unicode11Addon : T extends 'ligatures' ? typeof LigaturesAddon : + T extends 'image' ? typeof ImageAddon : never; } const IMAGE_WORKER_PATH = '/workers/xterm-addon-image-worker.js'; -const addons: { [T in AddonType]: IDemoAddon} = { +const addons: { [T in AddonType]: IDemoAddon } = { attach: { name: 'attach', ctor: AttachAddon, canChange: false }, + canvas: { name: 'canvas', ctor: CanvasAddon, canChange: true }, fit: { name: 'fit', ctor: FitAddon, canChange: false }, - image: { name: 'image', ctor: ImageAddon, canChange: true }, - ligatures: { name: 'ligatures', ctor: LigaturesAddon, canChange: true }, search: { name: 'search', ctor: SearchAddon, canChange: true }, serialize: { name: 'serialize', ctor: SerializeAddon, canChange: true }, 'web-links': { name: 'web-links', ctor: WebLinksAddon, canChange: true }, webgl: { name: 'webgl', ctor: WebglAddon, canChange: true }, - unicode11: { name: 'unicode11', ctor: Unicode11Addon, canChange: true } + unicode11: { name: 'unicode11', ctor: Unicode11Addon, canChange: true }, + ligatures: { name: 'ligatures', ctor: LigaturesAddon, canChange: true }, + image: { name: 'image', ctor: ImageAddon, canChange: true } }; -const terminalContainer = document.getElementById('terminal-container'); +let terminalContainer = document.getElementById('terminal-container'); const actionElements = { - findNext: document.querySelector('#find-next'), - findPrevious: document.querySelector('#find-previous') + find: document.querySelector('#find'), + findNext: document.querySelector('#find-next'), + findPrevious: document.querySelector('#find-previous'), + findResults: document.querySelector('#find-results') +}; +const paddingElement = document.getElementById('padding'); + +const xtermjsTheme = { + foreground: '#F8F8F8', + background: '#2D2E2C', + selectionBackground: '#5DA5D533', + 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' }; -const paddingElement = document.querySelector('#padding'); - function setPadding(): void { term.element.style.padding = parseInt(paddingElement.value, 10).toString() + 'px'; addons.fit.instance.fit(); @@ -111,10 +139,18 @@ function setPadding(): void { function getSearchOptions(e: KeyboardEvent): ISearchOptions { return { - regex: document.querySelector('#regex').checked, - wholeWord: document.querySelector('#whole-word').checked, - caseSensitive: document.querySelector('#case-sensitive').checked, - incremental: e.key !== `Enter` + regex: (document.getElementById('regex') as HTMLInputElement).checked, + wholeWord: (document.getElementById('whole-word') as HTMLInputElement).checked, + caseSensitive: (document.getElementById('case-sensitive') as HTMLInputElement).checked, + incremental: e.key !== `Enter`, + decorations: (document.getElementById('highlight-all-matches') as HTMLInputElement).checked ? { + matchBackground: '#232422', + matchBorder: '#555753', + matchOverviewRuler: '#555753', + activeMatchBackground: '#ef2929', + activeMatchBorder: '#ffffff', + activeMatchColorOverviewRuler: '#ef2929' + } : undefined }; } @@ -126,14 +162,15 @@ const disposeRecreateButtonHandler = () => { window.term = null; socket = null; addons.attach.instance = undefined; + addons.canvas.instance = undefined; addons.fit.instance = undefined; - addons.image.instance = undefined; addons.search.instance = undefined; addons.serialize.instance = undefined; addons.unicode11.instance = undefined; addons.ligatures.instance = undefined; addons['web-links'].instance = undefined; addons.webgl.instance = undefined; + addons.image.instance = undefined; document.getElementById('dispose').innerHTML = 'Recreate Terminal'; } else { createTerminal(); @@ -141,24 +178,61 @@ const disposeRecreateButtonHandler = () => { } }; +const createNewWindowButtonHandler = () => { + 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.FitAddon = FitAddon; - window.ImageAddon = ImageAddon; window.SearchAddon = SearchAddon; window.SerializeAddon = SerializeAddon; window.Unicode11Addon = Unicode11Addon; window.LigaturesAddon = LigaturesAddon; window.WebLinksAddon = WebLinksAddon; window.WebglAddon = WebglAddon; + window.ImageAddon = ImageAddon; } else { 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').addEventListener('click', writeCustomGlyphHandler); - initImageAddonExposed(); document.getElementById('load-test').addEventListener('click', loadTest); + 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('add-decoration').addEventListener('click', addDecoration); + document.getElementById('add-overview-ruler').addEventListener('click', addOverviewRuler); + initImageAddonExposed(); } function createTerminal(): void { @@ -169,8 +243,10 @@ function createTerminal(): void { const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0; term = new Terminal({ + allowProposedApi: true, windowsMode: isWindows, - fontFamily: 'Fira Code, courier-new, courier, monospace' + fontFamily: '"Fira Code", courier-new, courier, monospace, "Powerline Extra Symbols"', + theme: xtermjsTheme } as ITerminalOptions); // Load addons @@ -179,6 +255,7 @@ function createTerminal(): void { addons.serialize.instance = new SerializeAddon(); addons.fit.instance = new FitAddon(); addons.unicode11.instance = new Unicode11Addon(); + addons.webgl.instance = new WebglAddon(); addons.image.instance = new ImageAddon(IMAGE_WORKER_PATH); // TODO: Remove arguments when link provider API is the default addons['web-links'].instance = new WebLinksAddon(undefined, undefined, true); @@ -186,8 +263,8 @@ function createTerminal(): void { typedTerm.loadAddon(addons.search.instance); typedTerm.loadAddon(addons.serialize.instance); typedTerm.loadAddon(addons.unicode11.instance); - typedTerm.loadAddon(addons.image.instance); typedTerm.loadAddon(addons['web-links'].instance); + typedTerm.loadAddon(addons.image.instance); window.term = term; // Expose `term` to window for debugging purposes term.onResize((size: { cols: number, rows: number }) => { @@ -198,13 +275,23 @@ function createTerminal(): void { const rows = size.rows; const url = '/terminals/' + pid + '/size?cols=' + cols + '&rows=' + rows; - fetch(url, {method: 'POST'}); + fetch(url, { method: 'POST' }); }); protocol = (location.protocol === 'https:') ? 'wss://' : 'ws://'; socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + '/terminals/'; term.open(terminalContainer); addons.fit.instance!.fit(); + try { + typedTerm.loadAddon(addons.webgl.instance); + setTimeout(() => { + addTextureAtlas(addons.webgl.instance.textureAtlas); + addons.webgl.instance.onChangeTextureAtlas(e => addTextureAtlas(e)); + }, 0); + } + catch { + addons.webgl.instance = undefined; + } term.focus(); addDomListener(paddingElement, 'change', setPadding); @@ -212,23 +299,28 @@ function createTerminal(): void { addDomListener(actionElements.findNext, 'keyup', (e) => { addons.search.instance.findNext(actionElements.findNext.value, getSearchOptions(e)); }); - addDomListener(actionElements.findPrevious, 'keyup', (e) => { addons.search.instance.findPrevious(actionElements.findPrevious.value, getSearchOptions(e)); }); + 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(() => { initOptions(term); // TODO: Clean this up, opt-cols/rows doesn't exist anymore - document.querySelector('#opt-cols').valueAsNumber = term.cols; - document.querySelector('#opt-rows').valueAsNumber = term.rows; + (document.getElementById(`opt-cols`)).value = term.cols; + (document.getElementById(`opt-rows`)).value = term.rows; paddingElement.value = '0'; // Set terminal size again to set the specific dimensions on the demo updateTerminalSize(); - fetch('/terminals?cols=' + term.cols + '&rows=' + term.rows, {method: 'POST'}).then((res) => { + fetch('/terminals?cols=' + term.cols + '&rows=' + term.rows, { method: 'POST' }).then((res) => { res.text().then((processId) => { pid = processId; socketURL += processId; @@ -273,7 +365,7 @@ function runFakeTerminal(): void { if (ev.keyCode === 13) { term.prompt(); } else if (ev.keyCode === 8) { - // Do not delete the prompt + // Do not delete the prompt if (term._core.buffer.x > 2) { term.write('\b \b'); } @@ -294,20 +386,20 @@ function initOptions(term: TerminalType): void { 'windowOptions' ]; const stringOptions = { - bellSound: null, - bellStyle: ['none', 'sound'], cursorStyle: ['block', 'underline', 'bar'], - fastScrollModifier: ['alt', 'ctrl', 'shift', undefined], + fastScrollModifier: ['none', 'alt', 'ctrl', 'shift'], fontFamily: null, fontWeight: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], logLevel: ['debug', 'info', 'warn', 'error', 'off'], - rendererType: ['dom', 'canvas'], + theme: ['default', 'xtermjs', 'sapphire', 'light'], wordSeparator: null }; const options = Object.getOwnPropertyNames(term.options); const booleanOptions = []; - const numberOptions = []; + const numberOptions = [ + 'overviewRulerWidth' + ]; options.filter(o => blacklistedOptions.indexOf(o) === -1).forEach(o => { switch (typeof term.options[o]) { case 'boolean': @@ -317,7 +409,7 @@ function initOptions(term: TerminalType): void { numberOptions.push(o); break; default: - if (Object.keys(stringOptions).indexOf(o) === -1) { + if (Object.keys(stringOptions).indexOf(o) === -1 && numberOptions.indexOf(o) === -1 && booleanOptions.indexOf(o) === -1) { console.warn(`Unrecognized option: "${o}"`); } } @@ -330,12 +422,13 @@ function initOptions(term: TerminalType): void { }); html += '
'; numberOptions.forEach(o => { - html += `
`; + html += `
`; }); html += '
'; Object.keys(stringOptions).forEach(o => { if (stringOptions[o]) { - html += `
`; + const selectedOption = o === 'theme' ? 'xtermjs' : term.options[o]; + html += `
`; } else { html += `
`; } @@ -347,37 +440,98 @@ function initOptions(term: TerminalType): void { // Attach listeners booleanOptions.forEach(o => { - const input = document.querySelector(`#opt-${o}`); + const input = document.getElementById(`opt-${o}`); addDomListener(input, 'change', () => { console.log('change', o, input.checked); term.options[o] = input.checked; }); }); numberOptions.forEach(o => { - const input = document.querySelector(`#opt-${o}`); + const input = document.getElementById(`opt-${o}`); addDomListener(input, 'change', () => { console.log('change', o, input.value); - if (o === 'cols' || o === 'rows') { - updateTerminalSize(); + if (o === 'rows') { + term.resize(term.cols, parseInt(input.value)); + } else if (o === 'cols') { + term.resize(parseInt(input.value), term.rows); } else if (o === 'lineHeight') { term.options.lineHeight = parseFloat(input.value); - updateTerminalSize(); } else if (o === 'scrollSensitivity') { term.options.scrollSensitivity = parseFloat(input.value); - updateTerminalSize(); - } else if(o === 'scrollback') { + } else if (o === 'scrollback') { term.options.scrollback = parseInt(input.value); setTimeout(() => updateTerminalSize(), 5); } else { term.options[o] = parseInt(input.value); } + // Always update terminal size in case the option changes the dimensions + updateTerminalSize(); }); }); Object.keys(stringOptions).forEach(o => { - const input = document.querySelector(`#opt-${o}`); + const input = document.getElementById(`opt-${o}`); addDomListener(input, 'change', () => { console.log('change', o, input.value); - term.options[o] = input.value; + let value: any = input.value; + if (o === 'theme') { + switch (input.value) { + case 'default': + value = undefined; + break; + case 'xtermjs': + // Custom theme to match style of xterm.js logo + value = xtermjsTheme; + case 'sapphire': + // Color source: https://github.com/Tyriar/vscode-theme-sapphire + value = { + background: '#1c2431', + foreground: '#cccccc', + selectionBackground: '#399ef440', + black: '#666666', + blue: '#399ef4', + brightBlack: '#666666', + brightBlue: '#399ef4', + brightCyan: '#21c5c7', + brightGreen: '#4eb071', + brightMagenta: '#b168df', + brightRed: '#da6771', + brightWhite: '#efefef', + brightYellow: '#fff099', + cyan: '#21c5c7', + green: '#4eb071', + magenta: '#b168df', + red: '#da6771', + white: '#efefef', + yellow: '#fff099', + }; + break; + case 'light': + // Color source: https://github.com/microsoft/vscode/blob/main/extensions/theme-defaults/themes/light_plus.json + value = { + background: '#ffffff', + foreground: '#333333', + selectionBackground: '#add6ff', + black: '#000000', + blue: '#0451a5', + brightBlack: '#666666', + brightBlue: '#0451a5', + brightCyan: '#0598bc', + brightGreen: '#14ce14', + brightMagenta: '#bc05bc', + brightRed: '#cd3131', + brightWhite: '#a5a5a5', + brightYellow: '#b5ba00', + cyan: '#0598bc', + green: '#00bc00', + magenta: '#bc05bc', + red: '#cd3131', + white: '#555555', + yellow: '#949800', + }; + break; + } + } + term.options[o] = value; }); }); } @@ -392,9 +546,12 @@ function initAddons(term: TerminalType): void { if (!addon.canChange) { checkbox.disabled = true; } - if(name === 'unicode11' && checkbox.checked) { + if (name === 'unicode11' && checkbox.checked) { term.unicode.activeVersion = '11'; } + if (name === 'search' && checkbox.checked) { + addon.instance.onDidChangeResults(e => updateFindResults(e)); + } addDomListener(checkbox, 'change', () => { if (name === 'image') { if (checkbox.checked) { @@ -411,17 +568,24 @@ function initAddons(term: TerminalType): void { } if (checkbox.checked) { addon.instance = new addon.ctor(); - term.loadAddon(addon.instance); - if (name === 'webgl') { - setTimeout(() => { - document.body.appendChild((addon.instance as WebglAddon).textureAtlas); - }, 0); - } else if (name === 'unicode11') { - term.unicode.activeVersion = '11'; + try { + term.loadAddon(addon.instance); + if (name === 'webgl') { + (addon.instance as WebglAddon).onChangeTextureAtlas(e => addTextureAtlas(e)); + } else if (name === 'unicode11') { + term.unicode.activeVersion = '11'; + } else if (name === 'search') { + addon.instance.onDidChangeResults(e => updateFindResults(e)); + } + } + catch { + addon.instance = undefined; + checkbox.checked = false; + checkbox.disabled = true; } } else { if (name === 'webgl') { - document.body.removeChild((addon.instance as WebglAddon).textureAtlas); + (addon.instance as WebglAddon).textureAtlas.remove(); } else if (name === 'unicode11') { term.unicode.activeVersion = '6'; } @@ -446,14 +610,24 @@ function initAddons(term: TerminalType): void { container.appendChild(fragment); } +function updateFindResults(e: { resultIndex: number, resultCount: number } | undefined) { + 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 cols = document.querySelector('#opt-cols').valueAsNumber; - const rows = document.querySelector('#opt-rows').valueAsNumber; + const cols = parseInt((document.getElementById(`opt-cols`)).value, 10); + const rows = parseInt((document.getElementById(`opt-rows`)).value, 10); const width = (cols * term._core._renderService.dimensions.actualCellWidth + term._core.viewport.scrollBarWidth).toString() + 'px'; const height = (rows * term._core._renderService.dimensions.actualCellHeight).toString() + 'px'; terminalContainer.style.width = width; @@ -465,13 +639,32 @@ function serializeButtonHandler(): void { const output = addons.serialize.instance.serialize(); const outputString = JSON.stringify(output); - document.querySelector('#serialize-output').innerText = outputString; - if (document.querySelector('#write-to-terminal').checked) { + 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) { + 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 addTextureAtlas(e: HTMLCanvasElement) { + document.querySelector('#texture-atlas').replaceChildren(e); +} + function writeCustomGlyphHandler() { term.write('\n\r'); term.write('\n\r'); @@ -517,6 +710,242 @@ function writeCustomGlyphHandler() { window.scrollTo(0, 0); } +function loadTest() { + const rendererName = addons.webgl.instance ? 'webgl' : !!addons.canvas.instance ? 'canvas' : '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 powerlineSymbolTest() { + 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() { + 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) { + 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() { + 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 addAnsiHyperlink() { + 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'); +} + +function addDecoration() { + term.options['overviewRulerWidth'] = 15; + 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() { + term.options['overviewRulerWidth'] = 15; + 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' } }); +} + function initImageAddonExposed(): void { const DEFAULT_OPTIONS: IImageAddonOptions = (addons.image.instance as any)._defaultOpts; const limitStorageElement = document.querySelector('#image-storagelimit'); @@ -566,36 +995,3 @@ function initImageAddonExposed(): void { canvas?.toBlob(data => window.open(URL.createObjectURL(data), '_blank')); }); } - -function loadTest() { - const isWebglEnabled = !!addons.webgl.instance; - 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 (${isWebglEnabled ? 'webgl' : 'canvas'} renderer)`); - // Send ^C to get a new prompt - term._core._onData.fire('\x03'); - }); -} diff --git a/overwrite/demo/index.html b/overwrite/demo/index.html index 85abf03..4a9893d 100644 --- a/overwrite/demo/index.html +++ b/overwrite/demo/index.html @@ -10,8 +10,8 @@ - - + +

xterm.js: A terminal for the web

@@ -28,7 +28,7 @@

xterm.js: A terminal for the

Options

-

These options can be set in the Terminal constructor or by using the Terminal.setOption function.

+

These options can be set in the Terminal constructor or by using the Terminal.options property.

@@ -40,15 +40,21 @@

SearchAddon

+
Results:
+

SerializeAddon

+ + + +

Image Addon

@@ -63,8 +69,6 @@

Image Addon

- -

Style

@@ -76,13 +80,34 @@

Style

Test

- - - +
+
Lifecycle
+
+
+ +
Performance
+
+ +
Styles
+
+
+
+
+
+ +
Decorations
+
+
+ +
Sixel Test
+
+
+
+