Merge remote-tracking branch 'upstream/master' into de_vs
This commit is contained in:
@@ -22,10 +22,10 @@ npm run build && npm run esbuild # Build all TypeScript and bundle
|
||||
**Testing**:
|
||||
- Unit tests: `npm run test-unit` (Mocha)
|
||||
- Unit tests filtering to file: `npm run test-unit -- **/fileName.ts
|
||||
- Per-addon unit tests: `npm run test-unit addons/addon-image/out-esbuild/*.test.js`
|
||||
- Per-addon unit tests: `npm run test-unit -- addons/addon-image/out-esbuild/*.test.js`
|
||||
- Integration tests: `npm run test-integration` (Playwright across Chrome/Firefox/WebKit)
|
||||
- Integration tests by file: `npm run test-integration -- test/playwright/InputHandler.test.ts`. Never use grep to filter tests, it doesn't work
|
||||
- Integration tests by addon: `npm run test-integration --suite=addon-search`. Suites always follow the format `addon-<something>`
|
||||
- Integration tests by addon: `npm run test-integration -- --suite=addon-search`. Suites always follow the format `addon-<something>`
|
||||
- Lint changes: `npm run lint-changes` to lint only changed files, `npm run lint-changes-fix` to fix them
|
||||
|
||||
## Addon Development Pattern
|
||||
|
||||
@@ -5,10 +5,9 @@ const config: PlaywrightTestConfig = {
|
||||
timeout: 10000,
|
||||
projects: [
|
||||
{
|
||||
name: 'ChromeStable',
|
||||
name: 'Chromium',
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
channel: 'chrome'
|
||||
browserName: 'chromium'
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -5,10 +5,9 @@ const config: PlaywrightTestConfig = {
|
||||
timeout: 10000,
|
||||
projects: [
|
||||
{
|
||||
name: 'ChromeStable',
|
||||
name: 'Chromium',
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
channel: 'chrome'
|
||||
browserName: 'chromium'
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -5,10 +5,9 @@ const config: PlaywrightTestConfig = {
|
||||
timeout: 10000,
|
||||
projects: [
|
||||
{
|
||||
name: 'ChromeStable',
|
||||
name: 'Chromium',
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
channel: 'chrome'
|
||||
browserName: 'chromium'
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -9,10 +9,6 @@ import Base64Decoder from 'xterm-wasm-parts/lib/base64/Base64Decoder.wasm';
|
||||
import { HeaderParser, IHeaderFields, HeaderState } from './IIPHeaderParser';
|
||||
import { imageType, UNSUPPORTED_TYPE } from './IIPMetrics';
|
||||
|
||||
|
||||
// eslint-disable-next-line
|
||||
declare const Buffer: any;
|
||||
|
||||
// limit hold memory in base64 decoder
|
||||
const KEEP_DATA = 4194304;
|
||||
|
||||
@@ -105,7 +101,8 @@ export class IIPHandler implements IOscHandler, IResetHandler {
|
||||
return true;
|
||||
}
|
||||
|
||||
const blob = new Blob([new Uint8Array(this._dec.data8)], { type: this._metrics.mime });
|
||||
// HACK: The types on Blob are too restrictive, this is a Uint8Array so the browser accepts it
|
||||
const blob = new Blob([this._dec.data8 as Uint8Array<ArrayBuffer>], { type: this._metrics.mime });
|
||||
this._dec.release();
|
||||
|
||||
if (!window.createImageBitmap) {
|
||||
|
||||
@@ -8,6 +8,7 @@ declare const Buffer: any;
|
||||
|
||||
|
||||
export interface IHeaderFields {
|
||||
[key: string]: number | string | Uint32Array | null | undefined;
|
||||
// base-64 encoded filename. Defaults to "Unnamed file".
|
||||
name: string;
|
||||
// File size in bytes. The file transfer will be canceled if this size is exceeded.
|
||||
@@ -81,7 +82,7 @@ function toName(data: Uint32Array): string {
|
||||
return new TextDecoder().decode(b);
|
||||
}
|
||||
|
||||
const DECODERS: {[key: string]: (v: Uint32Array) => any} = {
|
||||
const DECODERS: {[key: string]: (v: Uint32Array) => number | string} = {
|
||||
inline: toInt,
|
||||
size: toInt,
|
||||
name: toName,
|
||||
@@ -100,7 +101,7 @@ export class HeaderParser {
|
||||
private _buffer = new Uint32Array(MAX_FIELDCHARS);
|
||||
private _position = 0;
|
||||
private _key = '';
|
||||
public fields: {[key: string]: any} = {};
|
||||
public fields: {[key: string]: number | string | Uint32Array | null | undefined} = {};
|
||||
|
||||
public reset(): void {
|
||||
this._buffer.fill(0);
|
||||
|
||||
@@ -132,8 +132,10 @@ export class ImageStorage implements IDisposable {
|
||||
) {
|
||||
try {
|
||||
this.setLimit(this._opts.storageLimit);
|
||||
} catch (e: any) {
|
||||
console.error(e.message);
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Error) {
|
||||
console.error(e.message);
|
||||
}
|
||||
console.warn(`storageLimit is set to ${this.getLimit()} MB`);
|
||||
}
|
||||
this._viewportMetrics = {
|
||||
|
||||
@@ -5,10 +5,9 @@ const config: PlaywrightTestConfig = {
|
||||
timeout: 10000,
|
||||
projects: [
|
||||
{
|
||||
name: 'ChromeStable',
|
||||
name: 'Chromium',
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
channel: 'chrome'
|
||||
browserName: 'chromium'
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -42,11 +42,11 @@ export default async function load(fontFamily: string, cacheSize: number): Promi
|
||||
if (status && status.state !== 'granted') {
|
||||
throw new Error('Permission to access local fonts not granted.');
|
||||
}
|
||||
} catch (err: any) {
|
||||
} catch (err: unknown) {
|
||||
// A `TypeError` indicates the 'local-fonts'
|
||||
// permission is not yet implemented, so
|
||||
// only `throw` if this is _not_ the problem.
|
||||
if (err.name !== 'TypeError') {
|
||||
if (err instanceof Error && err.name !== 'TypeError') {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
@@ -60,8 +60,10 @@ export default async function load(fontFamily: string, cacheSize: number): Promi
|
||||
fonts[metadata.family].push(metadata);
|
||||
}
|
||||
fontsPromise = Promise.resolve(fonts);
|
||||
} catch (err: any) {
|
||||
console.error(err.name, err.message);
|
||||
} catch (err: unknown) {
|
||||
if (err instanceof Error) {
|
||||
console.error(err.name, err.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Latest proposal https://bugs.chromium.org/p/chromium/issues/detail?id=1312603
|
||||
@@ -76,8 +78,10 @@ export default async function load(fontFamily: string, cacheSize: number): Promi
|
||||
fonts[metadata.family].push(metadata);
|
||||
}
|
||||
fontsPromise = Promise.resolve(fonts);
|
||||
} catch (err: any) {
|
||||
console.error(err.name, err.message);
|
||||
} catch (err: unknown) {
|
||||
if (err instanceof Error) {
|
||||
console.error(err.name, err.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
fontsPromise ??= Promise.resolve({});
|
||||
|
||||
@@ -30,7 +30,7 @@ export function enableLigatures(term: Terminal, fallbackLigatures: string[] = []
|
||||
let currentFontName: string | undefined = undefined;
|
||||
let font: Font | undefined = undefined;
|
||||
let loadingState: LoadingState = LoadingState.UNLOADED;
|
||||
let loadError: any | undefined = undefined;
|
||||
let loadError: unknown = undefined;
|
||||
|
||||
return term.registerCharacterJoiner((text: string): [number, number][] => {
|
||||
// If the font hasn't been loaded yet, load it and return an empty result
|
||||
|
||||
@@ -5,10 +5,9 @@ const config: PlaywrightTestConfig = {
|
||||
timeout: 10000,
|
||||
projects: [
|
||||
{
|
||||
name: 'ChromeStable',
|
||||
name: 'Chromium',
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
channel: 'chrome'
|
||||
browserName: 'chromium'
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import type { Terminal, IDisposable, ITerminalAddon } from '@xterm/xterm';
|
||||
import type { SearchAddon as ISearchApi, ISearchOptions, ISearchAddonOptions, ISearchResultChangeEvent } from '@xterm/addon-search';
|
||||
import type { SearchAddon as ISearchApi, ISearchOptions, ISearchAddonOptions, ISearchResultChangeEvent, ISearchDecorationOptions } from '@xterm/addon-search';
|
||||
import { Emitter, type IEvent } from 'common/Event';
|
||||
import { Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';
|
||||
import { disposableTimeout } from 'common/Async';
|
||||
@@ -222,7 +222,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon, ISearchAp
|
||||
* @param result The result to select.
|
||||
* @returns Whether a result was selected.
|
||||
*/
|
||||
private _selectResult(result: ISearchResult | undefined, options?: any, noScroll?: boolean): boolean {
|
||||
private _selectResult(result: ISearchResult | undefined, options?: ISearchDecorationOptions, noScroll?: boolean): boolean {
|
||||
if (!this._terminal || !this._decorationManager) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -5,10 +5,9 @@ const config: PlaywrightTestConfig = {
|
||||
timeout: 10000,
|
||||
projects: [
|
||||
{
|
||||
name: 'ChromeStable',
|
||||
name: 'Chromium',
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
channel: 'chrome'
|
||||
browserName: 'chromium'
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -5,10 +5,9 @@ const config: PlaywrightTestConfig = {
|
||||
timeout: 10000,
|
||||
projects: [
|
||||
{
|
||||
name: 'ChromeStable',
|
||||
name: 'Chromium',
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
channel: 'chrome'
|
||||
browserName: 'chromium'
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import UnicodeTrie from './unicode-trie';
|
||||
const trieRaw = "AAARAAAAAABwxwAAAb4LQfTtmw+sVmUdx58LL/ffe/kjzNBV80gW1F3yR+6CvbJiypoZa0paWmAWSluErSBbFtYkkuZykq6QamGJ4WRqo2kFGy6dYWtEq6G1MFAJbRbOVTQr+x7f5+x97q/n/3me87wXzm/3s+f/7/d7/p7znnvOlvGMbQM7wIPgEbAPHABPgcPgefAS+BfYwuv/F/Q2OulBxKcK6TMRPxu8FcwFbwcjYCFYDC4Cl4ArwNXgGvBJsA58UdBDwy+jbBO4La8DtoEd4H7wkNBuN+KPgn3gADgIngaHwFHwF/AyeAWMm4C+TGi3LdiJ/EnIex04A2RgFpgD5oKFYDG4CLwHXAo+IKSvAqt4/evA9bz9jWA6+Cq3dyvCP8HWNwX93wF38/ROcD94SCjP2+1B+BiPP4HwgOD/7xD/I08fRniMx48jPAFeBeuF+n29jE0G08FZvaPHYWZvh9mcEfAOjlhXx/qGfd2QvLO3zccmtMnzliC9lPt+GenD1nyMiK/LNf1cycs+gfAzPJ6vtxe4jhuQtx5sBLeA28G3eb3v8/Beif4HkPewxu5G6N/rMP4qfgEdvwZPgj+AZ8Cx3nYfxiE8Dk6AV0FfH/YEOB28AbwJDIPzQAtcAC4Gl/Z19F+J+NVCehWPr0b46b7RvixvdPg8yr7U10l/BfFN4La8DdgGdoAHwU/AI2AfOACeAofB8+AlcAKwfvyBKeCM/o7NrF9PXmdWv9/Ynot2I7ztIg8dF5I2a8i63CjZU+9Fm2Wcy4U4ZQVYyeOrwVoev57UuxHcJKRvFuJXgnU8/nUebtbYrKmpCUOx31P7UVNTU1NTU1NTU1OGLTz8Xr/77+W7+9vP0or0MxPMbXaizY8FW3sQ3wseB/t5/kGEh8DR/vbzwL8i/Af4Dy8fP8BYE0weaKenI/wV/DhrQG97JspngzlgLpgHzgPzwUhdVpfVZXVZXRa87HxwAVgQ4Pn5WEd85l5TUzOasvezFw/E3b/LoP9D4CpwrcTWWsGXNQOj748/G9k3G56d1KYxmbELwQbwKFiJvBM8nDWlHa5E+AOwCzwLzjkNeeB28NvTeB1OYyr0gQ1g99R23nGE50xj7MPgc+A+8K5Bxj4FHgB/G2z/T9XEzCZjd/S0WYX4Pc3/r/Nn5I0f6qQXIP5x8ENwBMyYyNhHJ3b0pOCuLrBvM941NTU1JyNHEp+BrC8dMyalt1/m3uWfhmeULzRGp9d3wf0WZSN8+prCr60Wz09tuNmx35sl9Y825HXvRN39KNveaL8flb9f913kbec67kHeTsR3gYcH2uV7ED4m2HhCYi/X9ZuBzvuXv0f8iKIfx5B/XCg7gTgbVPdvAsomCuWnD45eK28UyvL3Jt+s0fU2TVnOXJQvJHUWIb0ELAWXgCt4+UcMumSsEtpch/g6ouMGpG/ieZsc9N/q4YsLd3D9WyPbsWEbfNgO7hN82TWY/n8xKbmsC3xQsYKf+7sjrx2TH+u4H3vhx+OO6+X9hmtXN7C/4r15EPaeBs9J7L7YBeeED/k7wn8fbIf/Rji+yVizmd4vW6bB19cb/PU9w7MxMA60bzPHgM8+zG623+OnzOf55yNc3Gw/k303wveBy3nZcoTXgNVgLfiCRNcG5N3SbIebwZ08fhe4l8d/BH7K4yI/4+HPwS/BAfBks+PzIaHuc3x+ivSL4GUyZ68I6fwZYRNMG2qnz+Th2QjfMtTx/1zE5w61nyN+Q7C3aKgdin1dgrylYBn4INdhGn/Z2FfFiqH01/SUXMvnPD+jC+j85N/RqRhR/DYaS6T+P09K1mD+vzW+5zVqqeVUl0wTz2lK8odJHRGXfBufdGLSoSo3+ZFJ6sl0qvJVNmhI4z4i06mrZ6uT1le1z5h5HE3tMiHPtQ5javu+ItMXUr/MXpmwmyRL3D6U7UwIMyYfczGu0qdqb2pbhcw4xQkhWQBMerrZ/liXrGTbsQwTwrEu4zSczKLrd7fCSKiKn+zSo8BWXMe8myXWOivrUxWi60OPoQ7VIasbQ0S/Ukk3rZVullNhHEL1rYoxUF0PTfm6elWJzq54ZsU4z11ohOy0oxT2izFqCNj4TesXcWZo6+Jfqr1O+1O1beqDagypj2J9F1u2daucj3Eknmq/6PaHrK7Mb1o35DiW1a/a76LuhlDXZX25SOz11S33ErKxDb2/fc/bFKI6axskn+4/W90u9mOtbRf7smsoTdvOfwoRz0t6DaP9k81v6P7Re5aUQudTd303rX+bZzBl97/KR7E+Xbux9lLI+aNr1PfaYLpPDiW2/vrYTX1drMIeXbMye6HXlw8292Jl7ZXxLxRlxXbcaH9drjFlxfa3Qozx8NWRi834lPVZbD+SmN7EJPzc9TVCSVXXDps9L+513b2J7fMu176V2YOhx1A3JrJ8KrLxUumpcu5j/lYT+2tzLRVDZmhjO442a1Clu0ox9VPVXzE/lcS4V0k1D6LI1pJsz8fct9SGbO5l/rmKzTlvsxdj3IvRtC2uv0t1fotltvd2VaCy5Sp5m0EhnZG4CCNxXZrWp/VUIrOjapfnNw11ZNI0V/GWzKNuxtzGKKTEtJeR0NVmpojbtBuW5On0u0is9ZMxvU8ZM+8vEyadtu10oqtP9Q4rcJEm85+Two/QkpGwjI6YkgkhtUfzZOW6fFVexuRri+qj9TJJHZkdmW5abiu0rs6uj2TMfmx06bISUj9tZ9Lja8dVQtox6WpxTJKfW3M4MSTmvU4sWy1CU6BF4jIfdNeDjHWuO1lCWIm2Jr2ixNZvklD2fP0Q6+vsmO4hqN1hJvfDtV5G8mTlsvau4qPP1a64L1skT6QYEzEtq0PzGZOfCbSdSmcKTP7Qs86Ej/1hEpelaV6IMdT5ayu2+nT9tmnnO746XbLxE8t0qOrYtJWhmk9bvaLfsrotRVw1PnR+bcafSUKZ6Mps7smobybJLH2R6WqRkJa1DHV0UmbfUcksiSF0HExSpp+uY0zbTklMaCm7blzEtg8h1rNMXNaYi05ZXsbC75sQ/4+aUxFV2jL50Q3jE0rK2rVtN09By8OHoo1vH2LPSdE323mr2sdu0pUZiDkWLRKWnfeQY6taKzHF9n/GPv8jd/0/egiRvYMR24fU79iY3s9Qva9RlYR8n8HHtq9fMcT1HRWfdZXiHd9YInt/iI4PTaf+BimXKvdXYU+3hlRpHzs2dVK/cxhDn+xs0I2jzxjL5kpXz1VU72aLtkK/97sALKyQqu25SshvG6h08/cLrlKswRklKXvvXfa+pZt+y8nah5YUv2Oo/ap/X2URdRfico9K69hcp6r6XaCz5Wo/hs/iNTGF6N6tV92/9ZS0Wba9SlT3pKF/e6W674+x9ly+VRL73cPU8ygb31D3eSqfVd+iqET0y3YMYojoO11XqrTt2nPxmeq1HYeqxkmUMt8DiesjpoTSr+qDrD+qPZDiOZxMdH0pRPX8MFUfQtv0Xbs+a1a1NnRryNZ/2+tsaPG5ZoX0RXZei88yZGdo4UMPj/cwv/kMJboxLISuQbE+1VW12Mx7FWOrW3M9Hv7Y+uxyraPSo8B2TGPuLdOeZha+hBKf8Sjsm/oR+7pmsx/oeOraFWdXleeV6oyl41zm+mgSuq9C6ox1TsU8D+m4dwMmf8v2nz7Tm+fYfj7HV1K/x1HWjquvY+2dllxM64ue87Su772zzbXIVC+WxLZTRR9MdkMTypZNH1z6G0tUvoccwxA+hfLNdV+a7MaQqscztMi+7QnxDZXvd1dldWQOyMbApb1Jd2h91Ffx+y9Xfb7tClokboOvrRhrbVpFFO8z+65t2/u4su9MUx028znH01/TGVDmHAj13W1o+1USw+eUfYtpO+b82rRNsb6oPpV+1fdBqddB6n3WDXvdJDZrJ0QfQp6bsc/kqq4BIddHWXGdN1pmWveh58F1zYUW1zmOITHXWOg1XrZvZSWUf77tq1ofqear6muaT1lIQp3bofabSafJVlnfYo9B6LGr8uzz2Xchvzfw+T9PlgiV/A8=";
|
||||
|
||||
declare const Buffer: any;
|
||||
declare const Buffer: { from(s: string, encoding: string): Uint8Array } | undefined;
|
||||
function _dec(s: string): Uint8Array {
|
||||
if (typeof Buffer !== 'undefined') return Buffer.from(s, 'base64');
|
||||
const bs = atob(s);
|
||||
|
||||
@@ -5,10 +5,9 @@ const config: PlaywrightTestConfig = {
|
||||
timeout: 10000,
|
||||
projects: [
|
||||
{
|
||||
name: 'ChromeStable',
|
||||
name: 'Chromium',
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
channel: 'chrome'
|
||||
browserName: 'chromium'
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -5,10 +5,9 @@ const config: PlaywrightTestConfig = {
|
||||
timeout: 10000,
|
||||
projects: [
|
||||
{
|
||||
name: 'ChromeStable',
|
||||
name: 'Chromium',
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
channel: 'chrome'
|
||||
browserName: 'chromium'
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -5,10 +5,9 @@ const config: PlaywrightTestConfig = {
|
||||
timeout: 10000,
|
||||
projects: [
|
||||
{
|
||||
name: 'ChromeStable',
|
||||
name: 'Chromium',
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
channel: 'chrome'
|
||||
browserName: 'chromium'
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -5,10 +5,9 @@ const config: PlaywrightTestConfig = {
|
||||
timeout: 10000,
|
||||
projects: [
|
||||
{
|
||||
name: 'ChromeStable',
|
||||
name: 'Chromium',
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
channel: 'chrome'
|
||||
browserName: 'chromium'
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -13,7 +13,8 @@ import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IThemeS
|
||||
import { CharData, IBufferLine, ICellData } from 'common/Types';
|
||||
import { AttributeData } from 'common/buffer/AttributeData';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { Attributes, Content, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants';
|
||||
import { Attributes, Content, FgFlags, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants';
|
||||
import { TextBlinkStateManager } from 'browser/renderer/shared/TextBlinkStateManager';
|
||||
import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { GlyphRenderer } from './GlyphRenderer';
|
||||
@@ -30,6 +31,7 @@ import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils';
|
||||
export class WebglRenderer extends Disposable implements IRenderer {
|
||||
private _renderLayers: IRenderLayer[];
|
||||
private _cursorBlinkStateManager: MutableDisposable<CursorBlinkStateManager> = new MutableDisposable();
|
||||
private _textBlinkStateManager: TextBlinkStateManager;
|
||||
private _charAtlasDisposable = this._register(new MutableDisposable());
|
||||
private _charAtlas: ITextureAtlas | undefined;
|
||||
private _devicePixelRatio: number;
|
||||
@@ -37,8 +39,9 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
private _observerDisposable = this._register(new MutableDisposable());
|
||||
|
||||
private _model: RenderModel = new RenderModel();
|
||||
private _rowHasBlinkingCells: boolean[] = [];
|
||||
private _rowHasBlinkingCellsCount: number = 0;
|
||||
private _workCell: ICellData = new CellData();
|
||||
private _workCell2: ICellData = new CellData();
|
||||
private _cellColorResolver: CellColorResolver;
|
||||
|
||||
private _canvas: HTMLCanvasElement;
|
||||
@@ -105,6 +108,12 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
this._updateDimensions();
|
||||
this._updateCursorBlink();
|
||||
this._register(_optionsService.onOptionChange(() => this._handleOptionsChanged()));
|
||||
this._textBlinkStateManager = this._register(new TextBlinkStateManager(
|
||||
() => this._requestRedrawViewport(),
|
||||
this._coreBrowserService,
|
||||
this._optionsService
|
||||
));
|
||||
this._resetBlinkingRowState();
|
||||
|
||||
this._deviceMaxTextureSize = this._gl.getParameter(this._gl.MAX_TEXTURE_SIZE);
|
||||
|
||||
@@ -178,6 +187,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
this._updateDimensions();
|
||||
|
||||
this._model.resize(this._terminal.cols, this._terminal.rows);
|
||||
this._resetBlinkingRowState();
|
||||
|
||||
// Resize all render layers
|
||||
for (const l of this._renderLayers) {
|
||||
@@ -231,6 +241,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
this._requestRedrawViewport();
|
||||
}
|
||||
|
||||
public handleViewportVisibilityChange(isVisible: boolean): void {
|
||||
this._textBlinkStateManager.setViewportVisible(isVisible);
|
||||
}
|
||||
|
||||
public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
|
||||
for (const l of this._renderLayers) {
|
||||
l.handleSelectionChanged(this._terminal, start, end, columnSelectMode);
|
||||
@@ -323,6 +337,9 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
l.reset(this._terminal);
|
||||
}
|
||||
|
||||
this._resetBlinkingRowState();
|
||||
this._textBlinkStateManager.setNeedsBlinkInViewport(false);
|
||||
|
||||
this._cursorBlinkStateManager.value?.restartBlinkAnimation();
|
||||
this._updateCursorBlink();
|
||||
}
|
||||
@@ -420,6 +437,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
for (y = start; y <= end; y++) {
|
||||
row = y + terminal.buffer.ydisp;
|
||||
line = terminal.buffer.lines.get(row)!;
|
||||
let rowHasBlinkingCells = false;
|
||||
this._model.lineLengths[y] = 0;
|
||||
isCursorRow = cursorY === row;
|
||||
skipJoinedCheckUntilX = 0;
|
||||
@@ -477,6 +495,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
code = cell.getCode();
|
||||
i = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL;
|
||||
|
||||
if (!rowHasBlinkingCells && cell.isBlink()) {
|
||||
rowHasBlinkingCells = true;
|
||||
}
|
||||
|
||||
// Load colors/resolve overrides into work colors
|
||||
this._cellColorResolver.resolve(cell, x, row, this.dimensions.device.cell.width);
|
||||
|
||||
@@ -506,6 +528,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
if (this._textBlinkStateManager.isEnabled && !this._textBlinkStateManager.isBlinkOn && cell.isBlink()) {
|
||||
this._cellColorResolver.result.fg |= FgFlags.INVISIBLE;
|
||||
}
|
||||
|
||||
if (code !== NULL_CELL_CODE) {
|
||||
this._model.lineLengths[y] = x + 1;
|
||||
}
|
||||
@@ -552,11 +578,31 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
x--; // Go back to the previous update cell for next iteration
|
||||
}
|
||||
}
|
||||
this._setRowBlinkState(y, rowHasBlinkingCells);
|
||||
}
|
||||
if (modelUpdated) {
|
||||
this._rectangleRenderer.value!.updateBackgrounds(this._model);
|
||||
}
|
||||
this._rectangleRenderer.value!.updateCursor(this._model);
|
||||
this._updateTextBlinkState();
|
||||
}
|
||||
|
||||
private _resetBlinkingRowState(): void {
|
||||
this._rowHasBlinkingCells = new Array(this._terminal.rows).fill(false);
|
||||
this._rowHasBlinkingCellsCount = 0;
|
||||
}
|
||||
|
||||
private _setRowBlinkState(row: number, hasBlinkingCells: boolean): void {
|
||||
const previous = this._rowHasBlinkingCells[row];
|
||||
if (previous === hasBlinkingCells) {
|
||||
return;
|
||||
}
|
||||
this._rowHasBlinkingCells[row] = hasBlinkingCells;
|
||||
this._rowHasBlinkingCellsCount += hasBlinkingCells ? 1 : -1;
|
||||
}
|
||||
|
||||
private _updateTextBlinkState(): void {
|
||||
this._textBlinkStateManager.setNeedsBlinkInViewport(this._rowHasBlinkingCellsCount > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,10 +5,9 @@ const config: PlaywrightTestConfig = {
|
||||
timeout: 10000,
|
||||
projects: [
|
||||
{
|
||||
name: 'ChromeStable',
|
||||
name: 'Chromium',
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
channel: 'chrome'
|
||||
browserName: 'chromium'
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
+27
-6
@@ -16,9 +16,12 @@ if ('WebAssembly' in window) {
|
||||
import { Terminal, ITerminalOptions, type ITheme } from '@xterm/xterm';
|
||||
import { AttachAddon } from '@xterm/addon-attach';
|
||||
import { AddonImageWindow } from './components/window/addonImageWindow';
|
||||
import { AddonLigaturesWindow } from './components/window/addonLigaturesWindow';
|
||||
import { AddonProgressWindow } from './components/window/addonProgressWindow';
|
||||
import { AddonSearchWindow } from './components/window/addonSearchWindow';
|
||||
import { AddonSerializeWindow } from './components/window/addonSerializeWindow';
|
||||
import { AddonWebFontsWindow } from './components/window/addonWebFontsWindow';
|
||||
import { AddonWebLinksWindow } from './components/window/addonWebLinksWindow';
|
||||
import { AddonsWindow } from './components/window/addonsWindow';
|
||||
import { CellInspectorWindow } from './components/window/cellInspectorWindow';
|
||||
import { ControlBar } from './components/controlBar';
|
||||
@@ -213,11 +216,14 @@ if (document.location.pathname === '/test') {
|
||||
controlBar.registerWindow(new CellInspectorWindow(typedTerm, addons));
|
||||
controlBar.registerWindow(new VtWindow(typedTerm, addons));
|
||||
addonsWindow = controlBar.registerWindow(new AddonsWindow(typedTerm, addons));
|
||||
addonSearchWindow = controlBar.registerWindow(new AddonSearchWindow(typedTerm, addons), { afterId: 'addons', hidden: true, italics: true });
|
||||
controlBar.registerWindow(new AddonImageWindow(typedTerm, addons), { afterId: 'addons', hidden: true, italics: true });
|
||||
controlBar.registerWindow(new AddonLigaturesWindow(typedTerm, addons), { afterId: 'addon-image', hidden: true, italics: true });
|
||||
controlBar.registerWindow(new AddonProgressWindow(typedTerm, addons), { afterId: 'addon-ligatures', hidden: true, italics: true });
|
||||
addonSearchWindow = controlBar.registerWindow(new AddonSearchWindow(typedTerm, addons), { afterId: 'addon-progress', hidden: true, italics: true });
|
||||
controlBar.registerWindow(new AddonSerializeWindow(typedTerm, addons), { afterId: 'addon-search', hidden: true, italics: true });
|
||||
controlBar.registerWindow(new AddonImageWindow(typedTerm, addons), { afterId: 'addon-serialize', hidden: true, italics: true });
|
||||
controlBar.registerWindow(new AddonWebFontsWindow(typedTerm, addons), { afterId: 'addon-image', hidden: true, italics: true });
|
||||
addonWebglWindow = controlBar.registerWindow(new WebglWindow(typedTerm, addons), { afterId: 'addon-web-fonts', hidden: true, italics: true });
|
||||
controlBar.registerWindow(new AddonWebFontsWindow(typedTerm, addons), { afterId: 'addon-serialize', hidden: true, italics: true });
|
||||
controlBar.registerWindow(new AddonWebLinksWindow(typedTerm, addons), { afterId: 'addon-web-fonts', hidden: true, italics: true });
|
||||
addonWebglWindow = controlBar.registerWindow(new WebglWindow(typedTerm, addons), { afterId: 'addon-web-links', hidden: true, italics: true });
|
||||
controlBar.registerWindow(new TestWindow(typedTerm, addons, { disposeRecreateButtonHandler, createNewWindowButtonHandler }), { afterId: 'options' });
|
||||
actionElements = {
|
||||
findNext: addonSearchWindow.findNextInput,
|
||||
@@ -229,11 +235,14 @@ if (document.location.pathname === '/test') {
|
||||
// TODO: Most of below should be encapsulated within windows
|
||||
paddingElement = styleWindow.paddingElement;
|
||||
|
||||
controlBar.setTabVisible('addon-webgl', true);
|
||||
controlBar.setTabVisible('addon-image', !!addons.image.instance);
|
||||
controlBar.setTabVisible('addon-ligatures', !!addons.ligatures.instance);
|
||||
controlBar.setTabVisible('addon-progress', !!addons.progress.instance);
|
||||
controlBar.setTabVisible('addon-search', true);
|
||||
controlBar.setTabVisible('addon-serialize', true);
|
||||
controlBar.setTabVisible('addon-image', true);
|
||||
controlBar.setTabVisible('addon-web-fonts', true);
|
||||
controlBar.setTabVisible('addon-web-links', !!addons.webLinks.instance);
|
||||
controlBar.setTabVisible('addon-webgl', true);
|
||||
addonWebglWindow.setTextureAtlas(addons.webgl.instance!.textureAtlas!);
|
||||
addons.webgl.instance!.onChangeTextureAtlas(e => addonWebglWindow.setTextureAtlas(e));
|
||||
addons.webgl.instance!.onAddTextureAtlasCanvas(e => addonWebglWindow.appendTextureAtlas(e));
|
||||
@@ -501,6 +510,12 @@ function initAddons(term: Terminal): void {
|
||||
addons[name].instance!.onDidChangeResults(e => updateFindResults(e));
|
||||
} else if (name === 'serialize') {
|
||||
controlBar.setTabVisible('addon-serialize', true);
|
||||
} else if (name === 'ligatures') {
|
||||
controlBar.setTabVisible('addon-ligatures', true);
|
||||
} else if (name === 'progress') {
|
||||
controlBar.setTabVisible('addon-progress', true);
|
||||
} else if (name === 'webLinks') {
|
||||
controlBar.setTabVisible('addon-web-links', true);
|
||||
}
|
||||
}
|
||||
catch {
|
||||
@@ -517,6 +532,12 @@ function initAddons(term: Terminal): void {
|
||||
controlBar.setTabVisible('addon-search', false);
|
||||
} else if (name === 'serialize') {
|
||||
controlBar.setTabVisible('addon-serialize', false);
|
||||
} else if (name === 'ligatures') {
|
||||
controlBar.setTabVisible('addon-ligatures', false);
|
||||
} else if (name === 'progress') {
|
||||
controlBar.setTabVisible('addon-progress', false);
|
||||
} else if (name === 'webLinks') {
|
||||
controlBar.setTabVisible('addon-web-links', false);
|
||||
}
|
||||
addon.instance!.dispose();
|
||||
addon.instance = undefined;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import { BaseWindow } from './baseWindow';
|
||||
import type { IControlWindow } from '../controlBar';
|
||||
import type { IImageAddonOptions } from '@xterm/addon-image';
|
||||
|
||||
export class AddonImageWindow extends BaseWindow implements IControlWindow {
|
||||
public readonly id = 'addon-image';
|
||||
@@ -46,6 +47,20 @@ export class AddonImageWindow extends BaseWindow implements IControlWindow {
|
||||
this._imageOptionsTextarea.rows = 12;
|
||||
optionsLabel.appendChild(this._imageOptionsTextarea);
|
||||
container.appendChild(optionsLabel);
|
||||
|
||||
container.appendChild(document.createElement('br'));
|
||||
container.appendChild(document.createElement('br'));
|
||||
|
||||
const dl = document.createElement('dl');
|
||||
const dt = document.createElement('dt');
|
||||
dt.textContent = 'Image Test';
|
||||
dl.appendChild(dt);
|
||||
this._addDdWithButton(dl, 'image-demo1', 'snake (sixel)');
|
||||
this._addDdWithButton(dl, 'image-demo2', 'oranges (sixel)');
|
||||
this._addDdWithButton(dl, 'image-demo3', 'palette (iip)');
|
||||
container.appendChild(dl);
|
||||
|
||||
this._initImageAddonExposed();
|
||||
}
|
||||
|
||||
public get imageStorageLimitInput(): HTMLInputElement {
|
||||
@@ -59,4 +74,93 @@ export class AddonImageWindow extends BaseWindow implements IControlWindow {
|
||||
public get imageOptionsTextarea(): HTMLTextAreaElement {
|
||||
return this._imageOptionsTextarea;
|
||||
}
|
||||
|
||||
private _addDdWithButton(dl: HTMLElement, id: string, label: string): void {
|
||||
const dd = document.createElement('dd');
|
||||
const button = document.createElement('button');
|
||||
button.id = id;
|
||||
button.textContent = label;
|
||||
dd.appendChild(button);
|
||||
dl.appendChild(dd);
|
||||
}
|
||||
|
||||
private _initImageAddonExposed(): void {
|
||||
const imageAddon = this._addons.image.instance!;
|
||||
const defaultOptions: IImageAddonOptions = (imageAddon as any)._defaultOpts;
|
||||
const limitStorageElement = document.querySelector<HTMLInputElement>('#image-storagelimit')!;
|
||||
limitStorageElement.valueAsNumber = imageAddon.storageLimit;
|
||||
this._addDomListener(limitStorageElement, 'change', () => {
|
||||
try {
|
||||
imageAddon.storageLimit = limitStorageElement.valueAsNumber;
|
||||
limitStorageElement.valueAsNumber = imageAddon.storageLimit;
|
||||
console.log('changed storageLimit to', imageAddon.storageLimit);
|
||||
} catch (e) {
|
||||
limitStorageElement.valueAsNumber = imageAddon.storageLimit;
|
||||
console.log('storageLimit at', imageAddon.storageLimit);
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
const showPlaceholderElement = document.querySelector<HTMLInputElement>('#image-showplaceholder')!;
|
||||
showPlaceholderElement.checked = imageAddon.showPlaceholder;
|
||||
this._addDomListener(showPlaceholderElement, 'change', () => {
|
||||
imageAddon.showPlaceholder = showPlaceholderElement.checked;
|
||||
});
|
||||
const ctorOptionsElement = document.querySelector<HTMLTextAreaElement>('#image-options')!;
|
||||
ctorOptionsElement.value = JSON.stringify(defaultOptions, null, 2);
|
||||
|
||||
const sixelDemo = (url: string) => () => fetch(url)
|
||||
.then(resp => resp.arrayBuffer())
|
||||
.then(buffer => {
|
||||
this._terminal.write('\r\n');
|
||||
this._terminal.write(new Uint8Array(buffer));
|
||||
});
|
||||
|
||||
const iipDemo = (url: string) => () => fetch(url)
|
||||
.then(resp => resp.arrayBuffer())
|
||||
.then(buffer => {
|
||||
const data = new Uint8Array(buffer);
|
||||
let sdata = '';
|
||||
for (let i = 0; i < data.length; ++i) sdata += String.fromCharCode(data[i]);
|
||||
this._terminal.write('\r\n');
|
||||
this._terminal.write(`\x1b]1337;File=inline=1;size=${data.length}:${btoa(sdata)}\x1b\\`);
|
||||
});
|
||||
|
||||
document.getElementById('image-demo1')!.addEventListener('click',
|
||||
sixelDemo('https://raw.githubusercontent.com/saitoha/libsixel/master/images/snake.six'));
|
||||
document.getElementById('image-demo2')!.addEventListener('click',
|
||||
sixelDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/testfiles/test2.sixel'));
|
||||
document.getElementById('image-demo3')!.addEventListener('click',
|
||||
iipDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/palette.png'));
|
||||
|
||||
// demo for image retrieval API
|
||||
this._terminal.element!.addEventListener('click', (ev: MouseEvent) => {
|
||||
if (!ev.ctrlKey || !imageAddon) return;
|
||||
|
||||
// TODO...
|
||||
// if (ev.altKey) {
|
||||
// const sel = term.getSelectionPosition();
|
||||
// if (sel) {
|
||||
// addons.image.instance
|
||||
// .extractCanvasAtBufferRange(term.getSelectionPosition())
|
||||
// ?.toBlob(data => window.open(URL.createObjectURL(data), '_blank'));
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
const pos = (this._terminal as any)._core._mouseService!.getCoords(ev, (this._terminal as any)._core.screenElement!, this._terminal.cols, this._terminal.rows);
|
||||
const x = pos[0] - 1;
|
||||
const y = pos[1] - 1;
|
||||
const canvas = ev.shiftKey
|
||||
// ctrl+shift+click: get single tile
|
||||
? imageAddon.extractTileAtBufferCell(x, this._terminal.buffer.active.viewportY + y)
|
||||
// ctrl+click: get original image
|
||||
: imageAddon.getImageAtBufferCell(x, this._terminal.buffer.active.viewportY + y);
|
||||
canvas?.toBlob(data => data && window.open(URL.createObjectURL(data), '_blank'));
|
||||
});
|
||||
}
|
||||
|
||||
private _addDomListener(element: HTMLElement, type: string, handler: (...args: any[]) => any): void {
|
||||
element.addEventListener(type, handler);
|
||||
(this._terminal as any)._core._register({ dispose: () => element.removeEventListener(type, handler) });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) 2026 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { BaseWindow } from './baseWindow';
|
||||
import type { IControlWindow } from '../controlBar';
|
||||
|
||||
export class AddonLigaturesWindow extends BaseWindow implements IControlWindow {
|
||||
public readonly id = 'addon-ligatures';
|
||||
public readonly label = 'ligatures';
|
||||
|
||||
public build(container: HTMLElement): void {
|
||||
const dl = document.createElement('dl');
|
||||
const dt = document.createElement('dt');
|
||||
dt.textContent = 'Ligatures Addon';
|
||||
dl.appendChild(dt);
|
||||
|
||||
const dd = document.createElement('dd');
|
||||
const button = document.createElement('button');
|
||||
button.id = 'ligatures-test';
|
||||
button.textContent = 'Common ligatures';
|
||||
button.title = 'Write common ligatures sequences';
|
||||
button.addEventListener('click', () => this._ligaturesTest());
|
||||
dd.appendChild(button);
|
||||
dl.appendChild(dd);
|
||||
|
||||
container.appendChild(dl);
|
||||
}
|
||||
|
||||
private _ligaturesTest(): void {
|
||||
this._terminal.write([
|
||||
'',
|
||||
'-<< -< -<- <-- <--- <<- <- -> ->> --> ---> ->- >- >>-',
|
||||
'=<< =< =<= <== <=== <<= <= => =>> ==> ===> =>= >= >>=',
|
||||
'<-> <--> <---> <----> <=> <==> <===> <====> :: ::: __',
|
||||
'<~~ </ </> /> ~~> == != /= ~= <> === !== !=== =/= =!=',
|
||||
'<: := *= *+ <* <*> *> <| <|> |> <. <.> .> +* =* =: :>',
|
||||
'(* *) /* */ [| |] {| |} ++ +++ \/ /\ |- -| <!-- <!---',
|
||||
'==== ===== ====== ======= ======== =========',
|
||||
'---- ----- ------ ------- -------- ---------'
|
||||
].join('\r\n'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* Copyright (c) 2026 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { BaseWindow } from './baseWindow';
|
||||
import type { IControlWindow } from '../controlBar';
|
||||
import type { IProgressState } from '@xterm/addon-progress';
|
||||
|
||||
export class AddonProgressWindow extends BaseWindow implements IControlWindow {
|
||||
public readonly id = 'addon-progress';
|
||||
public readonly label = 'progress';
|
||||
|
||||
public build(container: HTMLElement): void {
|
||||
const dl = document.createElement('dl');
|
||||
const dt = document.createElement('dt');
|
||||
dt.textContent = 'Progress Addon';
|
||||
dl.appendChild(dt);
|
||||
|
||||
this._addDdWithButton(dl, 'progress-run', 'full set run', '');
|
||||
this._addDdWithButton(dl, 'progress-0', 'state 0: remove', '');
|
||||
this._addDdWithButton(dl, 'progress-1', 'state 1: set 20%', '');
|
||||
this._addDdWithButton(dl, 'progress-2', 'state 2: error', '');
|
||||
this._addDdWithButton(dl, 'progress-3', 'state 3: indeterminate', '');
|
||||
this._addDdWithButton(dl, 'progress-4', 'state 4: pause', '');
|
||||
|
||||
const progressDd = document.createElement('dd');
|
||||
const progressDiv = document.createElement('div');
|
||||
progressDiv.id = 'progress-progress';
|
||||
const progressPercent = document.createElement('div');
|
||||
progressPercent.id = 'progress-percent';
|
||||
const progressIndeterminate = document.createElement('div');
|
||||
progressIndeterminate.id = 'progress-indeterminate';
|
||||
progressDiv.appendChild(progressPercent);
|
||||
progressDiv.appendChild(progressIndeterminate);
|
||||
progressDd.appendChild(progressDiv);
|
||||
dl.appendChild(progressDd);
|
||||
|
||||
const stateDd = document.createElement('dd');
|
||||
const stateDiv = document.createElement('div');
|
||||
stateDiv.id = 'progress-state';
|
||||
stateDiv.textContent = 'State:';
|
||||
stateDd.appendChild(stateDiv);
|
||||
dl.appendChild(stateDd);
|
||||
|
||||
container.appendChild(dl);
|
||||
|
||||
this._initProgress();
|
||||
this._addProgressStyles(container);
|
||||
}
|
||||
|
||||
private _addDdWithButton(dl: HTMLElement, id: string, label: string, title: string): void {
|
||||
const dd = document.createElement('dd');
|
||||
const button = document.createElement('button');
|
||||
button.id = id;
|
||||
button.textContent = label;
|
||||
if (title) {
|
||||
button.title = title;
|
||||
}
|
||||
dd.appendChild(button);
|
||||
dl.appendChild(dd);
|
||||
}
|
||||
|
||||
private _initProgress(): void {
|
||||
const states = { 0: 'remove', 1: 'set', 2: 'error', 3: 'indeterminate', 4: 'pause' };
|
||||
const colors = { 0: '', 1: 'green', 2: 'red', 3: '', 4: 'yellow' };
|
||||
|
||||
function progressHandler({ state, value }: IProgressState): void {
|
||||
// Simulate windows taskbar hack by windows terminal:
|
||||
// Since the taskbar has no means to indicate error/pause state other than by coloring
|
||||
// the current progress, we move 0 to 10% and distribute higher values in the remaining 90 %
|
||||
// NOTE: This is most likely not what you want to do for other progress indicators,
|
||||
// that have a proper visual state for error/paused.
|
||||
value = Math.min(10 + value * 0.9, 100);
|
||||
document.getElementById('progress-percent')!.style.width = `${value}%`;
|
||||
document.getElementById('progress-percent')!.style.backgroundColor = colors[state];
|
||||
document.getElementById('progress-state')!.innerText = `State: ${states[state]}`;
|
||||
|
||||
document.getElementById('progress-percent')!.style.display = state === 3 ? 'none' : 'block';
|
||||
document.getElementById('progress-indeterminate')!.style.display = state === 3 ? 'block' : 'none';
|
||||
}
|
||||
|
||||
const progressAddon = this._addons.progress.instance!;
|
||||
progressAddon.onChange(progressHandler);
|
||||
|
||||
const initialProgress = progressAddon.progress;
|
||||
progressHandler(initialProgress);
|
||||
|
||||
document.getElementById('progress-run')!.addEventListener('click', async () => {
|
||||
this._terminal.write('\x1b]9;4;0\x1b\\');
|
||||
for (let i = 0; i <= 100; i += 5) {
|
||||
this._terminal.write(`\x1b]9;4;1;${i}\x1b\\`);
|
||||
await new Promise(res => setTimeout(res, 200));
|
||||
}
|
||||
});
|
||||
document.getElementById('progress-0')!.addEventListener('click', () => this._terminal.write('\x1b]9;4;0\x1b\\'));
|
||||
document.getElementById('progress-1')!.addEventListener('click', () => this._terminal.write('\x1b]9;4;1;20\x1b\\'));
|
||||
document.getElementById('progress-2')!.addEventListener('click', () => this._terminal.write('\x1b]9;4;2\x1b\\'));
|
||||
document.getElementById('progress-3')!.addEventListener('click', () => this._terminal.write('\x1b]9;4;3\x1b\\'));
|
||||
document.getElementById('progress-4')!.addEventListener('click', () => this._terminal.write('\x1b]9;4;4\x1b\\'));
|
||||
}
|
||||
|
||||
private _addProgressStyles(container: HTMLElement): void {
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
#progress-progress {
|
||||
border: 1px solid black;
|
||||
height: 10px;
|
||||
}
|
||||
#progress-percent {
|
||||
height: 100%;
|
||||
}
|
||||
#progress-indeterminate {
|
||||
display: none;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
#progress-indeterminate:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0px;
|
||||
width: 50px;
|
||||
height: 10px;
|
||||
background: blue;
|
||||
animation: ballbns 1s ease-in-out infinite alternate;
|
||||
}
|
||||
@keyframes ballbns {
|
||||
0% { left: 0; transform: translateX(0%); }
|
||||
100% { left: 100%; transform: translateX(-100%); }
|
||||
}
|
||||
`;
|
||||
container.appendChild(style);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright (c) 2026 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { BaseWindow } from './baseWindow';
|
||||
import type { IControlWindow } from '../controlBar';
|
||||
|
||||
export class AddonWebLinksWindow extends BaseWindow implements IControlWindow {
|
||||
public readonly id = 'addon-web-links';
|
||||
public readonly label = 'web-links';
|
||||
|
||||
public build(container: HTMLElement): void {
|
||||
const dl = document.createElement('dl');
|
||||
const dt = document.createElement('dt');
|
||||
dt.textContent = 'Weblinks Addon';
|
||||
dl.appendChild(dt);
|
||||
|
||||
const dd = document.createElement('dd');
|
||||
const button = document.createElement('button');
|
||||
button.id = 'weblinks-test';
|
||||
button.textContent = 'Test URLs';
|
||||
button.title = 'Various url conditions from demo data, hover&click to test';
|
||||
button.addEventListener('click', () => this._testWeblinks());
|
||||
dd.appendChild(button);
|
||||
dl.appendChild(dd);
|
||||
|
||||
container.appendChild(dl);
|
||||
}
|
||||
|
||||
private _testWeblinks(): void {
|
||||
const linkExamples = `
|
||||
aaa http://example.com aaa http://example.com aaa
|
||||
\uFFE5\uFFE5\uFFE5 http://example.com aaa http://example.com aaa
|
||||
aaa http://example.com \uFFE5\uFFE5\uFFE5 http://example.com aaa
|
||||
\uFFE5\uFFE5\uFFE5 http://example.com \uFFE5\uFFE5\uFFE5 http://example.com aaa
|
||||
aaa https://ko.wikipedia.org/wiki/\uC704\uD0A4\uBC31\uACFC:\uB300\uBB38 aaa https://ko.wikipedia.org/wiki/\uC704\uD0A4\uBC31\uACFC:\uB300\uBB38 aaa
|
||||
\uFFE5\uFFE5\uFFE5 https://ko.wikipedia.org/wiki/\uC704\uD0A4\uBC31\uACFC:\uB300\uBB38 aaa https://ko.wikipedia.org/wiki/\uC704\uD0A4\uBC31\uACFC:\uB300\uBB38 \uFFE5\uFFE5\uFFE5
|
||||
aaa http://test:password@example.com/some_path aaa
|
||||
brackets enclosed:
|
||||
aaa [http://example.de] aaa
|
||||
aaa (http://example.de) aaa
|
||||
aaa <http://example.de> aaa
|
||||
aaa {http://example.de} aaa
|
||||
ipv6 https://[::1]/with/some?vars=and&a#hash aaa
|
||||
stop at final '.': This is a sentence with an url to http://example.com.
|
||||
stop at final '?': Is this the right url http://example.com/?
|
||||
stop at final '?': Maybe this one http://example.com/with?arguments=false?
|
||||
`;
|
||||
this._terminal.write(linkExamples.split('\n').join('\r\n'));
|
||||
}
|
||||
}
|
||||
@@ -109,7 +109,6 @@ export class OptionsWindow extends BaseWindow implements IControlWindow {
|
||||
|
||||
public initOptions(addDomListener: (el: HTMLElement, type: string, handler: (...args: any[]) => any) => void): void {
|
||||
const blacklistedOptions = [
|
||||
'cancelEvents',
|
||||
'convertEol',
|
||||
'termName',
|
||||
'cols', 'rows',
|
||||
|
||||
@@ -8,8 +8,6 @@ import type { IControlWindow } from '../controlBar';
|
||||
import { BaseWindow } from './baseWindow';
|
||||
import type { IDisposable, Terminal } from '@xterm/xterm';
|
||||
import type { AddonCollection } from 'types';
|
||||
import type { IProgressState } from '@xterm/addon-progress';
|
||||
import type { IImageAddonOptions } from '@xterm/addon-image';
|
||||
|
||||
export class TestWindow extends BaseWindow implements IControlWindow {
|
||||
public readonly id = 'test';
|
||||
@@ -66,61 +64,13 @@ export class TestWindow extends BaseWindow implements IControlWindow {
|
||||
this._addDdWithButton(dl, 'add-overview-ruler', 'Add Overview Ruler', 'Add an overview ruler to the terminal', () => addOverviewRuler(this._terminal));
|
||||
this._addDdWithButton(dl, 'decoration-stress-test', 'Stress Test', 'Toggle between adding and removing a decoration to each line', () => decorationStressTest(this._terminal));
|
||||
|
||||
// Ligatures Addon section
|
||||
this._addDt(dl, 'Ligatures Addon');
|
||||
this._addDdWithButton(dl, 'ligatures-test', 'Common ligatures', 'Write common ligatures sequences', () => ligaturesTest(this._terminal));
|
||||
|
||||
// Weblinks Addon section
|
||||
this._addDt(dl, 'Weblinks Addon');
|
||||
this._addDdWithButton(dl, 'weblinks-test', 'Test URLs', 'Various url conditions from demo data, hover&click to test', () => testWeblinks(this._terminal));
|
||||
|
||||
// Image Test section
|
||||
this._addDt(dl, 'Image Test');
|
||||
this._addDdWithButton(dl, 'image-demo1', 'snake (sixel)', '');
|
||||
this._addDdWithButton(dl, 'image-demo2', 'oranges (sixel)', '');
|
||||
this._addDdWithButton(dl, 'image-demo3', 'palette (iip)', '');
|
||||
|
||||
// Events Test section
|
||||
this._addDt(dl, 'Events Test');
|
||||
this._addDdWithButton(dl, 'event-focus', 'focus', '', () => this._terminal.focus());
|
||||
this._addDdWithButton(dl, 'event-blur', 'blur', '', () => this._terminal.blur());
|
||||
|
||||
// Progress Addon section
|
||||
this._addDt(dl, 'Progress Addon');
|
||||
this._addDdWithButton(dl, 'progress-run', 'full set run', '');
|
||||
this._addDdWithButton(dl, 'progress-0', 'state 0: remove', '');
|
||||
this._addDdWithButton(dl, 'progress-1', 'state 1: set 20%', '');
|
||||
this._addDdWithButton(dl, 'progress-2', 'state 2: error', '');
|
||||
this._addDdWithButton(dl, 'progress-3', 'state 3: indeterminate', '');
|
||||
this._addDdWithButton(dl, 'progress-4', 'state 4: pause', '');
|
||||
|
||||
// Progress bar
|
||||
const progressDd = document.createElement('dd');
|
||||
const progressDiv = document.createElement('div');
|
||||
progressDiv.id = 'progress-progress';
|
||||
const progressPercent = document.createElement('div');
|
||||
progressPercent.id = 'progress-percent';
|
||||
const progressIndeterminate = document.createElement('div');
|
||||
progressIndeterminate.id = 'progress-indeterminate';
|
||||
progressDiv.appendChild(progressPercent);
|
||||
progressDiv.appendChild(progressIndeterminate);
|
||||
progressDd.appendChild(progressDiv);
|
||||
dl.appendChild(progressDd);
|
||||
|
||||
// Progress state
|
||||
const stateDd = document.createElement('dd');
|
||||
const stateDiv = document.createElement('div');
|
||||
stateDiv.id = 'progress-state';
|
||||
stateDiv.textContent = 'State:';
|
||||
stateDd.appendChild(stateDiv);
|
||||
dl.appendChild(stateDd);
|
||||
|
||||
wrapper.appendChild(dl);
|
||||
container.appendChild(wrapper);
|
||||
|
||||
initProgress(this._terminal, this._addons);
|
||||
this._addProgressStyles(container);
|
||||
initImageAddonExposed(this._terminal, this._addons);
|
||||
}
|
||||
|
||||
private _addDt(dl: HTMLElement, text: string): void {
|
||||
@@ -161,38 +111,6 @@ export class TestWindow extends BaseWindow implements IControlWindow {
|
||||
dl.appendChild(dd);
|
||||
}
|
||||
|
||||
private _addProgressStyles(container: HTMLElement): void {
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
#progress-progress {
|
||||
border: 1px solid black;
|
||||
height: 10px;
|
||||
}
|
||||
#progress-percent {
|
||||
height: 100%;
|
||||
}
|
||||
#progress-indeterminate {
|
||||
display: none;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
#progress-indeterminate:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0px;
|
||||
width: 50px;
|
||||
height: 10px;
|
||||
background: blue;
|
||||
animation: ballbns 1s ease-in-out infinite alternate;
|
||||
}
|
||||
@keyframes ballbns {
|
||||
0% { left: 0; transform: translateX(0%); }
|
||||
100% { left: 100%; transform: translateX(-100%); }
|
||||
}
|
||||
`;
|
||||
container.appendChild(style);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -753,42 +671,6 @@ Test BG-colored Erase (BCE):
|
||||
term.write(data.split('\n').join('\r\n'));
|
||||
}
|
||||
|
||||
function ligaturesTest(term: Terminal): void {
|
||||
term.write([
|
||||
'',
|
||||
'-<< -< -<- <-- <--- <<- <- -> ->> --> ---> ->- >- >>-',
|
||||
'=<< =< =<= <== <=== <<= <= => =>> ==> ===> =>= >= >>=',
|
||||
'<-> <--> <---> <----> <=> <==> <===> <====> :: ::: __',
|
||||
'<~~ </ </> /> ~~> == != /= ~= <> === !== !=== =/= =!=',
|
||||
'<: := *= *+ <* <*> *> <| <|> |> <. <.> .> +* =* =: :>',
|
||||
'(* *) /* */ [| |] {| |} ++ +++ \/ /\ |- -| <!-- <!---',
|
||||
'==== ===== ====== ======= ======== =========',
|
||||
'---- ----- ------ ------- -------- ---------'
|
||||
].join('\r\n'));
|
||||
}
|
||||
|
||||
function testWeblinks(term: Terminal): void {
|
||||
const linkExamples = `
|
||||
aaa http://example.com aaa http://example.com aaa
|
||||
¥¥¥ http://example.com aaa http://example.com aaa
|
||||
aaa http://example.com ¥¥¥ http://example.com aaa
|
||||
¥¥¥ http://example.com ¥¥¥ http://example.com aaa
|
||||
aaa https://ko.wikipedia.org/wiki/위키백과:대문 aaa https://ko.wikipedia.org/wiki/위키백과:대문 aaa
|
||||
¥¥¥ https://ko.wikipedia.org/wiki/위키백과:대문 aaa https://ko.wikipedia.org/wiki/위키백과:대문 ¥¥¥
|
||||
aaa http://test:password@example.com/some_path aaa
|
||||
brackets enclosed:
|
||||
aaa [http://example.de] aaa
|
||||
aaa (http://example.de) aaa
|
||||
aaa <http://example.de> aaa
|
||||
aaa {http://example.de} aaa
|
||||
ipv6 https://[::1]/with/some?vars=and&a#hash aaa
|
||||
stop at final '.': This is a sentence with an url to http://example.com.
|
||||
stop at final '?': Is this the right url http://example.com/?
|
||||
stop at final '?': Maybe this one http://example.com/with?arguments=false?
|
||||
`;
|
||||
term.write(linkExamples.split('\n').join('\r\n'));
|
||||
}
|
||||
|
||||
function loadTest(term: Terminal, addons: AddonCollection): void {
|
||||
const rendererName = addons.webgl.instance ? 'webgl' : 'dom';
|
||||
const testData = [];
|
||||
@@ -912,123 +794,3 @@ function decorationStressTest(term: Terminal): void {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function initProgress(term: Terminal, addons: AddonCollection): void {
|
||||
const STATES = { 0: 'remove', 1: 'set', 2: 'error', 3: 'indeterminate', 4: 'pause' };
|
||||
const COLORS = { 0: '', 1: 'green', 2: 'red', 3: '', 4: 'yellow' };
|
||||
|
||||
function progressHandler({ state, value }: IProgressState): void {
|
||||
// Simulate windows taskbar hack by windows terminal:
|
||||
// Since the taskbar has no means to indicate error/pause state other than by coloring
|
||||
// the current progress, we move 0 to 10% and distribute higher values in the remaining 90 %
|
||||
// NOTE: This is most likely not what you want to do for other progress indicators,
|
||||
// that have a proper visual state for error/paused.
|
||||
value = Math.min(10 + value * 0.9, 100);
|
||||
document.getElementById('progress-percent')!.style.width = `${value}%`;
|
||||
document.getElementById('progress-percent')!.style.backgroundColor = COLORS[state];
|
||||
document.getElementById('progress-state')!.innerText = `State: ${STATES[state]}`;
|
||||
|
||||
document.getElementById('progress-percent')!.style.display = state === 3 ? 'none' : 'block';
|
||||
document.getElementById('progress-indeterminate')!.style.display = state === 3 ? 'block' : 'none';
|
||||
}
|
||||
|
||||
const progressAddon = addons.progress.instance!;
|
||||
progressAddon.onChange(progressHandler);
|
||||
|
||||
// apply initial state once to make it visible on page load
|
||||
const initialProgress = progressAddon.progress;
|
||||
progressHandler(initialProgress);
|
||||
|
||||
document.getElementById('progress-run')!.addEventListener('click', async () => {
|
||||
term.write('\x1b]9;4;0\x1b\\');
|
||||
for (let i = 0; i <= 100; i += 5) {
|
||||
term.write(`\x1b]9;4;1;${i}\x1b\\`);
|
||||
await new Promise(res => setTimeout(res, 200));
|
||||
}
|
||||
});
|
||||
document.getElementById('progress-0')!.addEventListener('click', () => term.write('\x1b]9;4;0\x1b\\'));
|
||||
document.getElementById('progress-1')!.addEventListener('click', () => term.write('\x1b]9;4;1;20\x1b\\'));
|
||||
document.getElementById('progress-2')!.addEventListener('click', () => term.write('\x1b]9;4;2\x1b\\'));
|
||||
document.getElementById('progress-3')!.addEventListener('click', () => term.write('\x1b]9;4;3\x1b\\'));
|
||||
document.getElementById('progress-4')!.addEventListener('click', () => term.write('\x1b]9;4;4\x1b\\'));
|
||||
}
|
||||
|
||||
function initImageAddonExposed(term: Terminal, addons: AddonCollection): void {
|
||||
const imageAddon = addons.image.instance!;
|
||||
const DEFAULT_OPTIONS: IImageAddonOptions = (imageAddon as any)._defaultOpts;
|
||||
const limitStorageElement = document.querySelector<HTMLInputElement>('#image-storagelimit')!;
|
||||
limitStorageElement.valueAsNumber = imageAddon.storageLimit;
|
||||
addDomListener(term, limitStorageElement, 'change', () => {
|
||||
try {
|
||||
imageAddon.storageLimit = limitStorageElement.valueAsNumber;
|
||||
limitStorageElement.valueAsNumber = imageAddon.storageLimit;
|
||||
console.log('changed storageLimit to', imageAddon.storageLimit);
|
||||
} catch (e) {
|
||||
limitStorageElement.valueAsNumber = imageAddon.storageLimit;
|
||||
console.log('storageLimit at', imageAddon.storageLimit);
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
const showPlaceholderElement = document.querySelector<HTMLInputElement>('#image-showplaceholder')!;
|
||||
showPlaceholderElement.checked = imageAddon.showPlaceholder;
|
||||
addDomListener(term, showPlaceholderElement, 'change', () => {
|
||||
imageAddon.showPlaceholder = showPlaceholderElement.checked;
|
||||
});
|
||||
const ctorOptionsElement = document.querySelector<HTMLTextAreaElement>('#image-options')!;
|
||||
ctorOptionsElement.value = JSON.stringify(DEFAULT_OPTIONS, null, 2);
|
||||
|
||||
const sixelDemo = (url: string) => () => fetch(url)
|
||||
.then(resp => resp.arrayBuffer())
|
||||
.then(buffer => {
|
||||
term.write('\r\n');
|
||||
term.write(new Uint8Array(buffer));
|
||||
});
|
||||
|
||||
const iipDemo = (url: string) => () => fetch(url)
|
||||
.then(resp => resp.arrayBuffer())
|
||||
.then(buffer => {
|
||||
const data = new Uint8Array(buffer);
|
||||
let sdata = '';
|
||||
for (let i = 0; i < data.length; ++i) sdata += String.fromCharCode(data[i]);
|
||||
term.write('\r\n');
|
||||
term.write(`\x1b]1337;File=inline=1;size=${data.length}:${btoa(sdata)}\x1b\\`);
|
||||
});
|
||||
|
||||
document.getElementById('image-demo1')!.addEventListener('click',
|
||||
sixelDemo('https://raw.githubusercontent.com/saitoha/libsixel/master/images/snake.six'));
|
||||
document.getElementById('image-demo2')!.addEventListener('click',
|
||||
sixelDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/testfiles/test2.sixel'));
|
||||
document.getElementById('image-demo3')!.addEventListener('click',
|
||||
iipDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/palette.png'));
|
||||
|
||||
// demo for image retrieval API
|
||||
term.element!.addEventListener('click', (ev: MouseEvent) => {
|
||||
if (!ev.ctrlKey || !imageAddon) return;
|
||||
|
||||
// TODO...
|
||||
// if (ev.altKey) {
|
||||
// const sel = term.getSelectionPosition();
|
||||
// if (sel) {
|
||||
// addons.image.instance
|
||||
// .extractCanvasAtBufferRange(term.getSelectionPosition())
|
||||
// ?.toBlob(data => window.open(URL.createObjectURL(data), '_blank'));
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
|
||||
const pos = (term as any)._core._mouseService!.getCoords(ev, (term as any)._core.screenElement!, term.cols, term.rows);
|
||||
const x = pos[0] - 1;
|
||||
const y = pos[1] - 1;
|
||||
const canvas = ev.shiftKey
|
||||
// ctrl+shift+click: get single tile
|
||||
? imageAddon.extractTileAtBufferCell(x, term.buffer.active.viewportY + y)
|
||||
// ctrl+click: get original image
|
||||
: imageAddon.getImageAtBufferCell(x, term.buffer.active.viewportY + y);
|
||||
canvas?.toBlob(data => data && window.open(URL.createObjectURL(data), '_blank'));
|
||||
});
|
||||
}
|
||||
|
||||
function addDomListener(term: Terminal, element: HTMLElement, type: string, handler: (...args: any[]) => any): void {
|
||||
element.addEventListener(type, handler);
|
||||
(term as any)._core._register({ dispose: () => element.removeEventListener(type, handler) });
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ body {
|
||||
padding: 4px 10px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
outline-offset: -1px;
|
||||
}
|
||||
.banner-tabs button:hover {
|
||||
background: rgba(255,255,255,0.1);
|
||||
|
||||
+1
-1
@@ -57,7 +57,7 @@
|
||||
"test-unit-coverage": "node ./bin/test_unit.js --coverage",
|
||||
"test-unit-dev": "cross-env NODE_PATH='./out' mocha",
|
||||
"test-integration": "node ./bin/test_integration.js --workers=75%",
|
||||
"test-integration-chromium": "node ./bin/test_integration.js --workers=75% \"--project=ChromeStable\"",
|
||||
"test-integration-chromium": "node ./bin/test_integration.js --workers=75% \"--project=Chromium\"",
|
||||
"test-integration-firefox": "node ./bin/test_integration.js --workers=75% \"--project=FirefoxStable\"",
|
||||
"test-integration-webkit": "node ./bin/test_integration.js --workers=75% \"--project=WebKit\"",
|
||||
"test-integration-debug": "node ./bin/test_integration.js --workers=1 --headed --timeout=30000",
|
||||
|
||||
@@ -763,11 +763,12 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
this._document!.removeEventListener('mousemove', requestedEvents.mousedrag);
|
||||
}
|
||||
}
|
||||
return this.cancel(ev);
|
||||
},
|
||||
wheel: (ev: WheelEvent) => {
|
||||
sendEvent(ev);
|
||||
return this.cancel(ev, true);
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
return false;
|
||||
},
|
||||
mousedrag: (ev: MouseEvent) => {
|
||||
// deal only with move while a button is held
|
||||
@@ -867,8 +868,6 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
if (requestedEvents.mousedrag) {
|
||||
this._document!.addEventListener('mousemove', requestedEvents.mousedrag);
|
||||
}
|
||||
|
||||
return this.cancel(ev);
|
||||
}));
|
||||
|
||||
this._register(addDisposableListener(el, 'wheel', (ev: WheelEvent) => {
|
||||
@@ -899,13 +898,17 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
self._coreBrowserService?.dpr
|
||||
);
|
||||
if (lines === 0) {
|
||||
return this.cancel(ev, true);
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Construct and send sequences
|
||||
const sequence = C0.ESC + (this.coreService.decPrivateModes.applicationCursorKeys ? 'O' : '[') + (ev.deltaY < 0 ? 'A' : 'B');
|
||||
this.coreService.triggerDataEvent(sequence, true);
|
||||
return this.cancel(ev, true);
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
}, { passive: false }));
|
||||
}
|
||||
@@ -1115,7 +1118,9 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
if (result.type === KeyboardResultType.PAGE_DOWN || result.type === KeyboardResultType.PAGE_UP) {
|
||||
const scrollCount = this.rows - 1;
|
||||
this.scrollLines(result.type === KeyboardResultType.PAGE_UP ? -scrollCount : scrollCount);
|
||||
return this.cancel(event, true);
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (result.type === KeyboardResultType.SELECT_ALL) {
|
||||
@@ -1128,7 +1133,8 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
|
||||
if (result.cancel) {
|
||||
// The event is canceled at the end already, is this necessary?
|
||||
this.cancel(event, true);
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
if (!result.key) {
|
||||
@@ -1156,16 +1162,19 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
this.textarea!.value = '';
|
||||
}
|
||||
|
||||
const wasModifierOnly = this._keyboardService.useWin32InputMode && wasModifierKeyOnlyEvent(event);
|
||||
this._onKey.fire({ key: result.key, domEvent: event });
|
||||
this._showCursor();
|
||||
this.coreService.triggerDataEvent(result.key, true);
|
||||
this.coreService.triggerDataEvent(result.key, !wasModifierOnly);
|
||||
|
||||
// Cancel events when not in screen reader mode so events don't get bubbled up and handled by
|
||||
// other listeners. When screen reader mode is enabled, we don't cancel them (unless ctrl or alt
|
||||
// is also depressed) so that the cursor textarea can be updated, which triggers the screen
|
||||
// reader to read it.
|
||||
if (!this.optionsService.rawOptions.screenReaderMode || event.altKey || event.ctrlKey) {
|
||||
return this.cancel(event, true);
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
|
||||
this._keyDownHandled = true;
|
||||
@@ -1199,7 +1208,8 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
// Handle key release for Kitty keyboard protocol
|
||||
const result = this._keyboardService.evaluateKeyUp(ev);
|
||||
if (result?.key) {
|
||||
this.coreService.triggerDataEvent(result.key, true);
|
||||
const wasModifierOnly = this._keyboardService.useWin32InputMode && wasModifierKeyOnlyEvent(ev);
|
||||
this.coreService.triggerDataEvent(result.key, !wasModifierOnly);
|
||||
}
|
||||
|
||||
this.updateCursorStyle(ev);
|
||||
@@ -1225,8 +1235,6 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.cancel(ev);
|
||||
|
||||
if (ev.charCode) {
|
||||
key = ev.charCode;
|
||||
} else if (ev.which === null || ev.which === undefined) {
|
||||
@@ -1279,8 +1287,6 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
|
||||
const text = ev.data;
|
||||
this.coreService.triggerDataEvent(text, true);
|
||||
|
||||
this.cancel(ev);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1392,15 +1398,6 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Remove cancel function and cancelEvents option
|
||||
public cancel(ev: MouseEvent | WheelEvent | KeyboardEvent | InputEvent, force?: boolean): boolean | undefined {
|
||||
if (!this.options.cancelEvents && !force) {
|
||||
return;
|
||||
}
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1410,5 +1407,10 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
function wasModifierKeyOnlyEvent(ev: KeyboardEvent): boolean {
|
||||
return ev.keyCode === 16 || // Shift
|
||||
ev.keyCode === 17 || // Ctrl
|
||||
ev.keyCode === 18; // Alt
|
||||
ev.keyCode === 18 || // Alt
|
||||
ev.keyCode === 91 || // Meta (Left)
|
||||
ev.keyCode === 92 || // Meta (Right)
|
||||
ev.keyCode === 93 || // Meta (Menu)
|
||||
ev.keyCode === 224 || // Meta (Firefox)
|
||||
ev.key === 'Meta';
|
||||
}
|
||||
|
||||
@@ -401,6 +401,42 @@ describe('Terminal', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('keyDown', () => {
|
||||
it('should not scroll down on modifier-only input in win32 input mode', async () => {
|
||||
term.options.vtExtensions = { win32InputMode: true };
|
||||
term.coreService.decPrivateModes.win32InputMode = true;
|
||||
(term as any).textarea = { value: '' };
|
||||
|
||||
await term.writeP('test\r\n'.repeat(term.rows * 3));
|
||||
const startYDisp = term.buffer.ydisp;
|
||||
term.scrollLines(-1);
|
||||
const scrolledYDisp = term.buffer.ydisp;
|
||||
assert.equal(scrolledYDisp, startYDisp - 1);
|
||||
|
||||
const evKeyDown = {
|
||||
type: 'keydown',
|
||||
key: 'Control',
|
||||
keyCode: 17,
|
||||
ctrlKey: true,
|
||||
preventDefault: () => { },
|
||||
stopPropagation: () => { }
|
||||
} as KeyboardEvent;
|
||||
|
||||
const evKeyUp = {
|
||||
type: 'keyup',
|
||||
key: 'Control',
|
||||
keyCode: 17,
|
||||
preventDefault: () => { },
|
||||
stopPropagation: () => { }
|
||||
} as KeyboardEvent;
|
||||
|
||||
term.keyDown(evKeyDown);
|
||||
assert.equal(term.buffer.ydisp, scrolledYDisp);
|
||||
(term as any)._keyUp(evKeyUp);
|
||||
assert.equal(term.buffer.ydisp, scrolledYDisp);
|
||||
});
|
||||
});
|
||||
|
||||
describe('scroll() function', () => {
|
||||
describe('when scrollback > 0', () => {
|
||||
it('should create a new line and scroll', () => {
|
||||
|
||||
@@ -198,9 +198,6 @@ export class MockTerminal implements ITerminal {
|
||||
public scrollToRow(absoluteRow: number): number {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public cancel(ev: MouseEvent | WheelEvent | KeyboardEvent | InputEvent, force?: boolean): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
public log(text: string): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
@@ -29,8 +29,6 @@ export interface ITerminal extends InternalPassthroughApis, ICoreTerminal {
|
||||
onA11yChar: IEvent<string>;
|
||||
onA11yTab: IEvent<number>;
|
||||
onWillOpen: IEvent<HTMLElement>;
|
||||
|
||||
cancel(ev: MouseEvent | WheelEvent | KeyboardEvent | InputEvent, force?: boolean): boolean | void;
|
||||
}
|
||||
|
||||
export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean;
|
||||
|
||||
@@ -8,6 +8,7 @@ import { WidthCache } from 'browser/renderer/dom/WidthCache';
|
||||
import { INVERTED_DEFAULT_COLOR, RendererConstants } from 'browser/renderer/shared/Constants';
|
||||
import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils';
|
||||
import { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRenderModel';
|
||||
import { TextBlinkStateManager } from 'browser/renderer/shared/TextBlinkStateManager';
|
||||
import { IRenderDimensions, IRenderer, IRequestRedrawEvent, ISelectionRenderModel } from 'browser/renderer/shared/Types';
|
||||
import { ICharSizeService, ICoreBrowserService, IThemeService } from 'browser/services/Services';
|
||||
import { ILinkifier2, ILinkifierEvent, ITerminal, ReadonlyColorSet } from 'browser/Types';
|
||||
@@ -45,10 +46,14 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
private _widthCache: WidthCache;
|
||||
private _selectionRenderModel: ISelectionRenderModel = createSelectionRenderModel();
|
||||
private _cursorBlinkStateManager: CursorBlinkStateManager;
|
||||
private _textBlinkStateManager: TextBlinkStateManager;
|
||||
private _rowHasBlinkingCells: boolean[] = [];
|
||||
private _rowHasBlinkingCellsCount: number = 0;
|
||||
|
||||
public dimensions: IRenderDimensions;
|
||||
|
||||
public readonly onRequestRedraw = this._register(new Emitter<IRequestRedrawEvent>()).event;
|
||||
private readonly _onRequestRedraw = this._register(new Emitter<IRequestRedrawEvent>());
|
||||
public readonly onRequestRedraw = this._onRequestRedraw.event;
|
||||
|
||||
constructor(
|
||||
private readonly _terminal: ITerminal,
|
||||
@@ -95,6 +100,11 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
this._cursorBlinkStateManager = new CursorBlinkStateManager(this._rowContainer, this._coreBrowserService);
|
||||
this._register(addDisposableListener(this._document, 'mousedown', () => this._cursorBlinkStateManager.restartBlinkAnimation()));
|
||||
this._register(toDisposable(() => this._cursorBlinkStateManager.dispose()));
|
||||
this._textBlinkStateManager = this._register(new TextBlinkStateManager(
|
||||
() => this._onRequestRedraw.fire({ start: 0, end: this._bufferService.rows - 1 }),
|
||||
this._coreBrowserService,
|
||||
this._optionsService
|
||||
));
|
||||
|
||||
this._register(toDisposable(() => {
|
||||
this._element.classList.remove(TERMINAL_CLASS_PREFIX + this._terminalClass);
|
||||
@@ -193,6 +203,9 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
`}` +
|
||||
`${this._terminalSelector} span.${RowCss.ITALIC_CLASS} {` +
|
||||
` font-style: italic;` +
|
||||
`}` +
|
||||
`${this._terminalSelector} span.${RowCss.BLINK_HIDDEN_CLASS} {` +
|
||||
` visibility: hidden;` +
|
||||
`}`;
|
||||
// Blink animation
|
||||
const blinkAnimationUnderlineId = `blink_underline_${this._terminalClass}`;
|
||||
@@ -318,10 +331,14 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
const row = this._document.createElement('div');
|
||||
this._rowContainer.appendChild(row);
|
||||
this._rowElements.push(row);
|
||||
this._rowHasBlinkingCells.push(false);
|
||||
}
|
||||
// Remove excess elements
|
||||
while (this._rowElements.length > rows) {
|
||||
this._rowContainer.removeChild(this._rowElements.pop()!);
|
||||
if (this._rowHasBlinkingCells.pop()) {
|
||||
this._rowHasBlinkingCellsCount--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,6 +366,10 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
this.renderRows(this._bufferService.buffer.y, this._bufferService.buffer.y);
|
||||
}
|
||||
|
||||
public handleViewportVisibilityChange(isVisible: boolean): void {
|
||||
this._textBlinkStateManager.setViewportVisible(isVisible);
|
||||
}
|
||||
|
||||
public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
|
||||
// Remove all selections
|
||||
this._selectionContainer.replaceChildren();
|
||||
@@ -450,6 +471,11 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
*/
|
||||
e.replaceChildren();
|
||||
}
|
||||
if (this._rowHasBlinkingCellsCount > 0) {
|
||||
this._rowHasBlinkingCells.fill(false);
|
||||
this._rowHasBlinkingCellsCount = 0;
|
||||
this._textBlinkStateManager.setNeedsBlinkInViewport(false);
|
||||
}
|
||||
}
|
||||
|
||||
public renderRows(start: number, end: number): void {
|
||||
@@ -459,6 +485,7 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
const cursorBlink = this._coreService.decPrivateModes.cursorBlink ?? this._optionsService.rawOptions.cursorBlink;
|
||||
const cursorStyle = this._coreService.decPrivateModes.cursorStyle ?? this._optionsService.rawOptions.cursorStyle;
|
||||
const cursorInactiveStyle = this._optionsService.rawOptions.cursorInactiveStyle;
|
||||
const rowInfo = { hasBlinkingCells: false };
|
||||
|
||||
for (let y = start; y <= end; y++) {
|
||||
const row = y + buffer.ydisp;
|
||||
@@ -476,13 +503,17 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
cursorInactiveStyle,
|
||||
cursorX,
|
||||
cursorBlink,
|
||||
this._textBlinkStateManager.isBlinkOn,
|
||||
this.dimensions.css.cell.width,
|
||||
this._widthCache,
|
||||
-1,
|
||||
-1
|
||||
-1,
|
||||
rowInfo
|
||||
)
|
||||
);
|
||||
this._setRowBlinkState(y, rowInfo.hasBlinkingCells);
|
||||
}
|
||||
this._updateTextBlinkState();
|
||||
}
|
||||
|
||||
private get _terminalSelector(): string {
|
||||
@@ -527,6 +558,7 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
const cursorBlink = this._optionsService.rawOptions.cursorBlink;
|
||||
const cursorStyle = this._optionsService.rawOptions.cursorStyle;
|
||||
const cursorInactiveStyle = this._optionsService.rawOptions.cursorInactiveStyle;
|
||||
const rowInfo = { hasBlinkingCells: false };
|
||||
|
||||
// refresh rows within link range
|
||||
for (let i = y; i <= y2; ++i) {
|
||||
@@ -545,13 +577,30 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
cursorInactiveStyle,
|
||||
cursorX,
|
||||
cursorBlink,
|
||||
this._textBlinkStateManager.isBlinkOn,
|
||||
this.dimensions.css.cell.width,
|
||||
this._widthCache,
|
||||
enabled ? (i === y ? x : 0) : -1,
|
||||
enabled ? ((i === y2 ? x2 : cols) - 1) : -1
|
||||
enabled ? ((i === y2 ? x2 : cols) - 1) : -1,
|
||||
rowInfo
|
||||
)
|
||||
);
|
||||
this._setRowBlinkState(i, rowInfo.hasBlinkingCells);
|
||||
}
|
||||
this._updateTextBlinkState();
|
||||
}
|
||||
|
||||
private _setRowBlinkState(row: number, hasBlinkingCells: boolean): void {
|
||||
const previous = this._rowHasBlinkingCells[row];
|
||||
if (previous === hasBlinkingCells) {
|
||||
return;
|
||||
}
|
||||
this._rowHasBlinkingCells[row] = hasBlinkingCells;
|
||||
this._rowHasBlinkingCellsCount += hasBlinkingCells ? 1 : -1;
|
||||
}
|
||||
|
||||
private _updateTextBlinkState(): void {
|
||||
this._textBlinkStateManager.setNeedsBlinkInViewport(this._rowHasBlinkingCellsCount > 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ describe('DomRendererRowFactory', () => {
|
||||
|
||||
describe('createRow', () => {
|
||||
it('should not create anything for an empty row', () => {
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
''
|
||||
);
|
||||
@@ -51,7 +51,7 @@ describe('DomRendererRowFactory', () => {
|
||||
lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, '語', 2, '語'.charCodeAt(0)]));
|
||||
// There should be no element for the following "empty" cell
|
||||
lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, '', 0, 0]));
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span>語</span>'
|
||||
);
|
||||
@@ -59,7 +59,7 @@ describe('DomRendererRowFactory', () => {
|
||||
|
||||
it('should add class for cursor and cursor style', () => {
|
||||
for (const style of ['block', 'bar', 'underline']) {
|
||||
const spans = rowFactory.createRow(lineData, 0, true, style, undefined, 0, false, 5, widthCache, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, true, style, undefined, 0, false, true, 5, widthCache, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
`<span class="xterm-cursor xterm-cursor-${style}"> </span>`
|
||||
);
|
||||
@@ -67,7 +67,7 @@ describe('DomRendererRowFactory', () => {
|
||||
});
|
||||
|
||||
it('should add class for cursor blink', () => {
|
||||
const spans = rowFactory.createRow(lineData, 0, true, 'block', undefined, 0, true, 5, widthCache, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, true, 'block', undefined, 0, true, true, 5, widthCache, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
`<span class="xterm-cursor xterm-cursor-blink xterm-cursor-block"> </span>`
|
||||
);
|
||||
@@ -86,7 +86,7 @@ describe('DomRendererRowFactory', () => {
|
||||
new MockThemeService()
|
||||
);
|
||||
for (const inactiveStyle of ['outline', 'block', 'bar', 'underline', 'none']){
|
||||
const spans = rowFactory.createRow(lineData, 0, true, 'block', inactiveStyle, 0, false, 5, widthCache, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, true, 'block', inactiveStyle, 0, false, true, 5, widthCache, -1, -1);
|
||||
if (inactiveStyle === 'none') {
|
||||
assert.equal(extractHtml(spans),
|
||||
`<span class="xterm-cursor"> </span>`);
|
||||
@@ -109,7 +109,7 @@ describe('DomRendererRowFactory', () => {
|
||||
new MockDecorationService(),
|
||||
new MockThemeService()
|
||||
);
|
||||
const spans = rowFactory.createRow(lineData, 0, true, 'block', undefined, 0, false, 5, widthCache, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, true, 'block', undefined, 0, false, true, 5, widthCache, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
`<span> </span>`
|
||||
);
|
||||
@@ -120,7 +120,7 @@ describe('DomRendererRowFactory', () => {
|
||||
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
|
||||
cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.BOLD;
|
||||
lineData.setCell(0, cell);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span class="xterm-bold">a</span>'
|
||||
);
|
||||
@@ -130,7 +130,7 @@ describe('DomRendererRowFactory', () => {
|
||||
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
|
||||
cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.ITALIC;
|
||||
lineData.setCell(0, cell);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span class="xterm-italic">a</span>'
|
||||
);
|
||||
@@ -140,7 +140,7 @@ describe('DomRendererRowFactory', () => {
|
||||
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
|
||||
cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.DIM;
|
||||
lineData.setCell(0, cell);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span class="xterm-dim">a</span>'
|
||||
);
|
||||
@@ -153,7 +153,7 @@ describe('DomRendererRowFactory', () => {
|
||||
cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.HAS_EXTENDED;
|
||||
cell.extended.underlineStyle = UnderlineStyle.SINGLE;
|
||||
lineData.setCell(0, cell);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span class="xterm-underline-1">a</span>'
|
||||
);
|
||||
@@ -164,7 +164,7 @@ describe('DomRendererRowFactory', () => {
|
||||
cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.HAS_EXTENDED;
|
||||
cell.extended.underlineStyle = UnderlineStyle.DOUBLE;
|
||||
lineData.setCell(0, cell);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span class="xterm-underline-2">a</span>'
|
||||
);
|
||||
@@ -175,7 +175,7 @@ describe('DomRendererRowFactory', () => {
|
||||
cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.HAS_EXTENDED;
|
||||
cell.extended.underlineStyle = UnderlineStyle.CURLY;
|
||||
lineData.setCell(0, cell);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span class="xterm-underline-3">a</span>'
|
||||
);
|
||||
@@ -186,7 +186,7 @@ describe('DomRendererRowFactory', () => {
|
||||
cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.HAS_EXTENDED;
|
||||
cell.extended.underlineStyle = UnderlineStyle.DOTTED;
|
||||
lineData.setCell(0, cell);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span class="xterm-underline-4">a</span>'
|
||||
);
|
||||
@@ -197,7 +197,7 @@ describe('DomRendererRowFactory', () => {
|
||||
cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.HAS_EXTENDED;
|
||||
cell.extended.underlineStyle = UnderlineStyle.DASHED;
|
||||
lineData.setCell(0, cell);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span class="xterm-underline-5">a</span>'
|
||||
);
|
||||
@@ -208,7 +208,7 @@ describe('DomRendererRowFactory', () => {
|
||||
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
|
||||
cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.OVERLINE;
|
||||
lineData.setCell(0, cell);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span class="xterm-overline">a</span>'
|
||||
);
|
||||
@@ -218,12 +218,28 @@ describe('DomRendererRowFactory', () => {
|
||||
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
|
||||
cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.STRIKETHROUGH;
|
||||
lineData.setCell(0, cell);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span class="xterm-strikethrough">a</span>'
|
||||
);
|
||||
});
|
||||
|
||||
it('should hide blinking text when blink is off', () => {
|
||||
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
|
||||
cell.fg = DEFAULT_ATTR_DATA.fg | FgFlags.BLINK | FgFlags.UNDERLINE;
|
||||
cell.bg = DEFAULT_ATTR_DATA.bg | BgFlags.HAS_EXTENDED;
|
||||
cell.extended.underlineStyle = UnderlineStyle.SINGLE;
|
||||
lineData.setCell(0, cell);
|
||||
const onSpans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);
|
||||
assert.equal(extractHtml(onSpans),
|
||||
'<span class="xterm-underline-1">a</span>'
|
||||
);
|
||||
const offSpans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, false, 5, widthCache, -1, -1);
|
||||
assert.equal(extractHtml(offSpans),
|
||||
'<span class="xterm-blink-hidden xterm-underline-1">a</span>'
|
||||
);
|
||||
});
|
||||
|
||||
it('should add classes for 256 foreground colors', () => {
|
||||
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
|
||||
cell.fg |= Attributes.CM_P256;
|
||||
@@ -231,7 +247,7 @@ describe('DomRendererRowFactory', () => {
|
||||
cell.fg &= ~Attributes.PCOLOR_MASK;
|
||||
cell.fg |= i;
|
||||
lineData.setCell(0, cell);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
`<span class="xterm-fg-${i}">a</span>`
|
||||
);
|
||||
@@ -245,7 +261,7 @@ describe('DomRendererRowFactory', () => {
|
||||
cell.bg &= ~Attributes.PCOLOR_MASK;
|
||||
cell.bg |= i;
|
||||
lineData.setCell(0, cell);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
`<span class="xterm-bg-${i}">a</span>`
|
||||
);
|
||||
@@ -257,7 +273,7 @@ describe('DomRendererRowFactory', () => {
|
||||
cell.fg |= Attributes.CM_P16 | 2 | FgFlags.INVERSE;
|
||||
cell.bg |= Attributes.CM_P16 | 1;
|
||||
lineData.setCell(0, cell);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span class="xterm-bg-2 xterm-fg-1">a</span>'
|
||||
);
|
||||
@@ -268,7 +284,7 @@ describe('DomRendererRowFactory', () => {
|
||||
cell.fg |= FgFlags.INVERSE;
|
||||
cell.bg |= Attributes.CM_P16 | 1;
|
||||
lineData.setCell(0, cell);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span class="xterm-bg-257 xterm-fg-1">a</span>'
|
||||
);
|
||||
@@ -278,7 +294,7 @@ describe('DomRendererRowFactory', () => {
|
||||
const cell = CellData.fromCharData([0, 'a', 1, 'a'.charCodeAt(0)]);
|
||||
cell.fg |= Attributes.CM_P16 | 1 | FgFlags.INVERSE;
|
||||
lineData.setCell(0, cell);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span class="xterm-bg-1 xterm-fg-257">a</span>'
|
||||
);
|
||||
@@ -291,7 +307,7 @@ describe('DomRendererRowFactory', () => {
|
||||
cell.fg &= ~Attributes.PCOLOR_MASK;
|
||||
cell.fg |= i;
|
||||
lineData.setCell(0, cell);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
`<span class="xterm-bold xterm-fg-${i + 8}">a</span>`
|
||||
);
|
||||
@@ -303,7 +319,7 @@ describe('DomRendererRowFactory', () => {
|
||||
cell.fg |= Attributes.CM_RGB | 1 << 16 | 2 << 8 | 3;
|
||||
cell.bg |= Attributes.CM_RGB | 4 << 16 | 5 << 8 | 6;
|
||||
lineData.setCell(0, cell);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span style="background-color:#040506;color:#010203;">a</span>'
|
||||
);
|
||||
@@ -314,7 +330,7 @@ describe('DomRendererRowFactory', () => {
|
||||
cell.fg |= Attributes.CM_RGB | 1 << 16 | 2 << 8 | 3 | FgFlags.INVERSE;
|
||||
cell.bg |= Attributes.CM_RGB | 4 << 16 | 5 << 8 | 6;
|
||||
lineData.setCell(0, cell);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span style="background-color:#010203;color:#040506;">a</span>'
|
||||
);
|
||||
@@ -326,7 +342,7 @@ describe('DomRendererRowFactory', () => {
|
||||
lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)]));
|
||||
lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)]));
|
||||
rowFactory.handleSelectionChanged([1, 0], [2, 0], false);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span>a</span><span style="background-color:#ff0000;" class="xterm-decoration-top">b</span>'
|
||||
);
|
||||
@@ -334,7 +350,7 @@ describe('DomRendererRowFactory', () => {
|
||||
it('should force whitespace cells to be rendered above the background', () => {
|
||||
lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)]));
|
||||
rowFactory.handleSelectionChanged([0, 0], [2, 0], false);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span style="background-color:#ff0000;" class="xterm-decoration-top"> a</span>'
|
||||
);
|
||||
@@ -351,7 +367,7 @@ describe('DomRendererRowFactory', () => {
|
||||
});
|
||||
|
||||
it('should not create anything for an empty row', () => {
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
''
|
||||
);
|
||||
@@ -361,7 +377,7 @@ describe('DomRendererRowFactory', () => {
|
||||
lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)]));
|
||||
lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)]));
|
||||
lineData.setCell(2, CellData.fromCharData([DEFAULT_ATTR, 'c', 1, 'c'.charCodeAt(0)]));
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span>abc</span>'
|
||||
);
|
||||
@@ -372,7 +388,7 @@ describe('DomRendererRowFactory', () => {
|
||||
lineData.setCell(0, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)]));
|
||||
lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, '€', 1, '€'.charCodeAt(0)]));
|
||||
lineData.setCell(2, CellData.fromCharData([DEFAULT_ATTR, 'c', 1, 'c'.charCodeAt(0)]));
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span>a</span><span style="letter-spacing: 3px;">€</span><span>c</span>'
|
||||
);
|
||||
@@ -387,7 +403,7 @@ describe('DomRendererRowFactory', () => {
|
||||
lineData.setCell(1, aColor1);
|
||||
lineData.setCell(2, bColor2);
|
||||
lineData.setCell(3, bColor2);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span class="xterm-fg-1">aa</span><span class="xterm-fg-2">bb</span>'
|
||||
);
|
||||
@@ -399,7 +415,7 @@ describe('DomRendererRowFactory', () => {
|
||||
lineData.setCell(2, CellData.fromCharData([DEFAULT_ATTR, 'X', 1, 'X'.charCodeAt(0)]));
|
||||
lineData.setCell(3, CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)]));
|
||||
lineData.setCell(4, CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)]));
|
||||
const spans = rowFactory.createRow(lineData, 0, true, undefined, undefined, 2, false, 5, widthCache, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, true, undefined, undefined, 2, false, true, 5, widthCache, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span>aa</span><span class="xterm-cursor xterm-cursor-block">X</span><span>bb</span>'
|
||||
);
|
||||
@@ -412,7 +428,7 @@ describe('DomRendererRowFactory', () => {
|
||||
nullCell.bg = Attributes.CM_P16 | 2;
|
||||
lineData.setCell(3, nullCell);
|
||||
lineData.setCell(4, nullCell);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span> </span><span class="xterm-bg-1"> </span><span class="xterm-bg-2"> </span>'
|
||||
);
|
||||
@@ -422,23 +438,23 @@ describe('DomRendererRowFactory', () => {
|
||||
const nullCell = lineData.loadCell(0, new CellData());
|
||||
nullCell.bg = Attributes.CM_P16 | 1;
|
||||
lineData.setCell(0, nullCell);
|
||||
let spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1);
|
||||
let spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span class="xterm-bg-1"> </span>'
|
||||
);
|
||||
lineData.setCell(1, nullCell);
|
||||
spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1);
|
||||
spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span class="xterm-bg-1"> </span>'
|
||||
);
|
||||
lineData.setCell(2, nullCell);
|
||||
lineData.setCell(3, nullCell);
|
||||
spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1);
|
||||
spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span class="xterm-bg-1"> </span>'
|
||||
);
|
||||
lineData.setCell(4, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)]));
|
||||
spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1);
|
||||
spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span class="xterm-bg-1"> </span><span>a</span>'
|
||||
);
|
||||
@@ -451,7 +467,7 @@ describe('DomRendererRowFactory', () => {
|
||||
lineData.setCell(2, CellData.fromCharData([DEFAULT_ATTR, 'c', 1, 'c'.charCodeAt(0)]));
|
||||
lineData.setCell(3, CellData.fromCharData([DEFAULT_ATTR, '語', 2, 'c'.charCodeAt(0)]));
|
||||
lineData.setCell(4, CellData.fromCharData([DEFAULT_ATTR, '𝄞', 1, 'c'.charCodeAt(0)]));
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -1, -1);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -1, -1);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span>a</span><span style="letter-spacing: 3px;">€</span><span>c語</span><span style="letter-spacing: -2px;">𝄞</span>'
|
||||
);
|
||||
@@ -465,7 +481,7 @@ describe('DomRendererRowFactory', () => {
|
||||
lineData.setCell(4, CellData.fromCharData([DEFAULT_ATTR, 'x', 1, 'x'.charCodeAt(0)]));
|
||||
lineData.setCell(5, CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)]));
|
||||
lineData.setCell(6, CellData.fromCharData([DEFAULT_ATTR, 'b', 1, 'b'.charCodeAt(0)]));
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, 2, 4);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, 2, 4);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span>aa</span><span style="text-decoration: underline;">xxx</span><span>bb</span>'
|
||||
);
|
||||
@@ -476,7 +492,7 @@ describe('DomRendererRowFactory', () => {
|
||||
lineData.setCell(1, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)]));
|
||||
lineData.setCell(2, CellData.fromCharData([DEFAULT_ATTR, 'x', 1, 'x'.charCodeAt(0)]));
|
||||
lineData.setCell(4, CellData.fromCharData([DEFAULT_ATTR, 'x', 1, 'x'.charCodeAt(0)]));
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, 2, 4);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, 2, 4);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span>aa</span><span style="text-decoration: underline;">x x</span>'
|
||||
);
|
||||
@@ -486,7 +502,7 @@ describe('DomRendererRowFactory', () => {
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
lineData.setCell(i, CellData.fromCharData([DEFAULT_ATTR, 'a', 1, 'a'.charCodeAt(0)]));
|
||||
}
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, 5, widthCache, -100, 100);
|
||||
const spans = rowFactory.createRow(lineData, 0, false, undefined, undefined, 0, false, true, 5, widthCache, -100, 100);
|
||||
assert.equal(extractHtml(spans),
|
||||
'<span style="text-decoration: underline;">aaaaaaaaaa</span>'
|
||||
);
|
||||
|
||||
@@ -24,6 +24,7 @@ export const enum RowCss {
|
||||
UNDERLINE_CLASS = 'xterm-underline',
|
||||
OVERLINE_CLASS = 'xterm-overline',
|
||||
STRIKETHROUGH_CLASS = 'xterm-strikethrough',
|
||||
BLINK_HIDDEN_CLASS = 'xterm-blink-hidden',
|
||||
CURSOR_CLASS = 'xterm-cursor',
|
||||
CURSOR_BLINK_CLASS = 'xterm-cursor-blink',
|
||||
CURSOR_STYLE_BLOCK_CLASS = 'xterm-cursor-block',
|
||||
@@ -66,13 +67,18 @@ export class DomRendererRowFactory {
|
||||
cursorInactiveStyle: string | undefined,
|
||||
cursorX: number,
|
||||
cursorBlink: boolean,
|
||||
blinkOn: boolean,
|
||||
cellWidth: number,
|
||||
widthCache: WidthCache,
|
||||
linkStart: number,
|
||||
linkEnd: number
|
||||
linkEnd: number,
|
||||
rowInfo?: { hasBlinkingCells: boolean }
|
||||
): HTMLSpanElement[] {
|
||||
|
||||
const elements: HTMLSpanElement[] = [];
|
||||
if (rowInfo) {
|
||||
rowInfo.hasBlinkingCells = false;
|
||||
}
|
||||
const joinedRanges = this._characterJoinerService.getJoinedCharacters(row);
|
||||
const colors = this._themeService.colors;
|
||||
|
||||
@@ -153,6 +159,13 @@ export class DomRendererRowFactory {
|
||||
const isInSelection = this._isCellInSelection(x, row);
|
||||
const isCursorCell = isCursorRow && x === cursorX;
|
||||
const isLinkHover = hasHover && x >= linkStart && x <= linkEnd;
|
||||
if (rowInfo && cell.isBlink()) {
|
||||
rowInfo.hasBlinkingCells = true;
|
||||
}
|
||||
const isBlinkHidden = !blinkOn && cell.isBlink();
|
||||
if (isBlinkHidden) {
|
||||
classes.push(RowCss.BLINK_HIDDEN_CLASS);
|
||||
}
|
||||
|
||||
let isDecorated = false;
|
||||
this._decorationService.forEachDecorationAtCell(x, row, undefined, d => {
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* Copyright (c) 2026 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { TextBlinkStateManager } from 'browser/renderer/shared/TextBlinkStateManager';
|
||||
import { MockOptionsService } from 'common/TestUtils.test';
|
||||
import type { ICoreBrowserService } from 'browser/services/Services';
|
||||
import { Emitter } from 'common/Event';
|
||||
|
||||
class FakeWindow {
|
||||
public nextId = 1;
|
||||
public intervals = new Map<number, () => void>();
|
||||
|
||||
public setInterval(callback: () => void, _duration: number): number {
|
||||
const id = this.nextId++;
|
||||
this.intervals.set(id, callback);
|
||||
return id;
|
||||
}
|
||||
|
||||
public clearInterval(id: number): void {
|
||||
this.intervals.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
function createManager(duration: number): {
|
||||
manager: TextBlinkStateManager;
|
||||
window: FakeWindow;
|
||||
getRenderCount: () => number;
|
||||
} {
|
||||
const fakeWindow = new FakeWindow();
|
||||
let renderCount = 0;
|
||||
const coreBrowserService: ICoreBrowserService = {
|
||||
serviceBrand: undefined,
|
||||
isFocused: true,
|
||||
dpr: 1,
|
||||
onDprChange: new Emitter<number>().event,
|
||||
onWindowChange: new Emitter<Window & typeof globalThis>().event,
|
||||
window: fakeWindow as any,
|
||||
mainDocument: {} as any
|
||||
};
|
||||
const optionsService = new MockOptionsService({ blinkIntervalDuration: duration });
|
||||
const manager = new TextBlinkStateManager(() => {
|
||||
renderCount++;
|
||||
}, coreBrowserService, optionsService);
|
||||
return {
|
||||
manager,
|
||||
window: fakeWindow,
|
||||
getRenderCount: () => renderCount
|
||||
};
|
||||
}
|
||||
|
||||
function getOnlyIntervalCallback(window: FakeWindow): () => void {
|
||||
const iterator = window.intervals.values();
|
||||
const first = iterator.next();
|
||||
assert.ok(!first.done);
|
||||
assert.ok(iterator.next().done);
|
||||
return first.value;
|
||||
}
|
||||
|
||||
describe('TextBlinkStateManager', () => {
|
||||
it('starts interval only when needed', () => {
|
||||
const { manager, window } = createManager(100);
|
||||
assert.equal(window.intervals.size, 0);
|
||||
manager.setNeedsBlinkInViewport(true);
|
||||
assert.equal(window.intervals.size, 1);
|
||||
});
|
||||
|
||||
it('stops interval and restores blink visibility when no longer needed', () => {
|
||||
const { manager, window, getRenderCount } = createManager(100);
|
||||
manager.setNeedsBlinkInViewport(true);
|
||||
const tick = getOnlyIntervalCallback(window);
|
||||
tick();
|
||||
const rendersAfterTick = getRenderCount();
|
||||
assert.equal(manager.isBlinkOn, false);
|
||||
manager.setNeedsBlinkInViewport(false);
|
||||
assert.equal(window.intervals.size, 0);
|
||||
assert.equal(manager.isBlinkOn, true);
|
||||
assert.equal(getRenderCount(), rendersAfterTick + 1);
|
||||
});
|
||||
|
||||
it('pauses while viewport is hidden and resumes when visible', () => {
|
||||
const { manager, window } = createManager(100);
|
||||
manager.setNeedsBlinkInViewport(true);
|
||||
assert.equal(window.intervals.size, 1);
|
||||
manager.setViewportVisible(false);
|
||||
assert.equal(window.intervals.size, 0);
|
||||
manager.setViewportVisible(true);
|
||||
assert.equal(window.intervals.size, 1);
|
||||
});
|
||||
|
||||
it('does not start interval when duration is zero', () => {
|
||||
const { manager, window } = createManager(0);
|
||||
manager.setNeedsBlinkInViewport(true);
|
||||
assert.equal(window.intervals.size, 0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Copyright (c) 2026 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { ICoreBrowserService } from 'browser/services/Services';
|
||||
import { Disposable, toDisposable } from 'common/Lifecycle';
|
||||
import { IOptionsService } from 'common/services/Services';
|
||||
|
||||
export class TextBlinkStateManager extends Disposable {
|
||||
private _intervalDuration: number = 0;
|
||||
private _interval: number | undefined;
|
||||
private _blinkOn: boolean = true;
|
||||
private _needsBlinkInViewport: boolean = false;
|
||||
private _isViewportVisible: boolean = true;
|
||||
|
||||
constructor(
|
||||
private readonly _renderCallback: () => void,
|
||||
private readonly _coreBrowserService: ICoreBrowserService,
|
||||
private readonly _optionsService: IOptionsService
|
||||
) {
|
||||
super();
|
||||
this._register(this._optionsService.onSpecificOptionChange('blinkIntervalDuration', duration => {
|
||||
this.setIntervalDuration(duration);
|
||||
}));
|
||||
this.setIntervalDuration(this._optionsService.rawOptions.blinkIntervalDuration);
|
||||
this._register(toDisposable(() => this._clearInterval()));
|
||||
}
|
||||
|
||||
public get isBlinkOn(): boolean {
|
||||
return this._blinkOn;
|
||||
}
|
||||
|
||||
public get isEnabled(): boolean {
|
||||
return this._intervalDuration > 0;
|
||||
}
|
||||
|
||||
public setNeedsBlinkInViewport(needsBlinkInViewport: boolean): void {
|
||||
if (this._needsBlinkInViewport === needsBlinkInViewport) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._needsBlinkInViewport = needsBlinkInViewport;
|
||||
this._updateIntervalState();
|
||||
}
|
||||
|
||||
public setViewportVisible(isVisible: boolean): void {
|
||||
if (this._isViewportVisible === isVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._isViewportVisible = isVisible;
|
||||
this._updateIntervalState();
|
||||
}
|
||||
|
||||
public setIntervalDuration(duration: number): void {
|
||||
if (duration === this._intervalDuration) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._intervalDuration = duration;
|
||||
this._clearInterval();
|
||||
this._updateIntervalState();
|
||||
}
|
||||
|
||||
private _updateIntervalState(): void {
|
||||
const shouldBlink = this._intervalDuration > 0 && this._needsBlinkInViewport && this._isViewportVisible;
|
||||
if (shouldBlink) {
|
||||
if (this._interval !== undefined) {
|
||||
return;
|
||||
}
|
||||
const wasBlinkOn = this._blinkOn;
|
||||
this._blinkOn = true;
|
||||
this._interval = this._coreBrowserService.window.setInterval(() => {
|
||||
this._blinkOn = !this._blinkOn;
|
||||
this._renderCallback();
|
||||
}, this._intervalDuration);
|
||||
if (!wasBlinkOn) {
|
||||
this._renderCallback();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this._clearInterval();
|
||||
if (!this._blinkOn) {
|
||||
this._blinkOn = true;
|
||||
this._renderCallback();
|
||||
}
|
||||
}
|
||||
|
||||
private _clearInterval(): void {
|
||||
if (this._interval !== undefined) {
|
||||
this._coreBrowserService.window.clearInterval(this._interval);
|
||||
this._interval = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,6 +65,7 @@ export interface IRenderer extends IDisposable {
|
||||
handleCharSizeChanged(): void;
|
||||
handleBlur(): void;
|
||||
handleFocus(): void;
|
||||
handleViewportVisibilityChange?(isVisible: boolean): void;
|
||||
handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void;
|
||||
handleCursorMove(): void;
|
||||
clear(): void;
|
||||
|
||||
@@ -131,6 +131,7 @@ export class RenderService extends Disposable implements IRenderService {
|
||||
|
||||
private _handleIntersectionChange(entry: IntersectionObserverEntry): void {
|
||||
this._isPaused = entry.isIntersecting === undefined ? (entry.intersectionRatio === 0) : !entry.isIntersecting;
|
||||
this._renderer.value?.handleViewportVisibilityChange?.(!this._isPaused);
|
||||
|
||||
// Terminal was hidden on open
|
||||
if (!this._isPaused && !this._charSizeService.hasValidSize) {
|
||||
|
||||
@@ -2724,12 +2724,6 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
attr.extended = attr.extended.clone();
|
||||
attr.extended.underlineColor = -1;
|
||||
attr.updateExtended();
|
||||
} else if (p === 100) { // FIXME: dead branch, p=100 already handled above!
|
||||
// reset fg/bg
|
||||
attr.fg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK);
|
||||
attr.fg |= DEFAULT_ATTR_DATA.fg & (Attributes.PCOLOR_MASK | Attributes.RGB_MASK);
|
||||
attr.bg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK);
|
||||
attr.bg |= DEFAULT_ATTR_DATA.bg & (Attributes.PCOLOR_MASK | Attributes.RGB_MASK);
|
||||
} else {
|
||||
this._logService.debug('Unknown SGR attribute: %d.', p);
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ export interface IDisposable {
|
||||
// TODO: The options that are not in the public API should be reviewed
|
||||
export interface ITerminalOptions extends IPublicTerminalOptions {
|
||||
[key: string]: any;
|
||||
cancelEvents?: boolean;
|
||||
convertEol?: boolean;
|
||||
termName?: string;
|
||||
}
|
||||
|
||||
@@ -91,14 +91,13 @@ export const VT500_TRANSITION_TABLE = (function (): TransitionTable {
|
||||
EXECUTABLES.push.apply(EXECUTABLES, r(0x1c, 0x20));
|
||||
|
||||
const states: number[] = r(ParserState.GROUND, ParserState.STATE_LENGTH);
|
||||
let state: any;
|
||||
|
||||
// set default transition
|
||||
table.setDefault(ParserAction.ERROR, ParserState.GROUND);
|
||||
// printables
|
||||
table.addMany(PRINTABLES, ParserState.GROUND, ParserAction.PRINT, ParserState.GROUND);
|
||||
// global anywhere rules
|
||||
for (state in states) {
|
||||
for (const state of states) {
|
||||
table.addMany([0x18, 0x1a, 0x99, 0x9a], state, ParserAction.EXECUTE, ParserState.GROUND);
|
||||
table.addMany(r(0x80, 0x90), state, ParserAction.EXECUTE, ParserState.GROUND);
|
||||
table.addMany(r(0x90, 0x98), state, ParserAction.EXECUTE, ParserState.GROUND);
|
||||
|
||||
@@ -14,6 +14,7 @@ export const DEFAULT_OPTIONS: Readonly<Required<ITerminalOptions>> = {
|
||||
rows: 24,
|
||||
showCursorImmediately: false,
|
||||
cursorBlink: false,
|
||||
blinkIntervalDuration: 0,
|
||||
cursorStyle: 'block',
|
||||
cursorWidth: 1,
|
||||
cursorInactiveStyle: 'outline',
|
||||
@@ -54,7 +55,6 @@ export const DEFAULT_OPTIONS: Readonly<Required<ITerminalOptions>> = {
|
||||
altClickMovesCursor: true,
|
||||
convertEol: false,
|
||||
termName: 'xterm',
|
||||
cancelEvents: false,
|
||||
overviewRuler: {},
|
||||
quirks: {},
|
||||
vtExtensions: {}
|
||||
@@ -170,6 +170,12 @@ export class OptionsService extends Disposable implements IOptionsService {
|
||||
}
|
||||
value = FONT_WEIGHT_OPTIONS.includes(value) ? value : DEFAULT_OPTIONS[key];
|
||||
break;
|
||||
case 'blinkIntervalDuration':
|
||||
value = Math.floor(value);
|
||||
if (value < 0) {
|
||||
throw new Error(`${key} cannot be less than 0, value: ${value}`);
|
||||
}
|
||||
break;
|
||||
case 'cursorWidth':
|
||||
value = Math.floor(value);
|
||||
// Fall through for bounds check
|
||||
|
||||
@@ -229,6 +229,7 @@ export interface ITerminalOptions {
|
||||
cols?: number;
|
||||
convertEol?: boolean;
|
||||
cursorBlink?: boolean;
|
||||
blinkIntervalDuration?: number;
|
||||
cursorStyle?: CursorStyle;
|
||||
cursorWidth?: number;
|
||||
cursorInactiveStyle?: CursorInactiveStyle;
|
||||
@@ -271,7 +272,6 @@ export interface ITerminalOptions {
|
||||
vtExtensions?: IVtExtensions;
|
||||
|
||||
[key: string]: any;
|
||||
cancelEvents: boolean;
|
||||
termName: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -227,9 +227,9 @@ test.describe('InputHandler Integration Tests', () => {
|
||||
await ctx.proxy.write('\x1b[2S');
|
||||
await pollFor(ctx.page, () => getLinesAsArray(5), ['3', '4', '5', '', '']);
|
||||
});
|
||||
test.skip('CSI ? Pi ; Pa ; Pv S - XTSMGRAPHICS: Set or request graphics attribute, xterm', async () => {
|
||||
// TODO: Implement
|
||||
});
|
||||
// This is intentionally not implemented here are image support lives within an addon
|
||||
// test.skip('CSI ? Pi ; Pa ; Pv S - XTSMGRAPHICS: Set or request graphics attribute, xterm', async () => {
|
||||
// });
|
||||
test('CSI Ps T - SD: Scroll down Ps lines (default = 1), VT420', async () => {
|
||||
await ctx.proxy.write('1\r\n2\r\n3\r\n4\r\n5');
|
||||
await pollFor(ctx.page, () => getLinesAsArray(5), ['1', '2', '3', '4', '5']);
|
||||
@@ -316,14 +316,16 @@ test.describe('InputHandler Integration Tests', () => {
|
||||
await ctx.proxy.write('abcdefg\x1b[3D\x1b[10b#\x1b[3b');
|
||||
await pollFor(ctx.page, () => getLinesAsArray(3), ['#', ' #', 'abcd####']);
|
||||
});
|
||||
test.skip('CSI Ps c - ', async () => {
|
||||
// TODO: Implement
|
||||
test('CSI Ps c - ', async () => {
|
||||
await ctx.proxy.write('\x1b[c');
|
||||
await pollFor(ctx.page, () => recordedData, ['\x1b[?1;2c']);
|
||||
});
|
||||
test.skip('CSI = Ps c - ', async () => {
|
||||
// TODO: Implement
|
||||
});
|
||||
test.skip('CSI > Ps c - ', async () => {
|
||||
// TODO: Implement
|
||||
test('CSI > Ps c - ', async () => {
|
||||
await ctx.proxy.write('\x1b[>c');
|
||||
await pollFor(ctx.page, () => recordedData, ['\x1b[>0;276;0c']);
|
||||
});
|
||||
test('CSI Ps d - VPA: Line Position Absolute [row] (default = [1,column])', async () => {
|
||||
// Default
|
||||
@@ -384,11 +386,11 @@ test.describe('InputHandler Integration Tests', () => {
|
||||
await ctx.proxy.resize(80, 24);
|
||||
await ctx.proxy.setOption('windowOptions', windowOptions);
|
||||
});
|
||||
test.skip('Ps = 4 - Smooth (Slow) Scroll (DECSCLM), VT100', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 4 - Smooth (Slow) Scroll (DECSCLM), VT100', async () => {
|
||||
await assertNoModeChange('\x1b[?4h');
|
||||
});
|
||||
test.skip('Ps = 5 - Reverse Video (DECSCNM), VT100', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 5 - Reverse Video (DECSCNM), VT100', async () => {
|
||||
await assertNoModeChange('\x1b[?5h');
|
||||
});
|
||||
test('Ps = 6 - Origin Mode (DECOM), VT100', async () => {
|
||||
await ctx.proxy.write('\x1b[?6h');
|
||||
@@ -411,8 +413,8 @@ test.describe('InputHandler Integration Tests', () => {
|
||||
await pollFor(ctx.page, () => getLinesAsArray(1), ['1234X']);
|
||||
await ctx.proxy.resize(80, 24);
|
||||
});
|
||||
test.skip('Ps = 8 - Auto-Repeat Keys (DECARM), VT100', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 8 - Auto-Repeat Keys (DECARM), VT100', async () => {
|
||||
await assertNoModeChange('\x1b[?8h');
|
||||
});
|
||||
test('Ps = 9 - Send Mouse X & Y on button press', async () => {
|
||||
const selectionBefore = await dragSelection();
|
||||
@@ -422,23 +424,29 @@ test.describe('InputHandler Integration Tests', () => {
|
||||
const selectionAfter = await dragSelection();
|
||||
ok(selectionAfter === 0);
|
||||
});
|
||||
test.skip('Ps = 1 0 - Show toolbar (rxvt)', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 - Show toolbar (rxvt)', async () => {
|
||||
await assertNoModeChange('\x1b[?10h');
|
||||
});
|
||||
test.skip('Ps = 1 2 - Start blinking cursor (AT&T 610)', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 2 - Start blinking cursor (AT&T 610)', async () => {
|
||||
const previousQuirks = await ctx.proxy.getOption('quirks');
|
||||
const previousCursorBlink = await ctx.proxy.getOption('cursorBlink');
|
||||
await ctx.proxy.setOption('quirks', { ...(previousQuirks ?? {}), allowSetCursorBlink: true });
|
||||
await ctx.proxy.write('\x1b[?12h');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.getOption('cursorBlink'), true);
|
||||
await ctx.proxy.setOption('quirks', previousQuirks);
|
||||
await ctx.proxy.setOption('cursorBlink', previousCursorBlink);
|
||||
});
|
||||
test.skip('Ps = 1 3 - Start blinking cursor (set only via resource or menu)', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 3 - Start blinking cursor (set only via resource or menu)', async () => {
|
||||
await assertNoModeChange('\x1b[?13h');
|
||||
});
|
||||
test.skip('Ps = 1 4 - Enable XOR of blinking cursor control sequence and menu', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 4 - Enable XOR of blinking cursor control sequence and menu', async () => {
|
||||
await assertNoModeChange('\x1b[?14h');
|
||||
});
|
||||
test.skip('Ps = 1 8 - Print Form Feed (DECPFF), VT220', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 8 - Print Form Feed (DECPFF), VT220', async () => {
|
||||
await assertNoModeChange('\x1b[?18h');
|
||||
});
|
||||
test.skip('Ps = 1 9 - Set print extent to full screen (DECPEX), VT220', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 9 - Set print extent to full screen (DECPEX), VT220', async () => {
|
||||
await assertNoModeChange('\x1b[?19h');
|
||||
});
|
||||
test('Ps = 2 5 - Show cursor (DECTCEM), VT220', async () => {
|
||||
await ctx.proxy.write('\x1b[?25l');
|
||||
@@ -446,32 +454,32 @@ test.describe('InputHandler Integration Tests', () => {
|
||||
await ctx.proxy.write('\x1b[?25h');
|
||||
await pollFor(ctx.page, async () => (await ctx.proxy.modes).showCursor, true);
|
||||
});
|
||||
test.skip('Ps = 3 0 - Show scrollbar (rxvt)', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 3 0 - Show scrollbar (rxvt)', async () => {
|
||||
await assertNoModeChange('\x1b[?30h');
|
||||
});
|
||||
test.skip('Ps = 3 5 - Enable font-shifting functions (rxvt)', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 3 5 - Enable font-shifting functions (rxvt)', async () => {
|
||||
await assertNoModeChange('\x1b[?35h');
|
||||
});
|
||||
test.skip('Ps = 3 8 - Enter Tektronix mode (DECTEK), VT240, xterm', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 3 8 - Enter Tektronix mode (DECTEK), VT240, xterm', async () => {
|
||||
await assertNoModeChange('\x1b[?38h');
|
||||
});
|
||||
test.skip('Ps = 4 0 - Allow 80 ⇒ 132 mode, xterm', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 4 0 - Allow 80 ⇒ 132 mode, xterm', async () => {
|
||||
await assertNoModeChange('\x1b[?40h');
|
||||
});
|
||||
test.skip('Ps = 4 1 - more(1) fix (see curses resource)', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 4 1 - more(1) fix (see curses resource)', async () => {
|
||||
await assertNoModeChange('\x1b[?41h');
|
||||
});
|
||||
test.skip('Ps = 4 2 - Enable National Replacement Character sets (DECNRCM), VT220', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 4 2 - Enable National Replacement Character sets (DECNRCM), VT220', async () => {
|
||||
await assertNoModeChange('\x1b[?42h');
|
||||
});
|
||||
test.skip('Ps = 4 3 - Enable Graphic Expanded Print Mode (DECGEPM), VT340', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 4 3 - Enable Graphic Expanded Print Mode (DECGEPM), VT340', async () => {
|
||||
await assertNoModeChange('\x1b[?43h');
|
||||
});
|
||||
test.skip('Ps = 4 4 - Turn on margin bell, xterm', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 4 4 - Turn on margin bell, xterm', async () => {
|
||||
await assertNoModeChange('\x1b[?44h');
|
||||
});
|
||||
test.skip('Ps = 4 4 - Enable Graphic Print Color Mode (DECGPCM), VT340', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 4 4 - Enable Graphic Print Color Mode (DECGPCM), VT340', async () => {
|
||||
await assertNoModeChange('\x1b[?44h');
|
||||
});
|
||||
test('Ps = 4 5 - Reverse-wraparound mode (XTREVWRAP), xterm', async () => {
|
||||
await ctx.proxy.write('\x1b[?45h');
|
||||
@@ -484,13 +492,12 @@ test.describe('InputHandler Integration Tests', () => {
|
||||
await ctx.proxy.resize(80, 24);
|
||||
});
|
||||
test.skip('Ps = 4 5 - Enable Graphic Print Color Syntax (DECGPCS), VT340', async () => {
|
||||
// TODO: Implement
|
||||
});
|
||||
test.skip('Ps = 4 6 - Start logging (XTLOGGING), xterm', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 4 6 - Start logging (XTLOGGING), xterm', async () => {
|
||||
await assertNoModeChange('\x1b[?46h');
|
||||
});
|
||||
test.skip('Ps = 4 6 - Graphic Print Background Mode, VT340', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 4 6 - Graphic Print Background Mode, VT340', async () => {
|
||||
await assertNoModeChange('\x1b[?46h');
|
||||
});
|
||||
test('Ps = 4 7 - Use Alternate Screen Buffer, xterm', async () => {
|
||||
await ctx.proxy.write('main');
|
||||
@@ -505,23 +512,21 @@ test.describe('InputHandler Integration Tests', () => {
|
||||
await pollFor(ctx.page, () => getLinesAsArray(1), ['main']);
|
||||
});
|
||||
test.skip('Ps = 4 7 - Enable Graphic Rotated Print Mode (DECGRPM), VT340', async () => {
|
||||
// TODO: Implement
|
||||
});
|
||||
test('Ps = 6 6 - Application keypad mode (DECNKM), VT320', async () => {
|
||||
await ctx.proxy.write('\x1b[?66h');
|
||||
await pollFor(ctx.page, async () => (await ctx.proxy.modes).applicationKeypadMode, true);
|
||||
});
|
||||
test.skip('Ps = 6 7 - Backarrow key sends backspace (DECBKM), VT340, VT420', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 6 7 - Backarrow key sends backspace (DECBKM), VT340, VT420', async () => {
|
||||
await assertNoModeChange('\x1b[?67h');
|
||||
});
|
||||
test.skip('Ps = 6 9 - Enable left and right margin mode (DECLRMM), VT420 and up', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 6 9 - Enable left and right margin mode (DECLRMM), VT420 and up', async () => {
|
||||
await assertNoModeChange('\x1b[?69h');
|
||||
});
|
||||
test.skip('Ps = 8 0 - Enable Sixel Display Mode (DECSDM), VT330, VT340, VT382', async () => {
|
||||
// TODO: Implement
|
||||
});
|
||||
test.skip('Ps = 9 5 - Do not clear screen when DECCOLM is set/reset (DECNCSM), VT510 and up', async () => {
|
||||
// TODO: Implement
|
||||
// test.skip('Ps = 8 0 - Enable Sixel Display Mode (DECSDM), VT330, VT340, VT382', async () => {
|
||||
// });
|
||||
test('Ps = 9 5 - Do not clear screen when DECCOLM is set/reset (DECNCSM), VT510 and up', async () => {
|
||||
await assertNoModeChange('\x1b[?95h');
|
||||
});
|
||||
test('Ps = 1 0 0 0 - Send Mouse X & Y on button press and release', async () => {
|
||||
const selectionBefore = await dragSelection();
|
||||
@@ -531,8 +536,8 @@ test.describe('InputHandler Integration Tests', () => {
|
||||
const selectionAfter = await dragSelection();
|
||||
ok(selectionAfter === 0);
|
||||
});
|
||||
test.skip('Ps = 1 0 0 1 - Use Hilite Mouse Tracking, xterm', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 0 1 - Use Hilite Mouse Tracking, xterm', async () => {
|
||||
await assertNoModeChange('\x1b[?1001h');
|
||||
});
|
||||
test('Ps = 1 0 0 2 - Use Cell Motion Mouse Tracking, xterm', async () => {
|
||||
const selectionBefore = await dragSelection();
|
||||
@@ -577,64 +582,70 @@ test.describe('InputHandler Integration Tests', () => {
|
||||
await ctx.proxy.blur();
|
||||
await pollFor(ctx.page, () => recordedData, ['\x1b[I', '\x1b[O']);
|
||||
});
|
||||
test.skip('Ps = 1 0 0 5 - Enable UTF-8 Mouse Mode, xterm', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 0 5 - Enable UTF-8 Mouse Mode, xterm', async () => {
|
||||
await ctx.proxy.write('\x1b[?1006h');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding), 'SGR');
|
||||
await ctx.proxy.write('\x1b[?1005h');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding), 'SGR');
|
||||
});
|
||||
test('Ps = 1 0 0 6 - Enable SGR Mouse Mode, xterm', async () => {
|
||||
await ctx.proxy.write('\x1b[?1006h');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding), 'SGR');
|
||||
});
|
||||
test.skip('Ps = 1 0 0 7 - Enable Alternate Scroll Mode, xterm', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 0 7 - Enable Alternate Scroll Mode, xterm', async () => {
|
||||
await assertNoModeChange('\x1b[?1007h');
|
||||
});
|
||||
test.skip('Ps = 1 0 1 0 - Scroll to bottom on tty output (rxvt)', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 1 0 - Scroll to bottom on tty output (rxvt)', async () => {
|
||||
await assertNoModeChange('\x1b[?1010h');
|
||||
});
|
||||
test.skip('Ps = 1 0 1 1 - Scroll to bottom on key press (rxvt)', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 1 1 - Scroll to bottom on key press (rxvt)', async () => {
|
||||
await assertNoModeChange('\x1b[?1011h');
|
||||
});
|
||||
test.skip('Ps = 1 0 1 5 - Enable urxvt Mouse Mode', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 1 5 - Enable urxvt Mouse Mode', async () => {
|
||||
await ctx.proxy.write('\x1b[?1006h');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding), 'SGR');
|
||||
await ctx.proxy.write('\x1b[?1015h');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding), 'SGR');
|
||||
});
|
||||
test('Ps = 1 0 1 6 - Enable SGR Mouse PixelMode, xterm', async () => {
|
||||
await ctx.proxy.write('\x1b[?1016h');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding), 'SGR_PIXELS');
|
||||
});
|
||||
test.skip('Ps = 1 0 3 4 - Interpret "meta" key, xterm', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 3 4 - Interpret "meta" key, xterm', async () => {
|
||||
await assertNoModeChange('\x1b[?1034h');
|
||||
});
|
||||
test.skip('Ps = 1 0 3 5 - Enable special modifiers for Alt and NumLock keys, xterm', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 3 5 - Enable special modifiers for Alt and NumLock keys, xterm', async () => {
|
||||
await assertNoModeChange('\x1b[?1035h');
|
||||
});
|
||||
test.skip('Ps = 1 0 3 6 - Send ESC when Meta modifies a key, xterm', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 3 6 - Send ESC when Meta modifies a key, xterm', async () => {
|
||||
await assertNoModeChange('\x1b[?1036h');
|
||||
});
|
||||
test.skip('Ps = 1 0 3 7 - Send DEL from the editing-keypad Delete key, xterm', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 3 7 - Send DEL from the editing-keypad Delete key, xterm', async () => {
|
||||
await assertNoModeChange('\x1b[?1037h');
|
||||
});
|
||||
test.skip('Ps = 1 0 3 9 - Send ESC when Alt modifies a key, xterm', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 3 9 - Send ESC when Alt modifies a key, xterm', async () => {
|
||||
await assertNoModeChange('\x1b[?1039h');
|
||||
});
|
||||
test.skip('Ps = 1 0 4 0 - Keep selection even if not highlighted, xterm', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 4 0 - Keep selection even if not highlighted, xterm', async () => {
|
||||
await assertNoModeChange('\x1b[?1040h');
|
||||
});
|
||||
test.skip('Ps = 1 0 4 1 - Use the CLIPBOARD selection, xterm', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 4 1 - Use the CLIPBOARD selection, xterm', async () => {
|
||||
await assertNoModeChange('\x1b[?1041h');
|
||||
});
|
||||
test.skip('Ps = 1 0 4 2 - Enable Urgency window manager hint when Control-G is received, xterm', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 4 2 - Enable Urgency window manager hint when Control-G is received, xterm', async () => {
|
||||
await assertNoModeChange('\x1b[?1042h');
|
||||
});
|
||||
test.skip('Ps = 1 0 4 3 - Enable raising of the window when Control-G is received, xterm', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 4 3 - Enable raising of the window when Control-G is received, xterm', async () => {
|
||||
await assertNoModeChange('\x1b[?1043h');
|
||||
});
|
||||
test.skip('Ps = 1 0 4 4 - Reuse the most recent data copied to CLIPBOARD, xterm', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 4 4 - Reuse the most recent data copied to CLIPBOARD, xterm', async () => {
|
||||
await assertNoModeChange('\x1b[?1044h');
|
||||
});
|
||||
test.skip('Ps = 1 0 4 5 - XTREVWRAP2: Extended Reverse-wraparound mode, xterm', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 4 5 - XTREVWRAP2: Extended Reverse-wraparound mode, xterm', async () => {
|
||||
await assertNoModeChange('\x1b[?1045h');
|
||||
});
|
||||
test.skip('Ps = 1 0 4 6 - Enable switching to/from Alternate Screen Buffer, xterm', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 4 6 - Enable switching to/from Alternate Screen Buffer, xterm', async () => {
|
||||
await assertNoModeChange('\x1b[?1046h');
|
||||
});
|
||||
test('Ps = 1 0 4 7 - Use Alternate Screen Buffer, xterm', async () => {
|
||||
await ctx.proxy.write('main');
|
||||
@@ -655,35 +666,45 @@ test.describe('InputHandler Integration Tests', () => {
|
||||
await ctx.proxy.write('\x1b[?1048l');
|
||||
await pollFor(ctx.page, () => getCursor(), { col: 4, row: 3 });
|
||||
});
|
||||
test.skip('Ps = 1 0 4 9 - Save cursor as in DECSC, xterm', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 4 9 - Save cursor as in DECSC, xterm', async () => {
|
||||
await ctx.proxy.write('main');
|
||||
await pollFor(ctx.page, () => getLinesAsArray(1), ['main']);
|
||||
await ctx.proxy.write('\x1b[4;6H');
|
||||
await ctx.proxy.write('\x1b[?1049h');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.buffer.active.type, 'alternate');
|
||||
await ctx.proxy.write('\x1b[Halt');
|
||||
await pollFor(ctx.page, () => getLinesAsArray(1), ['alt']);
|
||||
await ctx.proxy.write('\x1b[?1049l');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.buffer.active.type, 'normal');
|
||||
await pollFor(ctx.page, () => getLinesAsArray(1), ['main']);
|
||||
await pollFor(ctx.page, () => getCursor(), { col: 5, row: 3 });
|
||||
});
|
||||
test.skip('Ps = 1 0 5 0 - Set terminfo/termcap function-key mode, xterm', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 5 0 - Set terminfo/termcap function-key mode, xterm', async () => {
|
||||
await assertNoModeChange('\x1b[?1050h');
|
||||
});
|
||||
test.skip('Ps = 1 0 5 1 - Set Sun function-key mode, xterm', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 5 1 - Set Sun function-key mode, xterm', async () => {
|
||||
await assertNoModeChange('\x1b[?1051h');
|
||||
});
|
||||
test.skip('Ps = 1 0 5 2 - Set HP function-key mode, xterm', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 5 2 - Set HP function-key mode, xterm', async () => {
|
||||
await assertNoModeChange('\x1b[?1052h');
|
||||
});
|
||||
test.skip('Ps = 1 0 5 3 - Set SCO function-key mode, xterm', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 5 3 - Set SCO function-key mode, xterm', async () => {
|
||||
await assertNoModeChange('\x1b[?1053h');
|
||||
});
|
||||
test.skip('Ps = 1 0 6 0 - Set legacy keyboard emulation, i.e, X11R6, xterm', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 6 0 - Set legacy keyboard emulation, i.e, X11R6, xterm', async () => {
|
||||
await assertNoModeChange('\x1b[?1060h');
|
||||
});
|
||||
test.skip('Ps = 1 0 6 1 - Set VT220 keyboard emulation, xterm', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 6 1 - Set VT220 keyboard emulation, xterm', async () => {
|
||||
await assertNoModeChange('\x1b[?1061h');
|
||||
});
|
||||
test.skip('Ps = 2 0 0 1 - Enable readline mouse button-1, xterm', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 2 0 0 1 - Enable readline mouse button-1, xterm', async () => {
|
||||
await assertNoModeChange('\x1b[?2001h');
|
||||
});
|
||||
test.skip('Ps = 2 0 0 2 - Enable readline mouse button-2, xterm', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 2 0 0 2 - Enable readline mouse button-2, xterm', async () => {
|
||||
await assertNoModeChange('\x1b[?2002h');
|
||||
});
|
||||
test.skip('Ps = 2 0 0 3 - Enable readline mouse button-3, xterm', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 2 0 0 3 - Enable readline mouse button-3, xterm', async () => {
|
||||
await assertNoModeChange('\x1b[?2003h');
|
||||
});
|
||||
test('Pm = 2 0 0 4, Set bracketed paste mode', async () => {
|
||||
if (ctx.browser.browserType().name() !== 'chromium') {
|
||||
@@ -696,11 +717,11 @@ test.describe('InputHandler Integration Tests', () => {
|
||||
await ctx.proxy.write('\x1b[?2004l');
|
||||
await pollFor(ctx.page, () => simulatePaste('baz'), 'baz');
|
||||
});
|
||||
test.skip('Ps = 2 0 0 5 - Enable readline character-quoting, xterm', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 2 0 0 5 - Enable readline character-quoting, xterm', async () => {
|
||||
await assertNoModeChange('\x1b[?2005h');
|
||||
});
|
||||
test.skip('Ps = 2 0 0 6 - Enable readline newline pasting, xterm', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 2 0 0 6 - Enable readline newline pasting, xterm', async () => {
|
||||
await assertNoModeChange('\x1b[?2006h');
|
||||
});
|
||||
});
|
||||
test.skip('CSI Ps i - MC: Media Copy', async () => {
|
||||
@@ -724,17 +745,24 @@ test.describe('InputHandler Integration Tests', () => {
|
||||
await ctx.proxy.write('\x1b[?1l');
|
||||
await pollFor(ctx.page, async () => (await ctx.proxy.modes).applicationCursorKeysMode, false);
|
||||
});
|
||||
test.skip('Ps = 2 - Designate VT52 mode (DECANM), VT100.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 2 - Designate VT52 mode (DECANM), VT100.', async () => {
|
||||
await assertNoModeChange('\x1b[?2l');
|
||||
});
|
||||
test.skip('Ps = 3 - 80 Column Mode (DECCOLM), VT100.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 3 - 80 Column Mode (DECCOLM), VT100.', async () => {
|
||||
const windowOptions = await ctx.proxy.getOption('windowOptions');
|
||||
await ctx.proxy.setOption('windowOptions', { ...windowOptions, setWinLines: true });
|
||||
await ctx.proxy.write('\x1b[?3h');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.cols, 132);
|
||||
await ctx.proxy.write('\x1b[?3l');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.cols, 80);
|
||||
await ctx.proxy.resize(80, 24);
|
||||
await ctx.proxy.setOption('windowOptions', windowOptions);
|
||||
});
|
||||
test.skip('Ps = 4 - Jump (Fast) Scroll (DECSCLM), VT100.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 4 - Jump (Fast) Scroll (DECSCLM), VT100.', async () => {
|
||||
await assertNoModeChange('\x1b[?4l');
|
||||
});
|
||||
test.skip('Ps = 5 - Normal Video (DECSCNM), VT100.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 5 - Normal Video (DECSCNM), VT100.', async () => {
|
||||
await assertNoModeChange('\x1b[?5l');
|
||||
});
|
||||
test('Ps = 6 - Normal Cursor Mode (DECOM), VT100.', async () => {
|
||||
await ctx.proxy.write('\x1b[?6h');
|
||||
@@ -758,8 +786,8 @@ test.describe('InputHandler Integration Tests', () => {
|
||||
await pollFor(ctx.page, () => getLinesAsArray(2), ['12345', 'X']);
|
||||
await ctx.proxy.resize(80, 24);
|
||||
});
|
||||
test.skip('Ps = 8 - No Auto-Repeat Keys (DECARM), VT100.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 8 - No Auto-Repeat Keys (DECARM), VT100.', async () => {
|
||||
await assertNoModeChange('\x1b[?8l');
|
||||
});
|
||||
test('Ps = 9 - Don\'t send Mouse X & Y on button press, xterm.', async () => {
|
||||
await ctx.proxy.write('\x1b[?9h');
|
||||
@@ -767,23 +795,30 @@ test.describe('InputHandler Integration Tests', () => {
|
||||
await ctx.proxy.write('\x1b[?9l');
|
||||
await pollFor(ctx.page, async () => (await ctx.proxy.modes).mouseTrackingMode, 'none');
|
||||
});
|
||||
test.skip('Ps = 1 0 - Hide toolbar (rxvt).', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 - Hide toolbar (rxvt).', async () => {
|
||||
await assertNoModeChange('\x1b[?10l');
|
||||
});
|
||||
test.skip('Ps = 1 2 - Stop blinking cursor (AT&T 610).', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 2 - Stop blinking cursor (AT&T 610).', async () => {
|
||||
const previousQuirks = await ctx.proxy.getOption('quirks');
|
||||
const previousCursorBlink = await ctx.proxy.getOption('cursorBlink');
|
||||
await ctx.proxy.setOption('quirks', { ...(previousQuirks ?? {}), allowSetCursorBlink: true });
|
||||
await ctx.proxy.setOption('cursorBlink', true);
|
||||
await ctx.proxy.write('\x1b[?12l');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.getOption('cursorBlink'), false);
|
||||
await ctx.proxy.setOption('quirks', previousQuirks);
|
||||
await ctx.proxy.setOption('cursorBlink', previousCursorBlink);
|
||||
});
|
||||
test.skip('Ps = 1 3 - Disable blinking cursor (reset only via resource or menu).', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 3 - Disable blinking cursor (reset only via resource or menu).', async () => {
|
||||
await assertNoModeChange('\x1b[?13l');
|
||||
});
|
||||
test.skip('Ps = 1 4 - Disable XOR of blinking cursor control sequence and menu.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 4 - Disable XOR of blinking cursor control sequence and menu.', async () => {
|
||||
await assertNoModeChange('\x1b[?14l');
|
||||
});
|
||||
test.skip('Ps = 1 8 - Don\'t Print Form Feed (DECPFF), VT220.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 8 - Don\'t Print Form Feed (DECPFF), VT220.', async () => {
|
||||
await assertNoModeChange('\x1b[?18l');
|
||||
});
|
||||
test.skip('Ps = 1 9 - Limit print to scrolling region (DECPEX), VT220.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 9 - Limit print to scrolling region (DECPEX), VT220.', async () => {
|
||||
await assertNoModeChange('\x1b[?19l');
|
||||
});
|
||||
test('Ps = 2 5 - Hide cursor (DECTCEM), VT220.', async () => {
|
||||
await ctx.proxy.write('\x1b[?25h');
|
||||
@@ -791,29 +826,29 @@ test.describe('InputHandler Integration Tests', () => {
|
||||
await ctx.proxy.write('\x1b[?25l');
|
||||
await pollFor(ctx.page, async () => (await ctx.proxy.modes).showCursor, false);
|
||||
});
|
||||
test.skip('Ps = 3 0 - Don\'t show scrollbar (rxvt).', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 3 0 - Don\'t show scrollbar (rxvt).', async () => {
|
||||
await assertNoModeChange('\x1b[?30l');
|
||||
});
|
||||
test.skip('Ps = 3 5 - Disable font-shifting functions (rxvt).', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 3 5 - Disable font-shifting functions (rxvt).', async () => {
|
||||
await assertNoModeChange('\x1b[?35l');
|
||||
});
|
||||
test.skip('Ps = 4 0 - Disallow 80 ⇒ 132 mode, xterm.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 4 0 - Disallow 80 ⇒ 132 mode, xterm.', async () => {
|
||||
await assertNoModeChange('\x1b[?40l');
|
||||
});
|
||||
test.skip('Ps = 4 1 - No more(1) fix (see curses resource).', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 4 1 - No more(1) fix (see curses resource).', async () => {
|
||||
await assertNoModeChange('\x1b[?41l');
|
||||
});
|
||||
test.skip('Ps = 4 2 - Disable National Replacement Character sets (DECNRCM), VT220.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 4 2 - Disable National Replacement Character sets (DECNRCM), VT220.', async () => {
|
||||
await assertNoModeChange('\x1b[?42l');
|
||||
});
|
||||
test.skip('Ps = 4 3 - Disable Graphic Expanded Print Mode (DECGEPM), VT340.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 4 3 - Disable Graphic Expanded Print Mode (DECGEPM), VT340.', async () => {
|
||||
await assertNoModeChange('\x1b[?43l');
|
||||
});
|
||||
test.skip('Ps = 4 4 - Turn off margin bell, xterm.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 4 4 - Turn off margin bell, xterm.', async () => {
|
||||
await assertNoModeChange('\x1b[?44l');
|
||||
});
|
||||
test.skip('Ps = 4 4 - Disable Graphic Print Color Mode (DECGPCM), VT340.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 4 4 - Disable Graphic Print Color Mode (DECGPCM), VT340.', async () => {
|
||||
await assertNoModeChange('\x1b[?44l');
|
||||
});
|
||||
test('Ps = 4 5 - No Reverse-wraparound mode (XTREVWRAP), xterm.', async () => {
|
||||
await ctx.proxy.write('\x1b[?45h');
|
||||
@@ -822,10 +857,9 @@ test.describe('InputHandler Integration Tests', () => {
|
||||
await pollFor(ctx.page, async () => (await ctx.proxy.modes).reverseWraparoundMode, false);
|
||||
});
|
||||
test.skip('Ps = 4 5 - Disable Graphic Print Color Syntax (DECGPCS), VT340.', async () => {
|
||||
// TODO: Implement
|
||||
});
|
||||
test.skip('Ps = 4 6 - Stop logging (XTLOGGING), xterm. This is normally disabled by a compile-time option.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 4 6 - Stop logging (XTLOGGING), xterm. This is normally disabled by a compile-time option.', async () => {
|
||||
await assertNoModeChange('\x1b[?46l');
|
||||
});
|
||||
test('Ps = 4 7 - Use Normal Screen Buffer, xterm.', async () => {
|
||||
await ctx.proxy.write('\x1b[?47h');
|
||||
@@ -834,7 +868,6 @@ test.describe('InputHandler Integration Tests', () => {
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.buffer.active.type, 'normal');
|
||||
});
|
||||
test.skip('Ps = 4 7 - Disable Graphic Rotated Print Mode (DECGRPM), VT340.', async () => {
|
||||
// TODO: Implement
|
||||
});
|
||||
test('Ps = 6 6 - Numeric keypad mode (DECNKM), VT320.', async () => {
|
||||
await ctx.proxy.write('\x1b[?66h');
|
||||
@@ -842,17 +875,17 @@ test.describe('InputHandler Integration Tests', () => {
|
||||
await ctx.proxy.write('\x1b[?66l');
|
||||
await pollFor(ctx.page, async () => (await ctx.proxy.modes).applicationKeypadMode, false);
|
||||
});
|
||||
test.skip('Ps = 6 7 - Backarrow key sends delete (DECBKM), VT340, VT420. This sets the backarrowKey resource to "false".', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 6 7 - Backarrow key sends delete (DECBKM), VT340, VT420. This sets the backarrowKey resource to "false".', async () => {
|
||||
await assertNoModeChange('\x1b[?67l');
|
||||
});
|
||||
test.skip('Ps = 6 9 - Disable left and right margin mode (DECLRMM), VT420 and up.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 6 9 - Disable left and right margin mode (DECLRMM), VT420 and up.', async () => {
|
||||
await assertNoModeChange('\x1b[?69l');
|
||||
});
|
||||
test.skip('Ps = 8 0 - Disable Sixel Display Mode (DECSDM), VT330, VT340, VT382. Turns on "Sixel Scrolling". See the section Sixel Graphics and mode 8 4 5 2 .', async () => {
|
||||
// TODO: Implement
|
||||
});
|
||||
test.skip('Ps = 9 5 - Clear screen when DECCOLM is set/reset (DECNCSM), VT510 and up.', async () => {
|
||||
// TODO: Implement
|
||||
// This is intentionally not implemented here are image support lives within an addon
|
||||
// test.skip('Ps = 8 0 - Disable Sixel Display Mode (DECSDM), VT330, VT340, VT382. Turns on "Sixel Scrolling". See the section Sixel Graphics and mode 8 4 5 2 .', async () => {
|
||||
// });
|
||||
test('Ps = 9 5 - Clear screen when DECCOLM is set/reset (DECNCSM), VT510 and up.', async () => {
|
||||
await assertNoModeChange('\x1b[?95l');
|
||||
});
|
||||
test('Ps = 1 0 0 0 - Don\'t send Mouse X & Y on button press and release. See the section Mouse Tracking.', async () => {
|
||||
await ctx.proxy.write('\x1b[?1000h');
|
||||
@@ -860,8 +893,8 @@ test.describe('InputHandler Integration Tests', () => {
|
||||
await ctx.proxy.write('\x1b[?1000l');
|
||||
await pollFor(ctx.page, async () => (await ctx.proxy.modes).mouseTrackingMode, 'none');
|
||||
});
|
||||
test.skip('Ps = 1 0 0 1 - Don\'t use Hilite Mouse Tracking, xterm.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 0 1 - Don\'t use Hilite Mouse Tracking, xterm.', async () => {
|
||||
await assertNoModeChange('\x1b[?1001l');
|
||||
});
|
||||
test('Ps = 1 0 0 2 - Don\'t use Cell Motion Mouse Tracking, xterm. See the section Button-event tracking.', async () => {
|
||||
await ctx.proxy.write('\x1b[?1002h');
|
||||
@@ -881,8 +914,11 @@ test.describe('InputHandler Integration Tests', () => {
|
||||
await ctx.proxy.write('\x1b[?1004l');
|
||||
await pollFor(ctx.page, async () => (await ctx.proxy.modes).sendFocusMode, false);
|
||||
});
|
||||
test.skip('Ps = 1 0 0 5 - Disable UTF-8 Mouse Mode, xterm.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 0 5 - Disable UTF-8 Mouse Mode, xterm.', async () => {
|
||||
await ctx.proxy.write('\x1b[?1006h');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding), 'SGR');
|
||||
await ctx.proxy.write('\x1b[?1005l');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding), 'SGR');
|
||||
});
|
||||
test('Ps = 1 0 0 6 - Disable SGR Mouse Mode, xterm.', async () => {
|
||||
await ctx.proxy.write('\x1b[?1006h');
|
||||
@@ -890,17 +926,20 @@ test.describe('InputHandler Integration Tests', () => {
|
||||
await ctx.proxy.write('\x1b[?1006l');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding), 'DEFAULT');
|
||||
});
|
||||
test.skip('Ps = 1 0 0 7 - Disable Alternate Scroll Mode, xterm. This corresponds to the alternateScroll resource.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 0 7 - Disable Alternate Scroll Mode, xterm. This corresponds to the alternateScroll resource.', async () => {
|
||||
await assertNoModeChange('\x1b[?1007l');
|
||||
});
|
||||
test.skip('Ps = 1 0 1 0 - Don\'t scroll to bottom on tty output (rxvt). This sets the scrollTtyOutput resource to "false".', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 1 0 - Don\'t scroll to bottom on tty output (rxvt). This sets the scrollTtyOutput resource to "false".', async () => {
|
||||
await assertNoModeChange('\x1b[?1010l');
|
||||
});
|
||||
test.skip('Ps = 1 0 1 1 - Don\'t scroll to bottom on key press (rxvt). This sets the scrollKey resource to "false".', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 1 1 - Don\'t scroll to bottom on key press (rxvt). This sets the scrollKey resource to "false".', async () => {
|
||||
await assertNoModeChange('\x1b[?1011l');
|
||||
});
|
||||
test.skip('Ps = 1 0 1 5 - Disable urxvt Mouse Mode.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 1 5 - Disable urxvt Mouse Mode.', async () => {
|
||||
await ctx.proxy.write('\x1b[?1006h');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding), 'SGR');
|
||||
await ctx.proxy.write('\x1b[?1015l');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding), 'SGR');
|
||||
});
|
||||
test('Ps = 1 0 1 6 - Disable SGR Mouse Pixel-Mode, xterm.', async () => {
|
||||
await ctx.proxy.write('\x1b[?1016h');
|
||||
@@ -908,38 +947,38 @@ test.describe('InputHandler Integration Tests', () => {
|
||||
await ctx.proxy.write('\x1b[?1016l');
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding), 'DEFAULT');
|
||||
});
|
||||
test.skip('Ps = 1 0 3 4 - Don\'t interpret "meta" key, xterm. This disables the eightBitInput resource.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 3 4 - Don\'t interpret "meta" key, xterm. This disables the eightBitInput resource.', async () => {
|
||||
await assertNoModeChange('\x1b[?1034l');
|
||||
});
|
||||
test.skip('Ps = 1 0 3 5 - Disable special modifiers for Alt and NumLock keys, xterm. This disables the numLock resource.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 3 5 - Disable special modifiers for Alt and NumLock keys, xterm. This disables the numLock resource.', async () => {
|
||||
await assertNoModeChange('\x1b[?1035l');
|
||||
});
|
||||
test.skip('Ps = 1 0 3 6 - Don\'t send ESC when Meta modifies a key, xterm. This disables the metaSendsEscape resource.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 3 6 - Don\'t send ESC when Meta modifies a key, xterm. This disables the metaSendsEscape resource.', async () => {
|
||||
await assertNoModeChange('\x1b[?1036l');
|
||||
});
|
||||
test.skip('Ps = 1 0 3 7 - Send VT220 Remove from the editing-keypad Delete key, xterm.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 3 7 - Send VT220 Remove from the editing-keypad Delete key, xterm.', async () => {
|
||||
await assertNoModeChange('\x1b[?1037l');
|
||||
});
|
||||
test.skip('Ps = 1 0 3 9 - Don\'t send ESC when Alt modifies a key, xterm. This disables the altSendsEscape resource.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 3 9 - Don\'t send ESC when Alt modifies a key, xterm. This disables the altSendsEscape resource.', async () => {
|
||||
await assertNoModeChange('\x1b[?1039l');
|
||||
});
|
||||
test.skip('Ps = 1 0 4 0 - Do not keep selection when not highlighted, xterm. This disables the keepSelection resource.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 4 0 - Do not keep selection when not highlighted, xterm. This disables the keepSelection resource.', async () => {
|
||||
await assertNoModeChange('\x1b[?1040l');
|
||||
});
|
||||
test.skip('Ps = 1 0 4 1 - Use the PRIMARY selection, xterm. This disables the selectToClipboard resource.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 4 1 - Use the PRIMARY selection, xterm. This disables the selectToClipboard resource.', async () => {
|
||||
await assertNoModeChange('\x1b[?1041l');
|
||||
});
|
||||
test.skip('Ps = 1 0 4 2 - Disable Urgency window manager hint when Control-G is received, xterm. This disables the bellIsUrgent resource.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 4 2 - Disable Urgency window manager hint when Control-G is received, xterm. This disables the bellIsUrgent resource.', async () => {
|
||||
await assertNoModeChange('\x1b[?1042l');
|
||||
});
|
||||
test.skip('Ps = 1 0 4 3 - Disable raising of the window when Control- G is received, xterm. This disables the popOnBell resource.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 4 3 - Disable raising of the window when Control- G is received, xterm. This disables the popOnBell resource.', async () => {
|
||||
await assertNoModeChange('\x1b[?1043l');
|
||||
});
|
||||
test.skip('Ps = 1 0 4 5 - No Extended Reverse-wraparound mode (XTREVWRAP2), xterm.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 4 5 - No Extended Reverse-wraparound mode (XTREVWRAP2), xterm.', async () => {
|
||||
await assertNoModeChange('\x1b[?1045l');
|
||||
});
|
||||
test.skip('Ps = 1 0 4 6 - Disable switching to/from Alternate Screen Buffer, xterm. This works for terminfo-based systems, updating the titeInhibit resource. If currently using the Alternate Screen Buffer, xterm switches to the Normal Screen Buffer.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 4 6 - Disable switching to/from Alternate Screen Buffer, xterm. This works for terminfo-based systems, updating the titeInhibit resource. If currently using the Alternate Screen Buffer, xterm switches to the Normal Screen Buffer.', async () => {
|
||||
await assertNoModeChange('\x1b[?1046l');
|
||||
});
|
||||
test('Ps = 1 0 4 7 - Use Normal Screen Buffer, xterm. Clear the screen first if in the Alternate Screen Buffer. This may be disabled by the titeInhibit resource.', async () => {
|
||||
await ctx.proxy.write('\x1b[?1047h');
|
||||
@@ -964,32 +1003,32 @@ test.describe('InputHandler Integration Tests', () => {
|
||||
await pollFor(ctx.page, async () => await ctx.proxy.buffer.active.type, 'normal');
|
||||
await pollFor(ctx.page, () => getCursor(), { col: 3, row: 2 });
|
||||
});
|
||||
test.skip('Ps = 1 0 5 0 - Reset terminfo/termcap function-key mode, xterm.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 5 0 - Reset terminfo/termcap function-key mode, xterm.', async () => {
|
||||
await assertNoModeChange('\x1b[?1050l');
|
||||
});
|
||||
test.skip('Ps = 1 0 5 1 - Reset Sun function-key mode, xterm.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 5 1 - Reset Sun function-key mode, xterm.', async () => {
|
||||
await assertNoModeChange('\x1b[?1051l');
|
||||
});
|
||||
test.skip('Ps = 1 0 5 2 - Reset HP function-key mode, xterm.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 5 2 - Reset HP function-key mode, xterm.', async () => {
|
||||
await assertNoModeChange('\x1b[?1052l');
|
||||
});
|
||||
test.skip('Ps = 1 0 5 3 - Reset SCO function-key mode, xterm.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 5 3 - Reset SCO function-key mode, xterm.', async () => {
|
||||
await assertNoModeChange('\x1b[?1053l');
|
||||
});
|
||||
test.skip('Ps = 1 0 6 0 - Reset legacy keyboard emulation, i.e, X11R6, xterm.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 6 0 - Reset legacy keyboard emulation, i.e, X11R6, xterm.', async () => {
|
||||
await assertNoModeChange('\x1b[?1060l');
|
||||
});
|
||||
test.skip('Ps = 1 0 6 1 - Reset keyboard emulation to Sun/PC style, xterm.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 1 0 6 1 - Reset keyboard emulation to Sun/PC style, xterm.', async () => {
|
||||
await assertNoModeChange('\x1b[?1061l');
|
||||
});
|
||||
test.skip('Ps = 2 0 0 1 - Disable readline mouse button-1, xterm.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 2 0 0 1 - Disable readline mouse button-1, xterm.', async () => {
|
||||
await assertNoModeChange('\x1b[?2001l');
|
||||
});
|
||||
test.skip('Ps = 2 0 0 2 - Disable readline mouse button-2, xterm.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 2 0 0 2 - Disable readline mouse button-2, xterm.', async () => {
|
||||
await assertNoModeChange('\x1b[?2002l');
|
||||
});
|
||||
test.skip('Ps = 2 0 0 3 - Disable readline mouse button-3, xterm.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 2 0 0 3 - Disable readline mouse button-3, xterm.', async () => {
|
||||
await assertNoModeChange('\x1b[?2003l');
|
||||
});
|
||||
test('Ps = 2 0 0 4 - Reset bracketed paste mode, xterm.', async () => {
|
||||
await ctx.proxy.write('\x1b[?2004h');
|
||||
@@ -997,11 +1036,11 @@ test.describe('InputHandler Integration Tests', () => {
|
||||
await ctx.proxy.write('\x1b[?2004l');
|
||||
await pollFor(ctx.page, async () => (await ctx.proxy.modes).bracketedPasteMode, false);
|
||||
});
|
||||
test.skip('Ps = 2 0 0 5 - Disable readline character-quoting, xterm.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 2 0 0 5 - Disable readline character-quoting, xterm.', async () => {
|
||||
await assertNoModeChange('\x1b[?2005l');
|
||||
});
|
||||
test.skip('Ps = 2 0 0 6 - Disable readline newline pasting, xterm.', async () => {
|
||||
// TODO: Implement
|
||||
test('Ps = 2 0 0 6 - Disable readline newline pasting, xterm.', async () => {
|
||||
await assertNoModeChange('\x1b[?2006l');
|
||||
});
|
||||
});
|
||||
test.describe('CSI Pm m - SGR: Character Attributes', () => {
|
||||
@@ -1492,8 +1531,14 @@ test.describe('InputHandler Integration Tests', () => {
|
||||
await ctx.proxy.write('\x1b[0 q');
|
||||
deepStrictEqual(await getCursorMode(), { style: undefined, blink: undefined });
|
||||
});
|
||||
test.skip('CSI Ps " q - Select character protection attribute (DECSCA), VT220.', async () => {
|
||||
// TODO: Implement
|
||||
test('CSI Ps " q - Select character protection attribute (DECSCA), VT220.', async () => {
|
||||
await ctx.proxy.write('\x1b[1"q');
|
||||
await ctx.proxy.write('PROT');
|
||||
await ctx.proxy.write('\x1b[2"q');
|
||||
await ctx.proxy.write('open');
|
||||
await pollFor(ctx.page, () => getLinesAsArray(1), ['PROTopen']);
|
||||
await ctx.proxy.write('\x1b[1;1H\x1b[?2K');
|
||||
await pollFor(ctx.page, () => getLinesAsArray(1), ['PROT']);
|
||||
});
|
||||
test.skip('CSI # q - Pop video attributes from stack (XTPOPSGR), xterm.', async () => {
|
||||
// TODO: Implement
|
||||
@@ -1797,6 +1842,25 @@ test.describe('InputHandler Integration Tests', () => {
|
||||
});
|
||||
|
||||
|
||||
async function getModeSnapshot(): Promise<any> {
|
||||
return {
|
||||
modes: await ctx.proxy.modes,
|
||||
cursorBlink: await ctx.proxy.getOption('cursorBlink'),
|
||||
cols: await ctx.proxy.cols,
|
||||
rows: await ctx.proxy.rows,
|
||||
bufferType: await ctx.proxy.buffer.active.type,
|
||||
mouseProtocol: await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeProtocol),
|
||||
mouseEncoding: await ctx.proxy.core.evaluate(([core]) => core.coreMouseService.activeEncoding)
|
||||
};
|
||||
}
|
||||
|
||||
async function assertNoModeChange(sequence: string): Promise<void> {
|
||||
const before = await getModeSnapshot();
|
||||
await ctx.proxy.write(sequence);
|
||||
deepStrictEqual(await getModeSnapshot(), before);
|
||||
}
|
||||
|
||||
|
||||
async function getLinesAsArray(count: number, start: number = 0): Promise<string[]> {
|
||||
let text = '';
|
||||
for (let i = start; i < start + count; i++) {
|
||||
|
||||
@@ -13,6 +13,7 @@ export interface ISharedRendererTestContext {
|
||||
skipDomExceptions?: boolean;
|
||||
}
|
||||
|
||||
|
||||
export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void {
|
||||
test.beforeEach(async () => {
|
||||
await ctx.value.proxy.reset();
|
||||
@@ -66,7 +67,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void
|
||||
window.term.options.drawBoldTextInBrightColors = true;
|
||||
`);
|
||||
await ctx.value.proxy.write(`\x1b[1;30m■\x1b[1;31m■\x1b[1;32m■\x1b[1;33m■\x1b[1;34m■\x1b[1;35m■\x1b[1;36m■\x1b[1;37m■`);
|
||||
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [1, 2, 3, 255]);
|
||||
await pollForCellColorFresh(ctx.value, 1, 1, [1, 2, 3, 255]);
|
||||
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [4, 5, 6, 255]);
|
||||
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [7, 8, 9, 255]);
|
||||
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [10, 11, 12, 255]);
|
||||
@@ -180,15 +181,16 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void
|
||||
white: '#161718'
|
||||
};
|
||||
await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
|
||||
await ctx.value.proxy.write(`\x1b[8;40m■\x1b[8;41m■\x1b[8;42m■\x1b[8;43m■\x1b[8;44m■\x1b[8;45m■\x1b[8;46m■\x1b[8;47m■`);
|
||||
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [1, 2, 3, 255]);
|
||||
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [4, 5, 6, 255]);
|
||||
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [7, 8, 9, 255]);
|
||||
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [10, 11, 12, 255]);
|
||||
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [13, 14, 15, 255]);
|
||||
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [16, 17, 18, 255]);
|
||||
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [19, 20, 21, 255]);
|
||||
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [22, 23, 24, 255]);
|
||||
await waitForRenderAfter(ctx.value, () => ctx.value.proxy.write(`\x1b[8;40m■\x1b[8;41m■\x1b[8;42m■\x1b[8;43m■\x1b[8;44m■\x1b[8;45m■\x1b[8;46m■\x1b[8;47m■`));
|
||||
const getBgColor = (col: number, row: number): Promise<[number, number, number, number]> => getCellDomBackgroundColor(ctx.value, col, row);
|
||||
await pollFor(ctx.value.page, () => getBgColor(1, 1), [1, 2, 3, 255]);
|
||||
await pollFor(ctx.value.page, () => getBgColor(2, 1), [4, 5, 6, 255]);
|
||||
await pollFor(ctx.value.page, () => getBgColor(3, 1), [7, 8, 9, 255]);
|
||||
await pollFor(ctx.value.page, () => getBgColor(4, 1), [10, 11, 12, 255]);
|
||||
await pollFor(ctx.value.page, () => getBgColor(5, 1), [13, 14, 15, 255]);
|
||||
await pollFor(ctx.value.page, () => getBgColor(6, 1), [16, 17, 18, 255]);
|
||||
await pollFor(ctx.value.page, () => getBgColor(7, 1), [19, 20, 21, 255]);
|
||||
await pollFor(ctx.value.page, () => getBgColor(8, 1), [22, 23, 24, 255]);
|
||||
});
|
||||
|
||||
test('foreground 0-15 bright', async () => {
|
||||
@@ -245,7 +247,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void
|
||||
}
|
||||
data += '\r\n';
|
||||
}
|
||||
await ctx.value.proxy.write(data);
|
||||
await waitForRenderAfter(ctx.value, () => ctx.value.proxy.write(data));
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const cssColor = COLORS_16_TO_255[y * 16 + x];
|
||||
@@ -265,7 +267,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void
|
||||
}
|
||||
data += '\r\n';
|
||||
}
|
||||
await ctx.value.proxy.write(data);
|
||||
await waitForRenderAfter(ctx.value, () => ctx.value.proxy.write(data));
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const cssColor = COLORS_16_TO_255[y * 16 + x];
|
||||
@@ -285,7 +287,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void
|
||||
}
|
||||
data += '\r\n';
|
||||
}
|
||||
await ctx.value.proxy.write(data);
|
||||
await waitForRenderAfter(ctx.value, () => ctx.value.proxy.write(data));
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const cssColor = COLORS_16_TO_255[y * 16 + x];
|
||||
@@ -305,7 +307,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void
|
||||
}
|
||||
data += '\r\n';
|
||||
}
|
||||
await ctx.value.proxy.write(data);
|
||||
await waitForRenderAfter(ctx.value, () => ctx.value.proxy.write(data));
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const cssColor = COLORS_16_TO_255[y * 16 + x];
|
||||
@@ -341,14 +343,20 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void
|
||||
}
|
||||
data += '\r\n';
|
||||
}
|
||||
await ctx.value.proxy.write(data);
|
||||
await waitForRenderAfter(ctx.value, () => ctx.value.proxy.write(data));
|
||||
const getBgColor = (col: number, row: number): Promise<[number, number, number, number]> => getCellDomBackgroundColor(ctx.value, col, row);
|
||||
const firstCssColor = COLORS_16_TO_255[1];
|
||||
const firstR = parseInt(firstCssColor.slice(1, 3), 16);
|
||||
const firstG = parseInt(firstCssColor.slice(3, 5), 16);
|
||||
const firstB = parseInt(firstCssColor.slice(5, 7), 16);
|
||||
await pollFor(ctx.value.page, () => getBgColor(2, 1), [firstR, firstG, firstB, 255]);
|
||||
for (let y = 0; y < 240 / 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const cssColor = COLORS_16_TO_255[y * 16 + x];
|
||||
const r = parseInt(cssColor.slice(1, 3), 16);
|
||||
const g = parseInt(cssColor.slice(3, 5), 16);
|
||||
const b = parseInt(cssColor.slice(5, 7), 16);
|
||||
await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [r, g, b, 255]);
|
||||
await pollFor(ctx.value.page, () => getBgColor(x + 1, y + 1), [r, g, b, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -716,11 +724,13 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void
|
||||
}
|
||||
data += '\r\n';
|
||||
}
|
||||
await ctx.value.proxy.write(data);
|
||||
await waitForRenderAfter(ctx.value, () => ctx.value.proxy.write(data));
|
||||
const getBgColor = (col: number, row: number): Promise<[number, number, number, number]> => getCellDomBackgroundColor(ctx.value, col, row);
|
||||
await pollFor(ctx.value.page, () => getBgColor(2, 1), [1, 1, 1, 255]);
|
||||
for (let y = 0; y < 16; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
const i = y * 16 + x;
|
||||
await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [i, i, i, 255]);
|
||||
await pollFor(ctx.value.page, () => getBgColor(x + 1, y + 1), [i, i, i, 255]);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -886,50 +896,61 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void
|
||||
window.term.options.theme = ${JSON.stringify(theme)};
|
||||
window.term.options.minimumContrastRatio = 1;
|
||||
`);
|
||||
await ctx.value.proxy.write(
|
||||
await waitForRenderAfter(ctx.value, () => ctx.value.proxy.write(
|
||||
'\x1b[2m' +
|
||||
`\x1b[30m■\x1b[31m■\x1b[32m■\x1b[33m■\x1b[34m■\x1b[35m■\x1b[36m■\x1b[37m■\r\n` +
|
||||
`\x1b[90m■\x1b[91m■\x1b[92m■\x1b[93m■\x1b[94m■\x1b[95m■\x1b[96m■\x1b[97m■`
|
||||
);
|
||||
));
|
||||
// Validate before minimumContrastRatio is applied
|
||||
const marginOfError = 1;
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 1, 1), [Math.floor((255 + 0x2e) / 2), Math.floor((255 + 0x34) / 2), Math.floor((255 + 0x36) / 2), 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 2, 1), [Math.floor((255 + 0xcc) / 2), Math.floor((255 + 0x00) / 2), Math.floor((255 + 0x00) / 2), 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 3, 1), [Math.floor((255 + 0x4e) / 2), Math.floor((255 + 0x9a) / 2), Math.floor((255 + 0x06) / 2), 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 4, 1), [Math.floor((255 + 0xc4) / 2), Math.floor((255 + 0xa0) / 2), Math.floor((255 + 0x00) / 2), 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 5, 1), [Math.floor((255 + 0x34) / 2), Math.floor((255 + 0x65) / 2), Math.floor((255 + 0xa4) / 2), 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 6, 1), [Math.floor((255 + 0x75) / 2), Math.floor((255 + 0x50) / 2), Math.floor((255 + 0x7b) / 2), 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 7, 1), [Math.floor((255 + 0x06) / 2), Math.floor((255 + 0x98) / 2), Math.floor((255 + 0x9a) / 2), 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 8, 1), [Math.floor((255 + 0xd3) / 2), Math.floor((255 + 0xd7) / 2), Math.floor((255 + 0xcf) / 2), 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 1, 2), [Math.floor((255 + 0x55) / 2), Math.floor((255 + 0x57) / 2), Math.floor((255 + 0x53) / 2), 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 2, 2), [Math.floor((255 + 0xef) / 2), Math.floor((255 + 0x29) / 2), Math.floor((255 + 0x29) / 2), 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 3, 2), [Math.floor((255 + 0x8a) / 2), Math.floor((255 + 0xe2) / 2), Math.floor((255 + 0x34) / 2), 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 4, 2), [Math.floor((255 + 0xfc) / 2), Math.floor((255 + 0xe9) / 2), Math.floor((255 + 0x4f) / 2), 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 5, 2), [Math.floor((255 + 0x72) / 2), Math.floor((255 + 0x9f) / 2), Math.floor((255 + 0xcf) / 2), 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 6, 2), [Math.floor((255 + 0xad) / 2), Math.floor((255 + 0x7f) / 2), Math.floor((255 + 0xa8) / 2), 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 7, 2), [Math.floor((255 + 0x34) / 2), Math.floor((255 + 0xe2) / 2), Math.floor((255 + 0xe2) / 2), 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 8, 2), [Math.floor((255 + 0xee) / 2), Math.floor((255 + 0xee) / 2), Math.floor((255 + 0xec) / 2), 255]);
|
||||
const background: [number, number, number, number] = [255, 255, 255, 255];
|
||||
const compositeOverBackground = (fg: [number, number, number, number]): [number, number, number, number] => {
|
||||
const alpha = fg[3] / 255;
|
||||
const inv = 1 - alpha;
|
||||
return [
|
||||
Math.round(fg[0] * alpha + background[0] * inv),
|
||||
Math.round(fg[1] * alpha + background[1] * inv),
|
||||
Math.round(fg[2] * alpha + background[2] * inv),
|
||||
255
|
||||
];
|
||||
};
|
||||
const getFgColor = async (col: number, row: number): Promise<[number, number, number, number]> => compositeOverBackground(await getCellDomForegroundColor(ctx.value, col, row));
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(1, 1), [Math.floor((255 + 0x2e) / 2), Math.floor((255 + 0x34) / 2), Math.floor((255 + 0x36) / 2), 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(2, 1), [Math.floor((255 + 0xcc) / 2), Math.floor((255 + 0x00) / 2), Math.floor((255 + 0x00) / 2), 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(3, 1), [Math.floor((255 + 0x4e) / 2), Math.floor((255 + 0x9a) / 2), Math.floor((255 + 0x06) / 2), 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(4, 1), [Math.floor((255 + 0xc4) / 2), Math.floor((255 + 0xa0) / 2), Math.floor((255 + 0x00) / 2), 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(5, 1), [Math.floor((255 + 0x34) / 2), Math.floor((255 + 0x65) / 2), Math.floor((255 + 0xa4) / 2), 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(6, 1), [Math.floor((255 + 0x75) / 2), Math.floor((255 + 0x50) / 2), Math.floor((255 + 0x7b) / 2), 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(7, 1), [Math.floor((255 + 0x06) / 2), Math.floor((255 + 0x98) / 2), Math.floor((255 + 0x9a) / 2), 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(8, 1), [Math.floor((255 + 0xd3) / 2), Math.floor((255 + 0xd7) / 2), Math.floor((255 + 0xcf) / 2), 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(1, 2), [Math.floor((255 + 0x55) / 2), Math.floor((255 + 0x57) / 2), Math.floor((255 + 0x53) / 2), 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(2, 2), [Math.floor((255 + 0xef) / 2), Math.floor((255 + 0x29) / 2), Math.floor((255 + 0x29) / 2), 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(3, 2), [Math.floor((255 + 0x8a) / 2), Math.floor((255 + 0xe2) / 2), Math.floor((255 + 0x34) / 2), 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(4, 2), [Math.floor((255 + 0xfc) / 2), Math.floor((255 + 0xe9) / 2), Math.floor((255 + 0x4f) / 2), 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(5, 2), [Math.floor((255 + 0x72) / 2), Math.floor((255 + 0x9f) / 2), Math.floor((255 + 0xcf) / 2), 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(6, 2), [Math.floor((255 + 0xad) / 2), Math.floor((255 + 0x7f) / 2), Math.floor((255 + 0xa8) / 2), 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(7, 2), [Math.floor((255 + 0x34) / 2), Math.floor((255 + 0xe2) / 2), Math.floor((255 + 0xe2) / 2), 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(8, 2), [Math.floor((255 + 0xee) / 2), Math.floor((255 + 0xee) / 2), Math.floor((255 + 0xec) / 2), 255]);
|
||||
// Setting and check for minimum contrast values, note that these are not
|
||||
// exact to the contrast ratio, if the increase luminance algorithm
|
||||
// changes then these will probably fail
|
||||
await ctx.value.page.evaluate(`window.term.options.minimumContrastRatio = 10;`);
|
||||
frameDetails = undefined;
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 1, 1), [150, 153, 154, 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 2, 1), [229, 127, 127, 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 3, 1), [63, 124, 4, 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 4, 1), [127, 104, 0, 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 5, 1), [153, 178, 209, 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 6, 1), [186, 167, 189, 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 7, 1), [4, 122, 124, 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 8, 1), [110, 112, 108, 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 1, 2), [170, 171, 169, 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 2, 2), [215, 36, 36, 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 3, 2), [72, 117, 25, 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 4, 2), [117, 109, 36, 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 5, 2), [72, 103, 135, 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 6, 2), [125, 91, 121, 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 7, 2), [25, 117, 117, 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getCellColor(ctx.value, 8, 2), [111, 111, 110, 255]);
|
||||
await waitForRenderAfter(ctx.value, () => ctx.value.page.evaluate(`window.term.options.minimumContrastRatio = 10;`));
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(1, 1), [150, 153, 154, 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(2, 1), [229, 127, 127, 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(3, 1), [63, 124, 4, 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(4, 1), [127, 104, 0, 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(5, 1), [153, 178, 209, 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(6, 1), [186, 167, 189, 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(7, 1), [4, 122, 124, 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(8, 1), [110, 112, 108, 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(1, 2), [170, 171, 169, 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(2, 2), [215, 36, 36, 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(3, 2), [72, 117, 25, 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(4, 2), [117, 109, 36, 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(5, 2), [72, 103, 135, 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(6, 2), [125, 91, 121, 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(7, 2), [25, 117, 117, 255]);
|
||||
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(8, 2), [111, 111, 110, 255]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1160,11 +1181,11 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void
|
||||
// Regression test: #4758 when multiple invisible characters are used
|
||||
await ctx.value.proxy.writeln(`■\x1b[8m■■`);
|
||||
// Full refresh as the before result is the same as after
|
||||
await ctx.value.proxy.refresh(0, await ctx.value.proxy.rows - 1);
|
||||
// Control to ensure rendering has occurred
|
||||
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 255, 255, 255]);
|
||||
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [0, 0, 0, 255]);
|
||||
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [0, 0, 0, 255]);
|
||||
const rows = await ctx.value.proxy.rows;
|
||||
await waitForRenderAfter(ctx.value, () => ctx.value.proxy.refresh(0, rows - 1));
|
||||
await pollFor(ctx.value.page, () => getCellDomChar(ctx.value, 1, 1), '■');
|
||||
await pollFor(ctx.value.page, () => getCellDomChar(ctx.value, 2, 1), ' ');
|
||||
await pollFor(ctx.value.page, () => getCellDomChar(ctx.value, 3, 1), ' ');
|
||||
});
|
||||
// HACK: It's not clear why DOM is failing here
|
||||
(ctx.skipDomExceptions ? test.skip : test)('#4759: minimum contrast ratio should be respected on inverse text', async () => {
|
||||
@@ -1341,6 +1362,95 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void
|
||||
});
|
||||
}
|
||||
|
||||
async function waitForRender(ctx: ITestContext): Promise<void> {
|
||||
await new Promise<void>(resolve => {
|
||||
const disposable = ctx.proxy.onRender(() => {
|
||||
disposable.dispose();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function waitForRenderAfter(ctx: ITestContext, action: () => Promise<void>): Promise<void> {
|
||||
const renderPromise = waitForRender(ctx);
|
||||
await action();
|
||||
await renderPromise;
|
||||
frameDetails = undefined;
|
||||
}
|
||||
|
||||
async function pollForCellColorFresh(ctx: ITestContext, col: number, row: number, expected: [number, number, number, number], position: CellColorPosition = CellColorPosition.CENTER): Promise<void> {
|
||||
await pollFor(ctx.page, () => getCellColor(ctx, col, row, position), expected, async () => {
|
||||
frameDetails = undefined;
|
||||
});
|
||||
}
|
||||
|
||||
interface ICellDomInfo {
|
||||
fg: string;
|
||||
bg: string;
|
||||
char: string;
|
||||
}
|
||||
|
||||
function parseCssColor(value: string): [number, number, number, number] {
|
||||
if (value === 'transparent') {
|
||||
return [0, 0, 0, 0];
|
||||
}
|
||||
const match = value.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([0-9.]+))?\)$/);
|
||||
if (!match) {
|
||||
throw new Error(`Unsupported color format: ${value}`);
|
||||
}
|
||||
const r = Number(match[1]);
|
||||
const g = Number(match[2]);
|
||||
const b = Number(match[3]);
|
||||
const a = match[4] === undefined ? 255 : Math.round(Number(match[4]) * 255);
|
||||
return [r, g, b, a];
|
||||
}
|
||||
|
||||
async function getCellDomInfo(ctx: ITestContext, col: number, row: number): Promise<ICellDomInfo> {
|
||||
const info = await ctx.page.evaluate(({ col, row }) => {
|
||||
const rows = document.querySelectorAll('#terminal-container .xterm-rows > div');
|
||||
const rowEl = rows[row - 1];
|
||||
if (!rowEl) {
|
||||
return undefined;
|
||||
}
|
||||
let currentCol = 1;
|
||||
for (const el of Array.from(rowEl.children)) {
|
||||
const text = el.textContent ?? '';
|
||||
const chars = Array.from(text);
|
||||
const nextCol = currentCol + chars.length;
|
||||
if (col >= currentCol && col < nextCol) {
|
||||
const style = window.getComputedStyle(el as HTMLElement);
|
||||
return {
|
||||
fg: style.color,
|
||||
bg: style.backgroundColor,
|
||||
char: chars[col - currentCol] ?? ''
|
||||
};
|
||||
}
|
||||
currentCol = nextCol;
|
||||
}
|
||||
return undefined;
|
||||
}, { col, row });
|
||||
|
||||
if (!info) {
|
||||
throw new Error(`Cell not found: ${col},${row}`);
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
async function getCellDomBackgroundColor(ctx: ITestContext, col: number, row: number): Promise<[number, number, number, number]> {
|
||||
const info = await getCellDomInfo(ctx, col, row);
|
||||
return parseCssColor(info.bg);
|
||||
}
|
||||
|
||||
async function getCellDomForegroundColor(ctx: ITestContext, col: number, row: number): Promise<[number, number, number, number]> {
|
||||
const info = await getCellDomInfo(ctx, col, row);
|
||||
return parseCssColor(info.fg);
|
||||
}
|
||||
|
||||
async function getCellDomChar(ctx: ITestContext, col: number, row: number): Promise<string> {
|
||||
const info = await getCellDomInfo(ctx, col, row);
|
||||
return info.char;
|
||||
}
|
||||
|
||||
enum CellColorPosition {
|
||||
CENTER = 0,
|
||||
FIRST = 1
|
||||
|
||||
@@ -5,10 +5,9 @@ const config: PlaywrightTestConfig = {
|
||||
timeout: 10000,
|
||||
projects: [
|
||||
{
|
||||
name: 'ChromeStable',
|
||||
name: 'Chromium',
|
||||
use: {
|
||||
browserName: 'chromium',
|
||||
channel: 'chrome'
|
||||
browserName: 'chromium'
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
Vendored
+7
@@ -55,6 +55,13 @@ declare module '@xterm/headless' {
|
||||
*/
|
||||
cursorBlink?: boolean;
|
||||
|
||||
/**
|
||||
* The interval in milliseconds for the blink attribute. This is the amount
|
||||
* of time text remains visible or hidden before toggling. Set to 0 to
|
||||
* disable blinking. The default is 0.
|
||||
*/
|
||||
blinkIntervalDuration?: number;
|
||||
|
||||
/**
|
||||
* The style of the cursor.
|
||||
*/
|
||||
|
||||
Vendored
+7
@@ -64,6 +64,13 @@ declare module '@xterm/xterm' {
|
||||
*/
|
||||
cursorBlink?: boolean;
|
||||
|
||||
/**
|
||||
* The interval in milliseconds for the blink attribute. This is the amount
|
||||
* of time text remains visible or hidden before toggling. Set to 0 to
|
||||
* disable blinking. The default is 0.
|
||||
*/
|
||||
blinkIntervalDuration?: number;
|
||||
|
||||
/**
|
||||
* The style of the cursor when the terminal is focused.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user