/** * Copyright (c) 2018 The xterm.js authors. All rights reserved. * @license MIT */ import { BaseWindow } from './baseWindow'; import type { Terminal, ITheme } from '@xterm/xterm'; import type { IControlWindow } from '../controlBar'; import type { AddonCollection } from 'types'; const xtermjsTheme: ITheme = Object.freeze({ 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' }); const sapphireTheme: ITheme = Object.freeze({ 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' }); const lightTheme: ITheme = Object.freeze({ background: '#ffffff', foreground: '#333333', cursor: '#333333', cursorAccent: '#ffffff', selectionBackground: '#add6ff', overviewRulerBorder: '#aaaaaa', 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' }); export class OptionsWindow extends BaseWindow implements IControlWindow { public readonly id = 'options'; public readonly label = 'Options'; private _container!: HTMLElement; private _optionsContainer!: HTMLElement; private _autoResize: boolean = true; constructor( terminal: Terminal, addons: AddonCollection, private readonly _handlers: { updateTerminalSize: () => void; updateTerminalContainerBackground: () => void; }, ) { super(terminal, addons); } public build(container: HTMLElement): void { this._container = container; const description = document.createElement('p'); description.innerHTML = 'These options can be set in the Terminal constructor or by using the Terminal.options property.'; container.appendChild(description); this._optionsContainer = document.createElement('div'); this._optionsContainer.id = 'options-container'; container.appendChild(this._optionsContainer); } public initOptions(addDomListener: (el: HTMLElement, type: string, handler: (...args: any[]) => any) => void): void { const blacklistedOptions = [ 'convertEol', 'termName', 'cols', 'rows', 'documentOverride', 'linkHandler', 'logger', 'quirks', 'scrollbar', 'theme', 'vtExtensions', 'windowOptions', 'windowsPty', ]; const nestedBooleanOptions: { label: string, path: string[], prop: string }[] = [ { label: 'scrollbar.showScrollbar', path: ['scrollbar'], prop: 'showScrollbar' }, { label: 'scrollbar.showArrows', path: ['scrollbar'], prop: 'showArrows' }, { label: 'scrollbar.overviewRuler.showTopBorder', path: ['scrollbar', 'overviewRuler'], prop: 'showTopBorder' }, { label: 'scrollbar.overviewRuler.showBottomBorder', path: ['scrollbar', 'overviewRuler'], prop: 'showBottomBorder' }, { label: 'vtExtensions.kittyKeyboard', path: ['vtExtensions'], prop: 'kittyKeyboard' }, { label: 'vtExtensions.kittySgrBoldFaintControl', path: ['vtExtensions'], prop: 'kittySgrBoldFaintControl' }, { label: 'vtExtensions.win32InputMode', path: ['vtExtensions'], prop: 'win32InputMode' } ]; const stringOptions: { [key: string]: string[] | null } = { cursorStyle: ['block', 'underline', 'bar'], cursorInactiveStyle: ['outline', 'block', 'bar', 'underline', 'none'], 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: ['trace', 'debug', 'info', 'warn', 'error', 'off'], theme: ['default', 'xtermjs', 'sapphire', 'light'], wordSeparator: null, colsRows: null }; const options = Object.getOwnPropertyNames(this._terminal.options); const booleanOptions: string[] = []; const numberOptions: string[] = []; options.filter(o => blacklistedOptions.indexOf(o) === -1).forEach(o => { switch (typeof (this._terminal.options as Record)[o]) { case 'boolean': booleanOptions.push(o); break; case 'number': numberOptions.push(o); break; default: if (Object.keys(stringOptions).indexOf(o) === -1 && numberOptions.indexOf(o) === -1 && booleanOptions.indexOf(o) === -1) { console.warn(`Unrecognized option: "${o}"`); } } }); let html = ''; html += '
'; booleanOptions.forEach(o => { html += `
`; }); nestedBooleanOptions.forEach(({ label, path, prop }) => { const options = this._terminal.options as Record; const parent = path.reduce | undefined>((acc, key) => (acc as Record | undefined)?.[key] as Record | undefined, options); const checked = (parent as Record | undefined)?.[prop] ?? false; html += `
`; }); html += '
'; numberOptions.forEach(o => { html += `
`; }); html += '
'; Object.keys(stringOptions).forEach(o => { if (o === 'colsRows') { html += `
`; } else if (stringOptions[o]) { const selectedOption = o === 'theme' ? 'xtermjs' : (this._terminal.options as Record)[o]; html += `
`; } else { html += `
`; } }); html += '
'; this._optionsContainer.innerHTML = html; // Attach listeners booleanOptions.forEach(o => { const input = document.getElementById(`opt-${o}`) as HTMLInputElement; addDomListener(input, 'change', () => { console.log('change', o, input.checked); (this._terminal.options as Record)[o] = input.checked; if (o ==='allowTransparency') { this._terminal.options.theme = this._getTheme(); this._handlers.updateTerminalContainerBackground(); } }); }); nestedBooleanOptions.forEach(({ label, path, prop }) => { const input = document.getElementById(`opt-${label.replace('.', '-')}`) as HTMLInputElement; addDomListener(input, 'change', () => { console.log('change', label, input.checked); const options = this._terminal.options as Record; if (path.length === 1) { const parentKey = path[0]; options[parentKey] = { ...(options[parentKey] as Record | undefined), [prop]: input.checked }; return; } if (path.length === 2) { const [parentKey, childKey] = path; const parent = (options[parentKey] as Record | undefined) ?? {}; const child = (parent[childKey] as Record | undefined) ?? {}; options[parentKey] = { ...parent, [childKey]: { ...child, [prop]: input.checked } }; } }); }); numberOptions.forEach(o => { const input = document.getElementById(`opt-${o}`) as HTMLInputElement; addDomListener(input, 'change', () => { console.log('change', o, input.value); if (o === 'lineHeight') { this._terminal.options.lineHeight = parseFloat(input.value); } else if (o === 'scrollSensitivity') { this._terminal.options.scrollSensitivity = parseFloat(input.value); } else if (o === 'scrollback') { this._terminal.options.scrollback = parseInt(input.value); setTimeout(() => this._handlers.updateTerminalSize(), 5); } else { (this._terminal.options as Record)[o] = parseInt(input.value); } this._handlers.updateTerminalSize(); }); }); Object.keys(stringOptions).forEach(o => { const input = document.getElementById(`opt-${o}`) as HTMLInputElement; addDomListener(input, 'change', () => { console.log('change', o, input.value); let value: unknown = input.value; if (o === 'colsRows') { const m = input.value.match(/^([0-9]+)x([0-9]+)$/); if (m) { this._autoResize = false; this._terminal.resize(parseInt(m[1]), parseInt(m[2])); } else { this._autoResize = true; input.value = 'auto'; this._handlers.updateTerminalSize(); } const screenElement = document.querySelector('.xterm-screen'); if (screenElement instanceof HTMLElement) { screenElement.style.outline = this._autoResize ? '' : 'solid thin red'; } } else if (o === 'theme') { value = this._getTheme(); } (this._terminal.options as Record)[o] = value; if (o === 'theme') { this._handlers.updateTerminalContainerBackground(); } if (o === 'fontFamily') { this._handlers.updateTerminalSize(); } }); }); } public get autoResize(): boolean { return this._autoResize; } public set autoResize(value: boolean) { this._autoResize = value; } private _getTheme(): ITheme { const input = document.querySelector('#opt-theme')!; let theme: ITheme = {}; switch (input.value) { case 'default': theme = {}; break; case 'xtermjs': theme = { ...xtermjsTheme }; break; case 'sapphire': theme = { ...sapphireTheme }; break; case 'light': theme = { ...lightTheme }; break; } if (this._terminal.options.allowTransparency) { theme.background = 'transparent'; } return theme; } }