Add multiline-comment-style lint rule and fix violations
Enable multiline-comment-style with starred-block to require JSDoc-style block comments with content on separate lines from the opening delimiter. Auto-fix existing violations across the codebase. Fixes #5899 Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
This commit is contained in:
co-authored by
Daniel Imms
parent
1a67b6f4b3
commit
4ec85e67ba
@@ -26,18 +26,24 @@ test.describe('FitAddon', () => {
|
||||
`);
|
||||
});
|
||||
|
||||
// test.beforeEach(async function(): Promise<any> {
|
||||
// await ctx.page.evaluate(`document.querySelector('#terminal-container').style.display=''`);
|
||||
// await openTerminal(page);
|
||||
// });
|
||||
/*
|
||||
* test.beforeEach(async function(): Promise<any> {
|
||||
* await ctx.page.evaluate(`document.querySelector('#terminal-container').style.display=''`);
|
||||
* await openTerminal(page);
|
||||
* });
|
||||
*/
|
||||
|
||||
// after(async () => {
|
||||
// await browser.close();
|
||||
// });
|
||||
/*
|
||||
* after(async () => {
|
||||
* await browser.close();
|
||||
* });
|
||||
*/
|
||||
|
||||
// afterEach(async function(): Promise<any> {
|
||||
// await ctx.proxy.dispose();
|
||||
// });
|
||||
/*
|
||||
* afterEach(async function(): Promise<any> {
|
||||
* await ctx.proxy.dispose();
|
||||
* });
|
||||
*/
|
||||
|
||||
test('no terminal', async function(): Promise<any> {
|
||||
await ctx.page.evaluate(`window.fit2 = new FitAddon();`);
|
||||
@@ -79,8 +85,10 @@ test.describe('FitAddon', () => {
|
||||
await ctx.page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`);
|
||||
await setDimensions();
|
||||
const dimensions: { cols: number, rows: number } | undefined = await ctx.page.evaluate(`window.fit.proposeDimensions()`);
|
||||
// The value of dims will be undefined if the char measure strategy falls back to the DOM
|
||||
// method, so only assert if it's not undefined.
|
||||
/*
|
||||
* The value of dims will be undefined if the char measure strategy falls back to the DOM
|
||||
* method, so only assert if it's not undefined.
|
||||
*/
|
||||
if (dimensions) {
|
||||
ok(dimensions.cols > 85);
|
||||
ok(dimensions.cols < 88);
|
||||
|
||||
@@ -24,8 +24,10 @@ export interface IHeaderFields {
|
||||
height?: string;
|
||||
// Optional, defaults to 1 respecting aspect ratio (width takes precedence).
|
||||
preserveAspectRatio?: number;
|
||||
// Optional, defaults to 0. If set to 1, the file will be displayed inline, else downloaded
|
||||
// (download not supported).
|
||||
/*
|
||||
* Optional, defaults to 0. If set to 1, the file will be displayed inline, else downloaded
|
||||
* (download not supported).
|
||||
*/
|
||||
inline?: number;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,8 +23,10 @@ export function imageType(d: Uint8Array): IMetrics {
|
||||
return UNSUPPORTED_TYPE;
|
||||
}
|
||||
const d32 = new Uint32Array(d.buffer, d.byteOffset, 6);
|
||||
// PNG: 89 50 4E 47 0D 0A 1A 0A (8 first bytes == magic number for PNG)
|
||||
// + first chunk must be IHDR
|
||||
/*
|
||||
* PNG: 89 50 4E 47 0D 0A 1A 0A (8 first bytes == magic number for PNG)
|
||||
* + first chunk must be IHDR
|
||||
*/
|
||||
if (d32[0] === 0x474E5089 && d32[1] === 0x0A1A0A0D && d32[3] === 0x52444849) {
|
||||
return {
|
||||
mime: 'image/png',
|
||||
|
||||
@@ -137,11 +137,13 @@ export class ImageAddon implements ITerminalAddon, IImageApi {
|
||||
|
||||
// enable size reports
|
||||
if (this._opts.enableSizeReports) {
|
||||
// const windowOptions = terminal.getOption('windowOptions');
|
||||
// windowOptions.getWinSizePixels = true;
|
||||
// windowOptions.getCellSizePixels = true;
|
||||
// windowOptions.getWinSizeChars = true;
|
||||
// terminal.setOption('windowOptions', windowOptions);
|
||||
/*
|
||||
* const windowOptions = terminal.getOption('windowOptions');
|
||||
* windowOptions.getWinSizePixels = true;
|
||||
* windowOptions.getCellSizePixels = true;
|
||||
* windowOptions.getWinSizeChars = true;
|
||||
* terminal.setOption('windowOptions', windowOptions);
|
||||
*/
|
||||
const windowOps = terminal.options.windowOptions ?? {};
|
||||
windowOps.getWinSizePixels = true;
|
||||
windowOps.getCellSizePixels = true;
|
||||
@@ -290,11 +292,13 @@ export class ImageAddon implements ITerminalAddon, IImageApi {
|
||||
if (params[0]) {
|
||||
return true;
|
||||
}
|
||||
// reported features:
|
||||
// 62 - VT220
|
||||
// 4 - SIXEL support
|
||||
// 9 - charsets
|
||||
// 22 - ANSI colors
|
||||
/*
|
||||
* reported features:
|
||||
* 62 - VT220
|
||||
* 4 - SIXEL support
|
||||
* 9 - charsets
|
||||
* 22 - ANSI colors
|
||||
*/
|
||||
if (this._opts.sixelSupport) {
|
||||
this._report(`\x1b[?62;4;9;22c`);
|
||||
return true;
|
||||
@@ -352,8 +356,10 @@ export class ImageAddon implements ITerminalAddon, IImageApi {
|
||||
let width = this._renderer?.dimensions?.css.canvas.width;
|
||||
let height = this._renderer?.dimensions?.css.canvas.height;
|
||||
if (!width || !height) {
|
||||
// for some reason we have no working image renderer
|
||||
// --> fallback to default cell size
|
||||
/*
|
||||
* for some reason we have no working image renderer
|
||||
* --> fallback to default cell size
|
||||
*/
|
||||
const cellSize = CELL_SIZE_DEFAULT;
|
||||
width = (this._terminal?.cols || 80) * cellSize.width;
|
||||
height = (this._terminal?.rows || 24) * cellSize.height;
|
||||
|
||||
@@ -197,10 +197,12 @@ export class ImageRenderer extends Disposable implements IDisposable {
|
||||
const finalWidth = count * width + sx > img.width ? img.width - sx : count * width;
|
||||
const finalHeight = sy + height > img.height ? img.height - sy : height;
|
||||
|
||||
// Floor all pixel offsets to get stable tile mapping without any overflows.
|
||||
// Note: For not pixel perfect aligned cells like in the DOM renderer
|
||||
// this will move a tile slightly to the top/left (subpixel range, thus ignore it).
|
||||
// FIX #34: avoid striping on displays with pixelDeviceRatio != 1 by ceiling height and width
|
||||
/*
|
||||
* Floor all pixel offsets to get stable tile mapping without any overflows.
|
||||
* Note: For not pixel perfect aligned cells like in the DOM renderer
|
||||
* this will move a tile slightly to the top/left (subpixel range, thus ignore it).
|
||||
* FIX #34: avoid striping on displays with pixelDeviceRatio != 1 by ceiling height and width
|
||||
*/
|
||||
ctx.drawImage(
|
||||
img,
|
||||
Math.floor(sx), Math.floor(sy), Math.ceil(finalWidth), Math.ceil(finalHeight),
|
||||
@@ -342,21 +344,27 @@ export class ImageRenderer extends Disposable implements IDisposable {
|
||||
);
|
||||
canvas.classList.add(`xterm-image-layer-${layer}`);
|
||||
const screenElement = this._terminal._core.screenElement;
|
||||
// Use isolation to create a stacking context without overriding z-index,
|
||||
// which would conflict with integrators (e.g. VS Code) that set their
|
||||
// own z-index on the screen element.
|
||||
/*
|
||||
* Use isolation to create a stacking context without overriding z-index,
|
||||
* which would conflict with integrators (e.g. VS Code) that set their
|
||||
* own z-index on the screen element.
|
||||
*/
|
||||
screenElement.style.isolation = 'isolate';
|
||||
if (layer === 'bottom') {
|
||||
// Use z-index:-1 so it paints behind non-positioned text elements.
|
||||
// The screen element needs to be a stacking context (via isolation)
|
||||
// to contain the negative z-index, otherwise it would go behind the
|
||||
// entire terminal.
|
||||
/*
|
||||
* Use z-index:-1 so it paints behind non-positioned text elements.
|
||||
* The screen element needs to be a stacking context (via isolation)
|
||||
* to contain the negative z-index, otherwise it would go behind the
|
||||
* entire terminal.
|
||||
*/
|
||||
canvas.style.zIndex = '-1';
|
||||
screenElement.insertBefore(canvas, screenElement.firstChild);
|
||||
} else {
|
||||
// Explicit z-index ensures the image canvas reliably stacks above
|
||||
// the text layer (DOM renderer rows). z-index: 0 is below the
|
||||
// selection overlay (z-index: 1).
|
||||
/*
|
||||
* Explicit z-index ensures the image canvas reliably stacks above
|
||||
* the text layer (DOM renderer rows). z-index: 0 is below the
|
||||
* selection overlay (z-index: 1).
|
||||
*/
|
||||
canvas.style.zIndex = '0';
|
||||
screenElement.appendChild(canvas);
|
||||
}
|
||||
|
||||
@@ -154,8 +154,10 @@ export class ImageStorage implements IDisposable {
|
||||
for (const spec of this._images.values()) {
|
||||
spec.marker?.dispose();
|
||||
}
|
||||
// NOTE: marker.dispose above already calls ImageBitmap.close
|
||||
// therefore we can just wipe the map here
|
||||
/*
|
||||
* NOTE: marker.dispose above already calls ImageBitmap.close
|
||||
* therefore we can just wipe the map here
|
||||
*/
|
||||
this._images.clear();
|
||||
this._renderer.clearAll();
|
||||
}
|
||||
@@ -306,8 +308,10 @@ export class ImageStorage implements IDisposable {
|
||||
this._delImg(id);
|
||||
}
|
||||
|
||||
// eviction marker:
|
||||
// delete the image when the marker gets disposed
|
||||
/*
|
||||
* eviction marker:
|
||||
* delete the image when the marker gets disposed
|
||||
*/
|
||||
const endMarker = this._terminal.registerMarker(0);
|
||||
endMarker?.onDispose(() => {
|
||||
const spec = this._images.get(imageId);
|
||||
@@ -316,8 +320,10 @@ export class ImageStorage implements IDisposable {
|
||||
}
|
||||
});
|
||||
|
||||
// since markers do not work on alternate for some reason,
|
||||
// we evict images here manually
|
||||
/*
|
||||
* since markers do not work on alternate for some reason,
|
||||
* we evict images here manually
|
||||
*/
|
||||
if (this._terminal.buffer.active.type === 'alternate') {
|
||||
this._evictOnAlternate();
|
||||
}
|
||||
@@ -416,10 +422,12 @@ export class ImageStorage implements IDisposable {
|
||||
const drawCalls: { imgSpec: IImageSpec, tileId: number, col: number, row: number, count: number }[] = [];
|
||||
const placeholderCalls: { col: number, row: number, count: number }[] = [];
|
||||
|
||||
// walk all cells in viewport and collect tiles found
|
||||
// Note: We check _extendedAttrs directly (not just HAS_EXTENDED flag)
|
||||
// because text writes clear the BG flag but leave image tile data intact.
|
||||
// This lets top-layer images survive text overwrites (kitty C=1 behavior).
|
||||
/*
|
||||
* walk all cells in viewport and collect tiles found
|
||||
* Note: We check _extendedAttrs directly (not just HAS_EXTENDED flag)
|
||||
* because text writes clear the BG flag but leave image tile data intact.
|
||||
* This lets top-layer images survive text overwrites (kitty C=1 behavior).
|
||||
*/
|
||||
for (let row = start; row <= end; ++row) {
|
||||
const line = buffer.lines.get(row + buffer.ydisp) as IBufferLineExt;
|
||||
if (!line) return;
|
||||
@@ -492,8 +500,10 @@ export class ImageStorage implements IDisposable {
|
||||
return;
|
||||
}
|
||||
|
||||
// handle only viewport width enlargements, exit all other cases
|
||||
// TODO: needs patch for tile counter
|
||||
/*
|
||||
* handle only viewport width enlargements, exit all other cases
|
||||
* TODO: needs patch for tile counter
|
||||
*/
|
||||
if (this._viewportMetrics.cols >= metrics.cols) {
|
||||
this._viewportMetrics = metrics;
|
||||
return;
|
||||
@@ -581,8 +591,10 @@ export class ImageStorage implements IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Do we need some blob offloading tricks here to avoid early eviction?
|
||||
// also see https://stackoverflow.com/questions/28307789/is-there-any-limitation-on-javascript-max-blob-size
|
||||
/*
|
||||
* TODO: Do we need some blob offloading tricks here to avoid early eviction?
|
||||
* also see https://stackoverflow.com/questions/28307789/is-there-any-limitation-on-javascript-max-blob-size
|
||||
*/
|
||||
private _evictOldest(room: number): number {
|
||||
const used = this._getStoredPixels();
|
||||
let current = used;
|
||||
@@ -605,9 +617,11 @@ export class ImageStorage implements IDisposable {
|
||||
const old = line._extendedAttrs[x];
|
||||
if (old) {
|
||||
if (old.imageId !== undefined) {
|
||||
// found an old ExtendedAttrsImage, since we know that
|
||||
// they are always isolated instances (single cell usage),
|
||||
// we can re-use it and just update their id entries
|
||||
/*
|
||||
* found an old ExtendedAttrsImage, since we know that
|
||||
* they are always isolated instances (single cell usage),
|
||||
* we can re-use it and just update their id entries
|
||||
*/
|
||||
const oldSpec = this._images.get(old.imageId);
|
||||
if (oldSpec) {
|
||||
// early eviction for in-viewport overwrites
|
||||
|
||||
@@ -113,13 +113,17 @@ export class SixelHandler implements IDcsHandler, IResetHandler {
|
||||
* Some helpers to extract current terminal colors.
|
||||
*/
|
||||
|
||||
// get currently active background color from terminal
|
||||
// also respect INVERSE setting
|
||||
/*
|
||||
* get currently active background color from terminal
|
||||
* also respect INVERSE setting
|
||||
*/
|
||||
function extractActiveBg(attr: AttributeData, colors: ReadonlyColorSet | undefined): RGBA8888 {
|
||||
let bg = 0;
|
||||
if (!colors) {
|
||||
// FIXME: theme service is prolly not available yet,
|
||||
// happens if .open() was not called yet (bug in core?)
|
||||
/*
|
||||
* FIXME: theme service is prolly not available yet,
|
||||
* happens if .open() was not called yet (bug in core?)
|
||||
*/
|
||||
return bg;
|
||||
}
|
||||
if (attr.isInverse()) {
|
||||
|
||||
@@ -62,9 +62,11 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
|
||||
// Storage related states
|
||||
|
||||
private _pendingTransmissions: Map<number, IPendingTransmission> = new Map();
|
||||
// Tracks the pending key of the most recently started chunked upload.
|
||||
// Per spec, subsequent chunks only need m= (and optionally q=), without i=.
|
||||
// When a chunk arrives with no i=, this key is used to find the pending upload.
|
||||
/*
|
||||
* Tracks the pending key of the most recently started chunked upload.
|
||||
* Per spec, subsequent chunks only need m= (and optionally q=), without i=.
|
||||
* When a chunk arrives with no i=, this key is used to find the pending upload.
|
||||
*/
|
||||
private _lastPendingKey: number | undefined;
|
||||
|
||||
constructor(
|
||||
@@ -173,9 +175,11 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
|
||||
private _streamPayload(data: Uint32Array, start: number, end: number): void {
|
||||
if (this._aborted) return;
|
||||
|
||||
// Check size limit (compare encoded bytes against pre-calculated limit)
|
||||
// Include cumulative size from pending transmission for multi-chunk images.
|
||||
// Per spec, subsequent chunks may omit i=, so fall back to _lastPendingKey.
|
||||
/*
|
||||
* Check size limit (compare encoded bytes against pre-calculated limit)
|
||||
* Include cumulative size from pending transmission for multi-chunk images.
|
||||
* Per spec, subsequent chunks may omit i=, so fall back to _lastPendingKey.
|
||||
*/
|
||||
const pendingKey = this._parsedCommand?.id ?? this._lastPendingKey ?? 0;
|
||||
const pending = this._pendingTransmissions.get(pendingKey);
|
||||
const previousEncodedSize = pending?.totalEncodedSize ?? 0;
|
||||
@@ -285,9 +289,11 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
|
||||
}
|
||||
this._activeDecoder = null;
|
||||
|
||||
// Handle command first — handlers create Blob/ImageData from imageBytes,
|
||||
// which copies the data. Only then is it safe to release the decoder's
|
||||
// wasm memory that imageBytes points into.
|
||||
/*
|
||||
* Handle command first — handlers create Blob/ImageData from imageBytes,
|
||||
* which copies the data. Only then is it safe to release the decoder's
|
||||
* wasm memory that imageBytes points into.
|
||||
*/
|
||||
const result = this._handleCommandWithBytesAndCmd(finalCmd, imageBytes, decodeError);
|
||||
if (decoder) {
|
||||
decoder.release();
|
||||
@@ -325,10 +331,12 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
|
||||
case KittyAction.PLACEMENT:
|
||||
return this._handlePlacement(cmd);
|
||||
default:
|
||||
// TODO: Implement remaining actions when needed:
|
||||
// - a=f (frame): animation frame operations
|
||||
// - a=a (animation): animation control
|
||||
// - a=c (compose): compose images
|
||||
/*
|
||||
* TODO: Implement remaining actions when needed:
|
||||
* - a=f (frame): animation frame operations
|
||||
* - a=a (animation): animation control
|
||||
* - a=c (compose): compose images
|
||||
*/
|
||||
if (cmd.id !== undefined) {
|
||||
this._sendResponse(cmd.id, 'EINVAL:unsupported action', cmd.quiet ?? 0);
|
||||
}
|
||||
@@ -342,8 +350,10 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
|
||||
switch (action) {
|
||||
case KittyAction.TRANSMIT: {
|
||||
const result = this._handleTransmit(cmd, bytes, decodeError);
|
||||
// Only send response when _handleTransmit didn't already respond
|
||||
// (it handles unsupported transmission medium responses internally)
|
||||
/*
|
||||
* Only send response when _handleTransmit didn't already respond
|
||||
* (it handles unsupported transmission medium responses internally)
|
||||
*/
|
||||
if ((cmd.transmission ?? 'd') === 'd' && cmd.id !== undefined) {
|
||||
if (decodeError) {
|
||||
this._sendResponse(cmd.id, 'EINVAL:invalid base64 data', cmd.quiet ?? 0);
|
||||
@@ -361,10 +371,12 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
|
||||
// a=p ignores any payload — image data was already transmitted
|
||||
return this._handlePlacement(cmd);
|
||||
default:
|
||||
// TODO: Implement remaining actions when needed:
|
||||
// - a=f (frame): animation frame operations
|
||||
// - a=a (animation): animation control
|
||||
// - a=c (compose): compose images
|
||||
/*
|
||||
* TODO: Implement remaining actions when needed:
|
||||
* - a=f (frame): animation frame operations
|
||||
* - a=a (animation): animation control
|
||||
* - a=c (compose): compose images
|
||||
*/
|
||||
if (cmd.id !== undefined) {
|
||||
this._sendResponse(cmd.id, 'EINVAL:unsupported action', cmd.quiet ?? 0);
|
||||
}
|
||||
@@ -390,16 +402,18 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
|
||||
}
|
||||
|
||||
private _handleTransmit(cmd: IKittyCommand, bytes: Uint8Array, decodeError: boolean): boolean {
|
||||
// TODO: Support file-based transmission modes (t=f, t=t, t=s)
|
||||
// Currently only supports direct transmission (t=d, the default).
|
||||
// - t=f (file): Payload is base64-encoded file path. Terminal reads image from that path.
|
||||
// - t=t (temp file): Payload is base64-encoded path in temp directory. Terminal reads, deletes.
|
||||
// - t=s: Payload is base64-encoded POSIX shm name. Terminal reads from shared memory.
|
||||
// These modes require filesystem/IPC access not available in browsers. For Node.js/Electron:
|
||||
// 1. Check cmd.transmission (t key) before treating bytes as image data
|
||||
// 2. For t=f/t/s: decode bytes as UTF-8 string (the path/name), then read file contents
|
||||
// 3. For t=d: treat bytes as image data (current behavior)
|
||||
// When implementing, also update _handleQuery to accept these transmission mediums.
|
||||
/*
|
||||
* TODO: Support file-based transmission modes (t=f, t=t, t=s)
|
||||
* Currently only supports direct transmission (t=d, the default).
|
||||
* - t=f (file): Payload is base64-encoded file path. Terminal reads image from that path.
|
||||
* - t=t (temp file): Payload is base64-encoded path in temp directory. Terminal reads, deletes.
|
||||
* - t=s: Payload is base64-encoded POSIX shm name. Terminal reads from shared memory.
|
||||
* These modes require filesystem/IPC access not available in browsers. For Node.js/Electron:
|
||||
* 1. Check cmd.transmission (t key) before treating bytes as image data
|
||||
* 2. For t=f/t/s: decode bytes as UTF-8 string (the path/name), then read file contents
|
||||
* 3. For t=d: treat bytes as image data (current behavior)
|
||||
* When implementing, also update _handleQuery to accept these transmission mediums.
|
||||
*/
|
||||
const transmission = cmd.transmission ?? 'd';
|
||||
if (transmission !== 'd') {
|
||||
if (cmd.id !== undefined) {
|
||||
@@ -449,9 +463,11 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
|
||||
const id = cmd.id ?? 0;
|
||||
const quiet = cmd.quiet ?? 0;
|
||||
|
||||
// Per spec: reject unsupported transmission mediums (only t=d is supported atm)
|
||||
// TODO: When filesystem support is added (Node.js/Electron), update this to accept
|
||||
// t=f (file), t=t (temp file), and t=s (shared memory) and respond OK for queries.
|
||||
/*
|
||||
* Per spec: reject unsupported transmission mediums (only t=d is supported atm)
|
||||
* TODO: When filesystem support is added (Node.js/Electron), update this to accept
|
||||
* t=f (file), t=t (temp file), and t=s (shared memory) and respond OK for queries.
|
||||
*/
|
||||
const transmission = cmd.transmission ?? 'd';
|
||||
if (transmission !== 'd') {
|
||||
this._sendResponse(id, 'EINVAL:unsupported transmission medium', quiet);
|
||||
@@ -500,9 +516,11 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
|
||||
// Per spec: default delete selector is 'a' (delete all visible placements)
|
||||
const selector = cmd.deleteSelector ?? 'a';
|
||||
|
||||
// TODO: Distinguish lowercase (delete placements only) from uppercase
|
||||
// (delete placements + free stored image data). Currently both variants
|
||||
// free everything since we don't separate stored data from placements.
|
||||
/*
|
||||
* TODO: Distinguish lowercase (delete placements only) from uppercase
|
||||
* (delete placements + free stored image data). Currently both variants
|
||||
* free everything since we don't separate stored data from placements.
|
||||
*/
|
||||
switch (selector) {
|
||||
case 'a':
|
||||
case 'A':
|
||||
@@ -511,10 +529,12 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
|
||||
break;
|
||||
case 'i':
|
||||
case 'I':
|
||||
// TODO: When placement id tracking is implemented (see TODO in
|
||||
// KittyImageStorage), d=i with p=<pid> should delete only that
|
||||
// specific placement, while d=i without p should delete all
|
||||
// placements for the image.
|
||||
/*
|
||||
* TODO: When placement id tracking is implemented (see TODO in
|
||||
* KittyImageStorage), d=i with p=<pid> should delete only that
|
||||
* specific placement, while d=i without p should delete all
|
||||
* placements for the image.
|
||||
*/
|
||||
if (cmd.id !== undefined) {
|
||||
const pending = this._pendingTransmissions.get(cmd.id);
|
||||
if (pending) {
|
||||
@@ -576,8 +596,10 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
|
||||
const cw = this._renderer.dimensions?.css.cell.width || CELL_SIZE_DEFAULT.width;
|
||||
const ch = this._renderer.dimensions?.css.cell.height || CELL_SIZE_DEFAULT.height;
|
||||
|
||||
// Per spec: c/r default to image's natural cell dimensions.
|
||||
// If only one of c/r is specified, compute the other from image aspect ratio.
|
||||
/*
|
||||
* Per spec: c/r default to image's natural cell dimensions.
|
||||
* If only one of c/r is specified, compute the other from image aspect ratio.
|
||||
*/
|
||||
let imgCols: number;
|
||||
let imgRows: number;
|
||||
if (cmd.columns !== undefined && cmd.rows !== undefined) {
|
||||
@@ -613,10 +635,12 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
|
||||
const savedY = buffer.y;
|
||||
const savedYbase = buffer.ybase;
|
||||
|
||||
// Determine layer based on z-index: negative = behind text, 0+ = on top.
|
||||
// When z<0 we always use the bottom layer even without allowTransparency —
|
||||
// the image will simply be hidden behind the opaque text background, which
|
||||
// is the correct behavior (client asked for "behind text").
|
||||
/*
|
||||
* Determine layer based on z-index: negative = behind text, 0+ = on top.
|
||||
* When z<0 we always use the bottom layer even without allowTransparency —
|
||||
* the image will simply be hidden behind the opaque text background, which
|
||||
* is the correct behavior (client asked for "behind text").
|
||||
*/
|
||||
const wantsBottom = cmd.zIndex !== undefined && cmd.zIndex < 0;
|
||||
const layer: ImageLayer = wantsBottom ? 'bottom' : 'top';
|
||||
|
||||
@@ -630,10 +654,12 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
|
||||
const xOffset = Math.min(Math.max(0, cmd.xOffset ?? 0), cw - 1);
|
||||
const yOffset = Math.min(Math.max(0, cmd.yOffset ?? 0), ch - 1);
|
||||
if (xOffset !== 0 || yOffset !== 0) {
|
||||
// Per spec: X/Y is not added to c/r area. When c/r are explicit, the
|
||||
// total placement area remains c*cw × r*ch pixels and the offset image
|
||||
// is clipped to fit. When c/r are unset, the padded canvas determines
|
||||
// the natural cell dimensions.
|
||||
/*
|
||||
* Per spec: X/Y is not added to c/r area. When c/r are explicit, the
|
||||
* total placement area remains c*cw × r*ch pixels and the offset image
|
||||
* is clipped to fit. When c/r are unset, the padded canvas determines
|
||||
* the natural cell dimensions.
|
||||
*/
|
||||
const canvasW = (cmd.columns !== undefined) ? Math.round(imgCols * cw) : bitmap.width + xOffset;
|
||||
const canvasH = (cmd.rows !== undefined) ? Math.round(imgRows * ch) : bitmap.height + yOffset;
|
||||
const offsetCanvas = ImageRenderer.createCanvas(window.document, canvasW, canvasH);
|
||||
@@ -664,9 +690,11 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
|
||||
this._kittyStorage.addImage(image.id, bitmap, true, layer, zIndex);
|
||||
bitmap = undefined; // ownership transferred to storage
|
||||
|
||||
// Kitty cursor movement
|
||||
// Per spec: cursor placed at first column after last image column,
|
||||
// on the last row of the image. C=1 means don't move cursor.
|
||||
/*
|
||||
* Kitty cursor movement
|
||||
* Per spec: cursor placed at first column after last image column,
|
||||
* on the last row of the image. C=1 means don't move cursor.
|
||||
*/
|
||||
if (cmd.cursorMovement === 1) {
|
||||
// C=1: restore cursor to position before image was placed
|
||||
const scrolled = buffer.ybase - savedYbase;
|
||||
@@ -674,8 +702,10 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
|
||||
// Can't restore cursor to scrollback?
|
||||
buffer.y = Math.max(savedY - scrolled, 0);
|
||||
} else {
|
||||
// Default (C=0): advance cursor horizontally past the image
|
||||
// addImage already positioned cursor on the last row via lineFeeds
|
||||
/*
|
||||
* Default (C=0): advance cursor horizontally past the image
|
||||
* addImage already positioned cursor on the last row via lineFeeds
|
||||
*/
|
||||
buffer.x = Math.min(savedX + imgCols, this._coreTerminal.cols);
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -736,9 +766,11 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
|
||||
return createImageBitmap(new ImageData(new Uint8ClampedArray(bytes.buffer as ArrayBuffer, bytes.byteOffset, pixelCount * KittyPixelConstants.BYTES_PER_PIXEL_RGBA), width, height));
|
||||
}
|
||||
|
||||
// RGB→RGBA: interleave alpha using uint32 block processing (4 pixels per iteration).
|
||||
// 3 uint32 reads + 4 uint32 writes per 4 pixels vs 28 byte reads/writes — ~6x faster.
|
||||
// Assumes little-endian (all modern browsers/Node.js).
|
||||
/*
|
||||
* RGB→RGBA: interleave alpha using uint32 block processing (4 pixels per iteration).
|
||||
* 3 uint32 reads + 4 uint32 writes per 4 pixels vs 28 byte reads/writes — ~6x faster.
|
||||
* Assumes little-endian (all modern browsers/Node.js).
|
||||
*/
|
||||
const data = new Uint8ClampedArray(pixelCount * KittyPixelConstants.BYTES_PER_PIXEL_RGBA);
|
||||
const src32 = new Uint32Array(bytes.buffer, bytes.byteOffset, Math.floor(bytes.byteLength / 4));
|
||||
const dst32 = new Uint32Array(data.buffer);
|
||||
|
||||
@@ -7,8 +7,10 @@
|
||||
|
||||
import type Base64Decoder from 'xterm-wasm-parts/lib/base64/Base64Decoder.wasm';
|
||||
|
||||
// Kitty graphics protocol action types.
|
||||
// See: https://sw.kovidgoyal.net/kitty/graphics-protocol/#control-data-reference under key 'a'.
|
||||
/*
|
||||
* Kitty graphics protocol action types.
|
||||
* See: https://sw.kovidgoyal.net/kitty/graphics-protocol/#control-data-reference under key 'a'.
|
||||
*/
|
||||
export const enum KittyAction {
|
||||
TRANSMIT = 't',
|
||||
TRANSMIT_DISPLAY = 'T',
|
||||
@@ -17,23 +19,29 @@ export const enum KittyAction {
|
||||
DELETE = 'd'
|
||||
}
|
||||
|
||||
// Kitty graphics protocol format types.
|
||||
// See: https://sw.kovidgoyal.net/kitty/graphics-protocol/#control-data-reference
|
||||
/*
|
||||
* Kitty graphics protocol format types.
|
||||
* See: https://sw.kovidgoyal.net/kitty/graphics-protocol/#control-data-reference
|
||||
*/
|
||||
export const enum KittyFormat {
|
||||
RGB = 24,
|
||||
RGBA = 32,
|
||||
PNG = 100
|
||||
}
|
||||
|
||||
// Kitty graphics protocol compression types.
|
||||
// See: https://sw.kovidgoyal.net/kitty/graphics-protocol/#control-data-reference under key 'o'.
|
||||
/*
|
||||
* Kitty graphics protocol compression types.
|
||||
* See: https://sw.kovidgoyal.net/kitty/graphics-protocol/#control-data-reference under key 'o'.
|
||||
*/
|
||||
export const enum KittyCompression {
|
||||
NONE = '',
|
||||
ZLIB = 'z'
|
||||
}
|
||||
|
||||
// Kitty graphics protocol control data keys.
|
||||
// See: https://sw.kovidgoyal.net/kitty/graphics-protocol/#control-data-reference
|
||||
/*
|
||||
* Kitty graphics protocol control data keys.
|
||||
* See: https://sw.kovidgoyal.net/kitty/graphics-protocol/#control-data-reference
|
||||
*/
|
||||
export const enum KittyKey {
|
||||
// Action to perform (t=transmit, T=transmit+display, q=query, p=placement, d=delete)
|
||||
ACTION = 'a',
|
||||
@@ -115,8 +123,10 @@ export interface IKittyCommand {
|
||||
payload?: string;
|
||||
}
|
||||
|
||||
// Pending chunked transmission state.
|
||||
// Stores metadata from the first chunk while accumulating decoded payload data.
|
||||
/*
|
||||
* Pending chunked transmission state.
|
||||
* Stores metadata from the first chunk while accumulating decoded payload data.
|
||||
*/
|
||||
export interface IPendingTransmission {
|
||||
// The parsed command from the first chunk (contains action, format, dimensions, etc.)
|
||||
cmd: IKittyCommand;
|
||||
|
||||
@@ -8,27 +8,31 @@ import { ImageStorage } from '../ImageStorage';
|
||||
import { ImageLayer } from '../Types';
|
||||
import { IKittyImageData } from './KittyGraphicsTypes';
|
||||
|
||||
// Kitty-specific image storage controller.
|
||||
//
|
||||
// Wraps shared ImageStorage with kitty protocol semantics:
|
||||
// - tracks transmitted image payloads by kitty image id
|
||||
// - tracks kitty image id -> shared ImageStorage id mapping for displayed images
|
||||
// - mirrors shared-storage evictions into kitty maps
|
||||
// - applies protocol-level undisplayed-image eviction policy
|
||||
/*
|
||||
* Kitty-specific image storage controller.
|
||||
*
|
||||
* Wraps shared ImageStorage with kitty protocol semantics:
|
||||
* - tracks transmitted image payloads by kitty image id
|
||||
* - tracks kitty image id -> shared ImageStorage id mapping for displayed images
|
||||
* - mirrors shared-storage evictions into kitty maps
|
||||
* - applies protocol-level undisplayed-image eviction policy
|
||||
*/
|
||||
export class KittyImageStorage implements IDisposable {
|
||||
private static readonly _maxStoredImages = 256;
|
||||
|
||||
private _nextImageId = 1;
|
||||
private readonly _images: Map<number, IKittyImageData> = new Map();
|
||||
// TODO: Support multiple placements per image. The kitty spec identifies
|
||||
// placements by an (image id, placement id) pair — same i + different p
|
||||
// values should coexist, and same i + same p should replace the prior
|
||||
// placement. Currently we track only one storage entry per kitty image id,
|
||||
// so multiple placements of the same image overwrite each other. Fixing
|
||||
// this requires changing these maps to Map<number, Map<number, number>>
|
||||
// (kittyId → placementId → storageId) and updating addImage/deleteById
|
||||
// accordingly. The underlying shared ImageStorage would also need to
|
||||
// support multiple entries per logical image.
|
||||
/*
|
||||
* TODO: Support multiple placements per image. The kitty spec identifies
|
||||
* placements by an (image id, placement id) pair — same i + different p
|
||||
* values should coexist, and same i + same p should replace the prior
|
||||
* placement. Currently we track only one storage entry per kitty image id,
|
||||
* so multiple placements of the same image overwrite each other. Fixing
|
||||
* this requires changing these maps to Map<number, Map<number, number>>
|
||||
* (kittyId → placementId → storageId) and updating addImage/deleteById
|
||||
* accordingly. The underlying shared ImageStorage would also need to
|
||||
* support multiple entries per logical image.
|
||||
*/
|
||||
private readonly _kittyIdToStorageId: Map<number, number> = new Map();
|
||||
private readonly _storageIdToKittyId: Map<number, number> = new Map();
|
||||
|
||||
@@ -90,10 +94,12 @@ export class KittyImageStorage implements IDisposable {
|
||||
}
|
||||
|
||||
public addImage(kittyId: number, image: HTMLCanvasElement | ImageBitmap, scrolling: boolean, layer: ImageLayer, zIndex: number): void {
|
||||
// Clean up stale reverse-mapping from a previous placement of the same
|
||||
// kitty image. The old shared-storage entry is kept (it may still be
|
||||
// visible on screen) but its reverse mapping is removed so that eviction
|
||||
// of the old entry won't incorrectly delete the kitty image data.
|
||||
/*
|
||||
* Clean up stale reverse-mapping from a previous placement of the same
|
||||
* kitty image. The old shared-storage entry is kept (it may still be
|
||||
* visible on screen) but its reverse mapping is removed so that eviction
|
||||
* of the old entry won't incorrectly delete the kitty image data.
|
||||
*/
|
||||
const oldStorageId = this._kittyIdToStorageId.get(kittyId);
|
||||
if (oldStorageId !== undefined) {
|
||||
this._storageIdToKittyId.delete(oldStorageId);
|
||||
|
||||
@@ -64,8 +64,10 @@ const TESTDATA: ITestData = (() => {
|
||||
};
|
||||
})();
|
||||
const SIXEL_SEQ_0 = introducer(0) + TESTDATA.sixel + FINALIZER;
|
||||
// const SIXEL_SEQ_1 = introducer(1) + TESTDATA.sixel + FINALIZER;
|
||||
// const SIXEL_SEQ_2 = introducer(2) + TESTDATA.sixel + FINALIZER;
|
||||
/*
|
||||
* const SIXEL_SEQ_1 = introducer(1) + TESTDATA.sixel + FINALIZER;
|
||||
* const SIXEL_SEQ_2 = introducer(2) + TESTDATA.sixel + FINALIZER;
|
||||
*/
|
||||
|
||||
// NOTE: the data is loaded as string for easier transport through playwright
|
||||
const TESTDATA_IIP: [string, [number, number]][] = [
|
||||
|
||||
@@ -101,12 +101,14 @@ test.beforeAll(async ({ browser }) => {
|
||||
test.afterAll(async () => await ctx.page.close());
|
||||
|
||||
test.describe('Kitty Graphics Protocol', () => {
|
||||
// TODO: Add tests for larger images with various dimensions
|
||||
// TODO: Add tests for virtual placement (U=1)
|
||||
// TODO: Add tests for animation frames
|
||||
// TODO: Add performance tests for streaming large images
|
||||
// TODO: Implement cursor movement per Kitty spec - cursor should move by cols/rows after placement (unless C=1)
|
||||
// TODO: Distinguish lowercase delete selectors (placement only) from uppercase (placement + free data)
|
||||
/*
|
||||
* TODO: Add tests for larger images with various dimensions
|
||||
* TODO: Add tests for virtual placement (U=1)
|
||||
* TODO: Add tests for animation frames
|
||||
* TODO: Add performance tests for streaming large images
|
||||
* TODO: Implement cursor movement per Kitty spec - cursor should move by cols/rows after placement (unless C=1)
|
||||
* TODO: Distinguish lowercase delete selectors (placement only) from uppercase (placement + free data)
|
||||
*/
|
||||
|
||||
test.beforeEach(async ({}, testInfo) => {
|
||||
await ctx.page.evaluate(`
|
||||
@@ -209,8 +211,10 @@ test.describe('Kitty Graphics Protocol', () => {
|
||||
});
|
||||
|
||||
test('enforces size limit across chunked transmissions', async () => {
|
||||
// Create a custom addon with very small size limit (100 bytes)
|
||||
// The 1x1 PNG is ~164 bytes base64, so 2 chunks should exceed 100
|
||||
/*
|
||||
* Create a custom addon with very small size limit (100 bytes)
|
||||
* The 1x1 PNG is ~164 bytes base64, so 2 chunks should exceed 100
|
||||
*/
|
||||
await ctx.page.evaluate(() => {
|
||||
(window as any).smallLimitAddon = new ImageAddon({
|
||||
kittySupport: true,
|
||||
@@ -1279,8 +1283,10 @@ test.describe('Kitty Graphics Protocol', () => {
|
||||
});
|
||||
|
||||
test('only c specified computes r from aspect ratio', async () => {
|
||||
// 200x100 image (2:1 aspect) with c=10.
|
||||
// Per spec: r = ceil((h/w) * c * cw / ch) = ceil(0.5 * 10 * cw / ch)
|
||||
/*
|
||||
* 200x100 image (2:1 aspect) with c=10.
|
||||
* Per spec: r = ceil((h/w) * c * cw / ch) = ceil(0.5 * 10 * cw / ch)
|
||||
*/
|
||||
await ctx.proxy.write(`\x1b_Ga=t,f=100,i=225;${KITTY_MULTICOLOR_200X100_BASE64}\x1b\\`);
|
||||
await timeout(100);
|
||||
|
||||
@@ -1297,8 +1303,10 @@ test.describe('Kitty Graphics Protocol', () => {
|
||||
});
|
||||
|
||||
test('only r specified computes c from aspect ratio', async () => {
|
||||
// 200x100 image (2:1 aspect) with r=5.
|
||||
// Per spec: c = ceil((w/h) * r * ch / cw) = ceil(2 * 5 * ch / cw)
|
||||
/*
|
||||
* 200x100 image (2:1 aspect) with r=5.
|
||||
* Per spec: c = ceil((w/h) * r * ch / cw) = ceil(2 * 5 * ch / cw)
|
||||
*/
|
||||
await ctx.proxy.write(`\x1b_Ga=t,f=100,i=226;${KITTY_MULTICOLOR_200X100_BASE64}\x1b\\`);
|
||||
await timeout(100);
|
||||
|
||||
@@ -1316,9 +1324,11 @@ test.describe('Kitty Graphics Protocol', () => {
|
||||
});
|
||||
|
||||
test.describe('Cursor positioning', () => {
|
||||
// NOTE: Current tests document ACTUAL behavior (MVP - cursor doesn't move)
|
||||
// Per Kitty spec: cursor placed at first column after last image column,
|
||||
// on the last row of the image. C=1 means don't move cursor.
|
||||
/*
|
||||
* NOTE: Current tests document ACTUAL behavior (MVP - cursor doesn't move)
|
||||
* Per Kitty spec: cursor placed at first column after last image column,
|
||||
* on the last row of the image. C=1 means don't move cursor.
|
||||
*/
|
||||
|
||||
test('cursor advances past 1x1 image', async () => {
|
||||
const cursorBefore = await getCursor();
|
||||
@@ -1376,8 +1386,10 @@ test.describe('Kitty Graphics Protocol', () => {
|
||||
});
|
||||
|
||||
test('cursor should move right by cols when c specified', async () => {
|
||||
// c=5, r computed from 1x1 aspect ratio: r = ceil((1/1) * (5*cw) / ch)
|
||||
// With 1:1 aspect, image height in pixels = 5*cw, so r = ceil(5*cw/ch)
|
||||
/*
|
||||
* c=5, r computed from 1x1 aspect ratio: r = ceil((1/1) * (5*cw) / ch)
|
||||
* With 1:1 aspect, image height in pixels = 5*cw, so r = ceil(5*cw/ch)
|
||||
*/
|
||||
await ctx.proxy.write(`\x1b_Ga=T,f=100,c=5;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
|
||||
await timeout(100);
|
||||
|
||||
@@ -1634,10 +1646,12 @@ test.describe('Kitty Graphics Protocol', () => {
|
||||
});
|
||||
|
||||
test.describe('Pixel verification', () => {
|
||||
// The 200x100 image has 20 colored rectangles in a 10x2 grid.
|
||||
// Each rectangle is 20px wide x 50px tall.
|
||||
// Top row (y=0..49): Red, Orange, Yellow, Lime, Green, Cyan, SkyBlue, Blue, Purple, Magenta
|
||||
// Bottom row (y=50..99): Pink, Brown, Maroon, Olive, Teal, Navy, Gray, DarkGray, LightGray, White
|
||||
/*
|
||||
* The 200x100 image has 20 colored rectangles in a 10x2 grid.
|
||||
* Each rectangle is 20px wide x 50px tall.
|
||||
* Top row (y=0..49): Red, Orange, Yellow, Lime, Green, Cyan, SkyBlue, Blue, Purple, Magenta
|
||||
* Bottom row (y=50..99): Pink, Brown, Maroon, Olive, Teal, Navy, Gray, DarkGray, LightGray, White
|
||||
*/
|
||||
|
||||
test('renders red rectangle at top-left origin (0,0)', async () => {
|
||||
await ctx.proxy.write(`\x1b_Ga=T,f=100;${KITTY_MULTICOLOR_200X100_BASE64}\x1b\\`);
|
||||
@@ -1650,9 +1664,11 @@ test.describe('Kitty Graphics Protocol', () => {
|
||||
await ctx.proxy.write(`\x1b_Ga=T,f=100;${KITTY_MULTICOLOR_200X100_BASE64}\x1b\\`);
|
||||
await timeout(200);
|
||||
|
||||
// Sample center of each top-row rectangle (y=25, x=10,30,50,...,190)
|
||||
// All within the first cell row, so we read from the canvas at cell (0,0)
|
||||
// Red at x=10
|
||||
/*
|
||||
* Sample center of each top-row rectangle (y=25, x=10,30,50,...,190)
|
||||
* All within the first cell row, so we read from the canvas at cell (0,0)
|
||||
* Red at x=10
|
||||
*/
|
||||
deepStrictEqual(await getPixel(0, 0, 10, 25), [255, 0, 0, 255]);
|
||||
// Orange at x=30
|
||||
deepStrictEqual(await getPixel(0, 0, 30, 25), [255, 128, 0, 255]);
|
||||
@@ -1668,8 +1684,10 @@ test.describe('Kitty Graphics Protocol', () => {
|
||||
await ctx.proxy.write(`\x1b_Ga=T,f=100;${KITTY_MULTICOLOR_200X100_BASE64}\x1b\\`);
|
||||
await timeout(200);
|
||||
|
||||
// Bottom row starts at y=50. Center at y=75.
|
||||
// Pink at x=10
|
||||
/*
|
||||
* Bottom row starts at y=50. Center at y=75.
|
||||
* Pink at x=10
|
||||
*/
|
||||
deepStrictEqual(await getPixel(0, 0, 10, 75), [255, 192, 203, 255]);
|
||||
// Brown at x=30
|
||||
deepStrictEqual(await getPixel(0, 0, 30, 75), [165, 42, 42, 255]);
|
||||
@@ -1725,8 +1743,10 @@ test.describe('Kitty Graphics Protocol', () => {
|
||||
});
|
||||
|
||||
test('scales cropped source region to c/r placement rectangle', async () => {
|
||||
// Firefox's createImageBitmap uses different resize sampling, producing
|
||||
// slightly off pixel values compared to Chromium, so skip on Firefox.
|
||||
/*
|
||||
* Firefox's createImageBitmap uses different resize sampling, producing
|
||||
* slightly off pixel values compared to Chromium, so skip on Firefox.
|
||||
*/
|
||||
if (ctx.browser.browserType().name() === 'firefox') {
|
||||
test.skip();
|
||||
}
|
||||
@@ -2137,8 +2157,10 @@ test.describe('Kitty Graphics Protocol', () => {
|
||||
});
|
||||
|
||||
test('memory limit eviction cleans Kitty handler maps', async () => {
|
||||
// Resize terminal to fit 7 non-overlapping 200x100 images without scrolling.
|
||||
// Each image ≈ 29 cols × 8 rows at default cell size.
|
||||
/*
|
||||
* Resize terminal to fit 7 non-overlapping 200x100 images without scrolling.
|
||||
* Each image ≈ 29 cols × 8 rows at default cell size.
|
||||
*/
|
||||
await ctx.page.evaluate(`
|
||||
window.term.reset();
|
||||
window.imageAddon?.dispose();
|
||||
@@ -2147,9 +2169,11 @@ test.describe('Kitty Graphics Protocol', () => {
|
||||
window.term.loadAddon(window.imageAddon);
|
||||
`);
|
||||
|
||||
// storageLimit 0.5 MB = 125,000 pixels. Each 200x100 image = 20,000 pixels.
|
||||
// 6 images = 120K pixels (under limit). 7th triggers eviction (140K > 125K).
|
||||
// Place non-overlapping so tile-count eviction doesn't interfere.
|
||||
/*
|
||||
* storageLimit 0.5 MB = 125,000 pixels. Each 200x100 image = 20,000 pixels.
|
||||
* 6 images = 120K pixels (under limit). 7th triggers eviction (140K > 125K).
|
||||
* Place non-overlapping so tile-count eviction doesn't interfere.
|
||||
*/
|
||||
const positions = [[1, 1], [30, 1], [1, 9], [30, 9], [1, 17], [30, 17]];
|
||||
for (let n = 0; n < 6; n++) {
|
||||
const [c, r] = positions[n];
|
||||
|
||||
@@ -43,9 +43,11 @@ export default async function load(fontFamily: string, cacheSize: number): Promi
|
||||
throw new Error('Permission to access local fonts not granted.');
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
// A `TypeError` indicates the 'local-fonts'
|
||||
// permission is not yet implemented, so
|
||||
// only `throw` if this is _not_ the problem.
|
||||
/*
|
||||
* A `TypeError` indicates the 'local-fonts'
|
||||
* permission is not yet implemented, so
|
||||
* only `throw` if this is _not_ the problem.
|
||||
*/
|
||||
if (err instanceof Error && err.name !== 'TypeError') {
|
||||
throw err;
|
||||
}
|
||||
@@ -89,9 +91,11 @@ export default async function load(fontFamily: string, cacheSize: number): Promi
|
||||
|
||||
const fonts = await fontsPromise;
|
||||
for (const family of parse(fontFamily)) {
|
||||
// If we reach one of the generic font families, the font resolution
|
||||
// will end for the browser and we can't determine the specific font
|
||||
// used. Throw.
|
||||
/*
|
||||
* If we reach one of the generic font families, the font resolution
|
||||
* will end for the browser and we can't determine the specific font
|
||||
* used. Throw.
|
||||
*/
|
||||
if (genericFontFamilies.includes(family)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -88,9 +88,11 @@ class FontImpl implements IFont {
|
||||
glyphIds.push(this._font.charToGlyphIndex(char));
|
||||
}
|
||||
|
||||
// If there are no lookup groups, there's no point looking for
|
||||
// replacements. This gives us a minor performance boost for fonts with
|
||||
// no ligatures
|
||||
/*
|
||||
* If there are no lookup groups, there's no point looking for
|
||||
* replacements. This gives us a minor performance boost for fonts with
|
||||
* no ligatures
|
||||
*/
|
||||
if (this._lookupTrees.length === 0) {
|
||||
return {
|
||||
inputGlyphs: glyphIds,
|
||||
@@ -113,8 +115,10 @@ class FontImpl implements IFont {
|
||||
}
|
||||
|
||||
public findLigatureRanges(text: string): [number, number][] {
|
||||
// Short circuit the process if there are no possible ligatures in the
|
||||
// font
|
||||
/*
|
||||
* Short circuit the process if there are no possible ligatures in the
|
||||
* font
|
||||
*/
|
||||
if (this._lookupTrees.length === 0) {
|
||||
return [];
|
||||
}
|
||||
@@ -170,9 +174,11 @@ class FontImpl implements IFont {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// We don't need to do the lastGlyphIndex tracking here because
|
||||
// reverse processing isn't allowed to replace more than one
|
||||
// character at a time.
|
||||
/*
|
||||
* We don't need to do the lastGlyphIndex tracking here because
|
||||
* reverse processing isn't allowed to replace more than one
|
||||
* character at a time.
|
||||
*/
|
||||
for (let i = nextLookup.last - 1; i >= nextLookup.first; i--) {
|
||||
const result = walkTree(lookup.tree, sequence, i, i);
|
||||
if (result) {
|
||||
@@ -224,8 +230,10 @@ class FontImpl implements IFont {
|
||||
for (let j = 0; j < lookups.length; j++) {
|
||||
const lookupIndex = lookups[j];
|
||||
if (lookupIndex >= start) {
|
||||
// Update the lookup information if it's the one we're
|
||||
// storing or earlier than it.
|
||||
/*
|
||||
* Update the lookup information if it's the one we're
|
||||
* storing or earlier than it.
|
||||
*/
|
||||
if (result.index === null || lookupIndex <= result.index) {
|
||||
result.index = lookupIndex;
|
||||
|
||||
|
||||
@@ -32,9 +32,11 @@ export default function mergeTrees(trees: ILookupTree[]): ILookupTree {
|
||||
function mergeSubtree(mainTree: ILookupTree, mergeTree: ILookupTree, mergedEntries: WeakMap<ILookupTreeEntry, Set<ILookupTreeEntry>>): void {
|
||||
// Need to fix this recursively (and handle lookups)
|
||||
for (const [glyphId, value] of Object.entries(mergeTree.individual)) {
|
||||
// The main tree is guaranteed to have no overlaps between the
|
||||
// individual and range values, so if we match an invididual, there
|
||||
// must not be a range
|
||||
/*
|
||||
* The main tree is guaranteed to have no overlaps between the
|
||||
* individual and range values, so if we match an invididual, there
|
||||
* must not be a range
|
||||
*/
|
||||
if (mainTree.individual[glyphId]) {
|
||||
mergeTreeEntry(mainTree.individual[glyphId], value, mergedEntries);
|
||||
} else {
|
||||
@@ -49,13 +51,17 @@ function mergeSubtree(mainTree: ILookupTree, mergeTree: ILookupTree, mergedEntri
|
||||
|
||||
matched = true;
|
||||
|
||||
// If they overlap, we have to split the range and then
|
||||
// merge the overlap
|
||||
/*
|
||||
* If they overlap, we have to split the range and then
|
||||
* merge the overlap
|
||||
*/
|
||||
mainTree.individual[glyphId] = value;
|
||||
mergeTreeEntry(mainTree.individual[glyphId], cloneEntry(entry), mergedEntries);
|
||||
|
||||
// When there's an overlap, we also have to fix up the range
|
||||
// that we had already processed
|
||||
/*
|
||||
* When there's an overlap, we also have to fix up the range
|
||||
* that we had already processed
|
||||
*/
|
||||
mainTree.range.splice(index, 1);
|
||||
for (const glyph of overlap.second) {
|
||||
if (Array.isArray(glyph)) {
|
||||
@@ -76,9 +82,11 @@ function mergeSubtree(mainTree: ILookupTree, mergeTree: ILookupTree, mergedEntri
|
||||
}
|
||||
|
||||
for (const { range, entry } of mergeTree.range) {
|
||||
// Ranges are more complicated, because they can overlap with
|
||||
// multiple things, individual and range alike. We start by
|
||||
// eliminating ranges that are already present in another range
|
||||
/*
|
||||
* Ranges are more complicated, because they can overlap with
|
||||
* multiple things, individual and range alike. We start by
|
||||
* eliminating ranges that are already present in another range
|
||||
*/
|
||||
let remainingRanges: (number | [number, number])[] = [range];
|
||||
|
||||
for (let index = 0; index < mainTree.range.length; index++) {
|
||||
@@ -123,13 +131,17 @@ function mergeSubtree(mainTree: ILookupTree, mergeTree: ILookupTree, mergedEntri
|
||||
continue;
|
||||
}
|
||||
|
||||
// If they overlap, we have to split the range and then
|
||||
// merge the overlap
|
||||
/*
|
||||
* If they overlap, we have to split the range and then
|
||||
* merge the overlap
|
||||
*/
|
||||
mainTree.individual[remainingRange] = cloneEntry(entry);
|
||||
mergeTreeEntry(mainTree.individual[remainingRange], cloneEntry(resultEntry), mergedEntries);
|
||||
|
||||
// When there's an overlap, we also have to fix up the range
|
||||
// that we had already processed
|
||||
/*
|
||||
* When there's an overlap, we also have to fix up the range
|
||||
* that we had already processed
|
||||
*/
|
||||
mainTree.range.splice(index, 1);
|
||||
index--;
|
||||
|
||||
|
||||
@@ -18,14 +18,18 @@ export default function mergeRange(ranges: [number, number][], newRangeStart: nu
|
||||
return ranges;
|
||||
}
|
||||
if (newRangeEnd <= range[1]) {
|
||||
// Case 2: New range is either wholly contained within the
|
||||
// search range or overlaps with the front of it
|
||||
/*
|
||||
* Case 2: New range is either wholly contained within the
|
||||
* search range or overlaps with the front of it
|
||||
*/
|
||||
range[0] = Math.min(newRangeStart, range[0]);
|
||||
return ranges;
|
||||
}
|
||||
if (newRangeStart < range[1]) {
|
||||
// Case 3: New range either wholly contains the search range
|
||||
// or overlaps with the end of it
|
||||
/*
|
||||
* Case 3: New range either wholly contains the search range
|
||||
* or overlaps with the end of it
|
||||
*/
|
||||
range[0] = Math.min(newRangeStart, range[0]);
|
||||
inRange = true;
|
||||
} else {
|
||||
@@ -34,21 +38,27 @@ export default function mergeRange(ranges: [number, number][], newRangeStart: nu
|
||||
}
|
||||
} else {
|
||||
if (newRangeEnd <= range[0]) {
|
||||
// Case 5: New range extends from previous range but doesn't
|
||||
// reach the current one
|
||||
/*
|
||||
* Case 5: New range extends from previous range but doesn't
|
||||
* reach the current one
|
||||
*/
|
||||
ranges[i - 1][1] = newRangeEnd;
|
||||
return ranges;
|
||||
}
|
||||
if (newRangeEnd <= range[1]) {
|
||||
// Case 6: New range extends from prvious range into the
|
||||
// current range
|
||||
/*
|
||||
* Case 6: New range extends from prvious range into the
|
||||
* current range
|
||||
*/
|
||||
ranges[i - 1][1] = Math.max(newRangeEnd, range[1]);
|
||||
ranges.splice(i, 1);
|
||||
inRange = false;
|
||||
return ranges;
|
||||
}
|
||||
// Case 7: New range extends from previous range past the
|
||||
// end of the current range
|
||||
/*
|
||||
* Case 7: New range extends from previous range past the
|
||||
* end of the current range
|
||||
*/
|
||||
ranges.splice(i, 1);
|
||||
i--;
|
||||
}
|
||||
|
||||
@@ -72,8 +72,10 @@ export default function buildTree(table: ChainingContextualSubstitutionTable.IFo
|
||||
);
|
||||
}
|
||||
|
||||
// When we get to the end, all of the entries we've accumulated
|
||||
// should have a lookup defined
|
||||
/*
|
||||
* When we get to the end, all of the entries we've accumulated
|
||||
* should have a lookup defined
|
||||
*/
|
||||
for (const { entry, substitutions } of currentEntries) {
|
||||
entry.lookup = {
|
||||
substitutions,
|
||||
|
||||
@@ -53,8 +53,10 @@ export default function buildTree(table: ChainingContextualSubstitutionTable.IFo
|
||||
);
|
||||
}
|
||||
|
||||
// When we get to the end, all of the entries we've accumulated
|
||||
// should have a lookup defined
|
||||
/*
|
||||
* When we get to the end, all of the entries we've accumulated
|
||||
* should have a lookup defined
|
||||
*/
|
||||
for (const { entry, substitutions } of currentEntries) {
|
||||
entry.lookup = {
|
||||
substitutions,
|
||||
|
||||
@@ -57,8 +57,10 @@ export function processLookaheadPosition(
|
||||
range: []
|
||||
};
|
||||
|
||||
// All glyphs at this position share ONE entry - lookahead just needs to match,
|
||||
// all paths lead to the same result
|
||||
/*
|
||||
* All glyphs at this position share ONE entry - lookahead just needs to match,
|
||||
* all paths lead to the same result
|
||||
*/
|
||||
const sharedEntry: ILookupTreeEntry = {};
|
||||
|
||||
for (const glyph of glyphs) {
|
||||
@@ -100,8 +102,10 @@ export function processBacktrackPosition(
|
||||
range: []
|
||||
};
|
||||
|
||||
// All glyphs at this position share ONE entry - backtrack just needs to match,
|
||||
// all paths lead to the same result
|
||||
/*
|
||||
* All glyphs at this position share ONE entry - backtrack just needs to match,
|
||||
* all paths lead to the same result
|
||||
*/
|
||||
const sharedEntry: ILookupTreeEntry = {};
|
||||
|
||||
for (const glyph of glyphs) {
|
||||
|
||||
@@ -49,8 +49,10 @@ export function getIndividualSubstitutionGlyph(table: SubstitutionTable, glyphId
|
||||
switch (table.substFormat) {
|
||||
// https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#11-single-substitution-format-1
|
||||
case 1:
|
||||
// TODO: determine if there's a rhyme or reason to the 16-bit
|
||||
// wraparound and if it can ever be a different number
|
||||
/*
|
||||
* TODO: determine if there's a rhyme or reason to the 16-bit
|
||||
* wraparound and if it can ever be a different number
|
||||
*/
|
||||
return (glyphId + table.deltaGlyphId) % (2 ** 16);
|
||||
// https://docs.microsoft.com/en-us/typography/opentype/spec/gsub#12-single-substitution-format-2
|
||||
case 2:
|
||||
|
||||
@@ -15,8 +15,10 @@ const enum LoadingState {
|
||||
FAILED
|
||||
}
|
||||
|
||||
// Caches 100K characters worth of ligatures. In practice this works out to
|
||||
// about 650 KB worth of cache, when a moderate number of ligatures are present.
|
||||
/*
|
||||
* Caches 100K characters worth of ligatures. In practice this works out to
|
||||
* about 650 KB worth of cache, when a moderate number of ligatures are present.
|
||||
*/
|
||||
const CACHE_SIZE = 100000;
|
||||
|
||||
/**
|
||||
@@ -46,8 +48,10 @@ export function enableLigatures(term: Terminal, fallbackLigatures: string[] = []
|
||||
|
||||
load(currentCallFontName, CACHE_SIZE)
|
||||
.then(f => {
|
||||
// Another request may have come in while we were waiting, so make
|
||||
// sure our font is still vaild.
|
||||
/*
|
||||
* Another request may have come in while we were waiting, so make
|
||||
* sure our font is still vaild.
|
||||
*/
|
||||
if (currentCallFontName === term.options.fontFamily) {
|
||||
loadingState = LoadingState.LOADED;
|
||||
font = f;
|
||||
@@ -59,8 +63,10 @@ export function enableLigatures(term: Terminal, fallbackLigatures: string[] = []
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
// Another request may have come in while we were waiting, so make
|
||||
// sure our font is still vaild.
|
||||
/*
|
||||
* Another request may have come in while we were waiting, so make
|
||||
* sure our font is still vaild.
|
||||
*/
|
||||
if (currentCallFontName === term.options.fontFamily) {
|
||||
loadingState = LoadingState.FAILED;
|
||||
if (term.options.logLevel === 'debug') {
|
||||
@@ -73,8 +79,10 @@ export function enableLigatures(term: Terminal, fallbackLigatures: string[] = []
|
||||
}
|
||||
|
||||
if (font && loadingState === LoadingState.LOADED) {
|
||||
// We clone the entries to avoid the internal cache of the ligature finder
|
||||
// getting messed up.
|
||||
/*
|
||||
* We clone the entries to avoid the internal cache of the ligature finder
|
||||
* getting messed up.
|
||||
*/
|
||||
return font.findLigatureRanges(text).map<[number, number]>(
|
||||
range => [range[0], range[1]]
|
||||
);
|
||||
|
||||
@@ -27,8 +27,10 @@ export default function parse(family: string): string[] {
|
||||
const families = [];
|
||||
let currentFamily = '';
|
||||
|
||||
// Work through the input character by character until there are none left.
|
||||
// This lexing and parsing in one pass.
|
||||
/*
|
||||
* Work through the input character by character until there are none left.
|
||||
* This lexing and parsing in one pass.
|
||||
*/
|
||||
while (context.offset < context.input.length) {
|
||||
const char = context.input[context.offset++];
|
||||
switch (char) {
|
||||
@@ -152,13 +154,17 @@ function parseUnicode(context: IParseContext): string {
|
||||
while (context.offset < context.input.length) {
|
||||
const char = context.input[context.offset++];
|
||||
if (/\s/.test(char)) {
|
||||
// The first whitespace character after a unicode escape indicates the end
|
||||
// of the escape and is swallowed.
|
||||
/*
|
||||
* The first whitespace character after a unicode escape indicates the end
|
||||
* of the escape and is swallowed.
|
||||
*/
|
||||
return unicodeToString(str);
|
||||
}
|
||||
if (str.length >= 6 || !/[\dA-Fa-f]/.test(char)) {
|
||||
// If the next character is not a valid hex digit or we have reached the
|
||||
// maximum of 6 digits in the escape, terminate the escape.
|
||||
/*
|
||||
* If the next character is not a valid hex digit or we have reached the
|
||||
* maximum of 6 digits in the escape, terminate the escape.
|
||||
*/
|
||||
context.offset--;
|
||||
return unicodeToString(str);
|
||||
}
|
||||
|
||||
@@ -261,8 +261,10 @@ export class SearchEngine {
|
||||
return;
|
||||
}
|
||||
|
||||
// This will iterate until we find the line start.
|
||||
// When we find it, we will search using the calculated start column.
|
||||
/*
|
||||
* This will iterate until we find the line start.
|
||||
* When we find it, we will search using the calculated start column.
|
||||
*/
|
||||
searchPosition.startRow--;
|
||||
searchPosition.startCol += this._terminal.cols;
|
||||
return this._findInLine(term, searchPosition, searchOptions);
|
||||
@@ -315,8 +317,10 @@ export class SearchEngine {
|
||||
return;
|
||||
}
|
||||
|
||||
// Adjust the row number and search index if needed since a "line" of text can span multiple
|
||||
// rows
|
||||
/*
|
||||
* Adjust the row number and search index if needed since a "line" of text can span multiple
|
||||
* rows
|
||||
*/
|
||||
let startRowOffset = 0;
|
||||
while (startRowOffset < offsets.length - 1 && resultIndex >= offsets[startRowOffset + 1]) {
|
||||
startRowOffset++;
|
||||
@@ -355,8 +359,10 @@ export class SearchEngine {
|
||||
if (char.length > 1) {
|
||||
offset -= char.length - 1;
|
||||
}
|
||||
// Adjust the searchIndex for empty characters following wide unicode
|
||||
// chars (eg. CJK)
|
||||
/*
|
||||
* Adjust the searchIndex for empty characters following wide unicode
|
||||
* chars (eg. CJK)
|
||||
*/
|
||||
const nextCell = line.getCell(i + 1);
|
||||
if (nextCell && nextCell.getWidth() === 0) {
|
||||
offset++;
|
||||
|
||||
@@ -38,8 +38,10 @@ export class SearchLineCache extends Disposable {
|
||||
private _linesCache: LineCacheEntry[] | undefined;
|
||||
private _linesCacheTimeout = this._register(new MutableDisposable());
|
||||
private _linesCacheDisposables = this._register(new MutableDisposable());
|
||||
// Track access to avoid recreating a timeout on every init call which occurs once per search
|
||||
// result (findNext/findPrevious -> _highlightAllMatches -> find loop).
|
||||
/*
|
||||
* Track access to avoid recreating a timeout on every init call which occurs once per search
|
||||
* result (findNext/findPrevious -> _highlightAllMatches -> find loop).
|
||||
*/
|
||||
private _lastAccessTimestamp = 0;
|
||||
|
||||
constructor(private readonly _terminal: Terminal) {
|
||||
|
||||
@@ -84,8 +84,10 @@ function equalBg(cell1: IBufferCell | IAttributeData, cell2: IBufferCell): boole
|
||||
}
|
||||
|
||||
function equalUnderline(cell1: IBufferCell | IAttributeData, cell2: IBufferCell): boolean {
|
||||
// If neither cell has underline, consider them equal regardless of internal underline color
|
||||
// values
|
||||
/*
|
||||
* If neither cell has underline, consider them equal regardless of internal underline color
|
||||
* values
|
||||
*/
|
||||
if (!cell1.isUnderline() && !cell2.isUnderline()) {
|
||||
return true;
|
||||
}
|
||||
@@ -134,14 +136,18 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
||||
private _currentRow: string = '';
|
||||
private _nullCellCount: number = 0;
|
||||
|
||||
// we can see a full colored cell and a null cell that only have background the same style
|
||||
// but the information isn't preserved by null cell itself
|
||||
// so wee need to record it when required.
|
||||
/*
|
||||
* we can see a full colored cell and a null cell that only have background the same style
|
||||
* but the information isn't preserved by null cell itself
|
||||
* so wee need to record it when required.
|
||||
*/
|
||||
private _cursorStyle: IBufferCell = this._buffer.getNullCell();
|
||||
|
||||
// where exact the cursor styles comes from
|
||||
// because we can't copy the cell directly
|
||||
// so we remember where the content comes from instead
|
||||
/*
|
||||
* where exact the cursor styles comes from
|
||||
* because we can't copy the cell directly
|
||||
* so we remember where the content comes from instead
|
||||
*/
|
||||
private _cursorStyleRow: number = 0;
|
||||
private _cursorStyleCol: number = 0;
|
||||
|
||||
@@ -172,8 +178,10 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
||||
private _thisRowLastSecondChar: IBufferCell = this._buffer.getNullCell();
|
||||
private _nextRowFirstChar: IBufferCell = this._buffer.getNullCell();
|
||||
protected _rowEnd(row: number, isLastRow: boolean): void {
|
||||
// if there is colorful empty cell at line end, whe must pad it back, or the the color block
|
||||
// will missing
|
||||
/*
|
||||
* if there is colorful empty cell at line end, whe must pad it back, or the the color block
|
||||
* will missing
|
||||
*/
|
||||
if (this._nullCellCount > 0 && !equalBg(this._cursorStyle, this._backgroundCell)) {
|
||||
// use clear right to set background.
|
||||
this._currentRow += `\u001b[${this._nullCellCount}X`;
|
||||
@@ -206,8 +214,10 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
||||
const nextRowFirstChar = nextLine.getCell(0, this._nextRowFirstChar)!;
|
||||
const isNextRowFirstCharDoubleWidth = nextRowFirstChar.getWidth() > 1;
|
||||
|
||||
// validate whether this line wrap is ever possible
|
||||
// which mean whether cursor can placed at a overflow position (x === row) naturally
|
||||
/*
|
||||
* validate whether this line wrap is ever possible
|
||||
* which mean whether cursor can placed at a overflow position (x === row) naturally
|
||||
*/
|
||||
let isValid = false;
|
||||
|
||||
if (
|
||||
@@ -216,23 +226,31 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
||||
isNextRowFirstCharDoubleWidth ? this._nullCellCount <= 1 : this._nullCellCount <= 0
|
||||
) {
|
||||
if (
|
||||
// the last character can't be null,
|
||||
// you can't use control sequence to move cursor to (x === row)
|
||||
/*
|
||||
* the last character can't be null,
|
||||
* you can't use control sequence to move cursor to (x === row)
|
||||
*/
|
||||
(thisRowLastChar.getChars() || thisRowLastChar.getWidth() === 0) &&
|
||||
// change background of the first wrapped cell also affects BCE
|
||||
// so we mark it as invalid to simply the process to determine line separator
|
||||
/*
|
||||
* change background of the first wrapped cell also affects BCE
|
||||
* so we mark it as invalid to simply the process to determine line separator
|
||||
*/
|
||||
equalBg(thisRowLastChar, nextRowFirstChar)
|
||||
) {
|
||||
isValid = true;
|
||||
}
|
||||
|
||||
if (
|
||||
// the second to last character can't be null if the next line starts with CJK,
|
||||
// you can't use control sequence to move cursor to (x === row)
|
||||
/*
|
||||
* the second to last character can't be null if the next line starts with CJK,
|
||||
* you can't use control sequence to move cursor to (x === row)
|
||||
*/
|
||||
isNextRowFirstCharDoubleWidth &&
|
||||
(thisRowLastSecondChar.getChars() || thisRowLastSecondChar.getWidth() === 0) &&
|
||||
// change background of the first wrapped cell also affects BCE
|
||||
// so we mark it as invalid to simply the process to determine line separator
|
||||
/*
|
||||
* change background of the first wrapped cell also affects BCE
|
||||
* so we mark it as invalid to simply the process to determine line separator
|
||||
*/
|
||||
equalBg(thisRowLastChar, nextRowFirstChar) &&
|
||||
equalBg(thisRowLastSecondChar, nextRowFirstChar)
|
||||
) {
|
||||
@@ -241,8 +259,10 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
||||
}
|
||||
|
||||
if (!isValid) {
|
||||
// force the wrap with magic
|
||||
// insert enough character to force the wrap
|
||||
/*
|
||||
* force the wrap with magic
|
||||
* insert enough character to force the wrap
|
||||
*/
|
||||
rowSeparator = '-'.repeat(this._nullCellCount + 1);
|
||||
// move back and erase next line head
|
||||
rowSeparator += '\u001b[1D\u001b[1X';
|
||||
@@ -256,8 +276,10 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
||||
rowSeparator += '\u001b[B';
|
||||
}
|
||||
|
||||
// This is content and need the be serialized even it is invisible.
|
||||
// without this, wrap will be missing from outputs.
|
||||
/*
|
||||
* This is content and need the be serialized even it is invisible.
|
||||
* without this, wrap will be missing from outputs.
|
||||
*/
|
||||
this._lastContentCursorRow = row + 1;
|
||||
this._lastContentCursorCol = 0;
|
||||
|
||||
@@ -358,8 +380,10 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
||||
|
||||
const sgrSeq = this._diffStyle(cell, this._cursorStyle);
|
||||
|
||||
// the empty cell style is only assumed to be changed when background changed, because
|
||||
// foreground is always 0.
|
||||
/*
|
||||
* the empty cell style is only assumed to be changed when background changed, because
|
||||
* foreground is always 0.
|
||||
*/
|
||||
const styleChanged = isEmptyCell ? !equalBg(this._cursorStyle, cell) : sgrSeq.length > 0;
|
||||
|
||||
/**
|
||||
@@ -398,9 +422,11 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
||||
this._nullCellCount += cell.getWidth();
|
||||
} else {
|
||||
if (this._nullCellCount > 0) {
|
||||
// we can just assume we have same style with previous one here
|
||||
// because style change is handled by previous stage
|
||||
// use move right when background is empty, use clear right when there is background.
|
||||
/*
|
||||
* we can just assume we have same style with previous one here
|
||||
* because style change is handled by previous stage
|
||||
* use move right when background is empty, use clear right when there is background.
|
||||
*/
|
||||
if (equalBg(this._cursorStyle, this._backgroundCell)) {
|
||||
this._currentRow += `\u001b[${this._nullCellCount}C`;
|
||||
} else {
|
||||
@@ -421,8 +447,10 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
||||
protected _serializeString(excludeFinalCursorPosition: boolean): string {
|
||||
let rowEnd = this._allRows.length;
|
||||
|
||||
// the fixup is only required for data without scrollback
|
||||
// because it will always be placed at last line otherwise
|
||||
/*
|
||||
* the fixup is only required for data without scrollback
|
||||
* because it will always be placed at last line otherwise
|
||||
*/
|
||||
if (this._buffer.length - this._firstRow <= this._terminal.rows) {
|
||||
rowEnd = this._lastContentCursorRow + 1 - this._firstRow;
|
||||
this._lastCursorCol = this._lastContentCursorCol;
|
||||
@@ -466,9 +494,11 @@ class StringSerializeHandler extends BaseSerializeHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// Restore the cursor's current style, see https://github.com/xtermjs/xterm.js/issues/3677
|
||||
// HACK: Internal API access since it's awkward to expose this in the API and serialize will
|
||||
// likely be the only consumer
|
||||
/*
|
||||
* Restore the cursor's current style, see https://github.com/xtermjs/xterm.js/issues/3677
|
||||
* HACK: Internal API access since it's awkward to expose this in the API and serialize will
|
||||
* likely be the only consumer
|
||||
*/
|
||||
const curAttrData: IAttributeData = (this._terminal as any)._core._inputHandler._curAttrData;
|
||||
const sgrSeq = this._diffStyle(curAttrData, this._cursorStyle);
|
||||
if (sgrSeq.length > 0) {
|
||||
@@ -581,8 +611,10 @@ export class SerializeAddon implements ITerminalAddon, ISerializeApi {
|
||||
}
|
||||
}
|
||||
|
||||
// Cursor visibility (DECTCEM)
|
||||
// Default: visible
|
||||
/*
|
||||
* Cursor visibility (DECTCEM)
|
||||
* Default: visible
|
||||
*/
|
||||
if (!modes.showCursor) {
|
||||
content += '\x1b[?25l';
|
||||
}
|
||||
|
||||
@@ -416,11 +416,13 @@ test.describe('SerializeAddon', () => {
|
||||
'中文中文',
|
||||
'12中文',
|
||||
'中文12',
|
||||
// This line is going to be wrapped at last character
|
||||
// because it has line length of 11 (1+2*5).
|
||||
// We concat it back without the null cell currently.
|
||||
// But this may be incorrect.
|
||||
// see also #3097
|
||||
/*
|
||||
* This line is going to be wrapped at last character
|
||||
* because it has line length of 11 (1+2*5).
|
||||
* We concat it back without the null cell currently.
|
||||
* But this may be incorrect.
|
||||
* see also #3097
|
||||
*/
|
||||
'1中文中文中'
|
||||
];
|
||||
await ctx.proxy.write(lines.join('\r\n'));
|
||||
@@ -503,9 +505,11 @@ test.describe('SerializeAddon', () => {
|
||||
const MOVE_DOWN = (l: number): string => `\u001b[${l}B`;
|
||||
const MOVE_LEFT = (l: number): string => `\u001b[${l}D`;
|
||||
|
||||
// A line wrap happened after current line.
|
||||
// But there is no content.
|
||||
// so wrap shouldn't even be able to happen.
|
||||
/*
|
||||
* A line wrap happened after current line.
|
||||
* But there is no content.
|
||||
* so wrap shouldn't even be able to happen.
|
||||
*/
|
||||
const segments = [
|
||||
`123456789012345`,
|
||||
MOVE_UP(1),
|
||||
@@ -531,9 +535,11 @@ test.describe('SerializeAddon', () => {
|
||||
10
|
||||
);
|
||||
|
||||
// A line wrap happened after current line.
|
||||
// But there is no content.
|
||||
// so wrap shouldn't even be able to happen.
|
||||
/*
|
||||
* A line wrap happened after current line.
|
||||
* But there is no content.
|
||||
* so wrap shouldn't even be able to happen.
|
||||
*/
|
||||
const lines = [
|
||||
padLines.join('\r\n'),
|
||||
'\r\n',
|
||||
|
||||
@@ -22,9 +22,11 @@ export class UnicodeGraphemeProvider implements IUnicodeVersionProvider {
|
||||
= UnicodeService.createPropertyValue(UC.GRAPHEME_BREAK_Other, 1, false);
|
||||
|
||||
public charProperties(codepoint: number, preceding: UnicodeCharProperties): UnicodeCharProperties {
|
||||
// Optimize the simple ASCII case, under the condition that
|
||||
// UnicodeService.extractCharKind(preceding) === GRAPHEME_BREAK_Other
|
||||
// (which also covers the case that preceding === 0).
|
||||
/*
|
||||
* Optimize the simple ASCII case, under the condition that
|
||||
* UnicodeService.extractCharKind(preceding) === GRAPHEME_BREAK_Other
|
||||
* (which also covers the case that preceding === 0).
|
||||
*/
|
||||
if ((codepoint >= 32 && codepoint < 127) && (preceding >> 3) === 0) {
|
||||
return UnicodeGraphemeProvider._plainNarrowProperties;
|
||||
}
|
||||
|
||||
@@ -21,8 +21,10 @@ test.describe('WebFontsAddon', () => {
|
||||
deepStrictEqual(empty, []);
|
||||
});
|
||||
test.afterEach(async () => {
|
||||
// for font loading tests to work, we have to remove added rules and fonts
|
||||
// to work around the quite aggressive font caching done by the browsers
|
||||
/*
|
||||
* for font loading tests to work, we have to remove added rules and fonts
|
||||
* to work around the quite aggressive font caching done by the browsers
|
||||
*/
|
||||
await ctx.page.evaluate(`
|
||||
document.styleSheets[0].deleteRule(1);
|
||||
document.styleSheets[0].deleteRule(0);
|
||||
@@ -73,8 +75,10 @@ test.describe('WebFontsAddon', () => {
|
||||
});
|
||||
|
||||
test('autoload & relayout from ctor', async ({ browser }) => {
|
||||
// to make this test work, we exclude the default measurement char W (x57) by restricting unicode-range
|
||||
// now the browser will postpone font loading until codepoint is hit --> wrong glyph metrics on first usage
|
||||
/*
|
||||
* to make this test work, we exclude the default measurement char W (x57) by restricting unicode-range
|
||||
* now the browser will postpone font loading until codepoint is hit --> wrong glyph metrics on first usage
|
||||
*/
|
||||
const data = await ctx.page.evaluate(`
|
||||
document.styleSheets[0].insertRule("@font-face {font-family: Kongtext; src: url(/fonts/kongtext.regular.ttf) format('truetype'); unicode-range: U+00A0-00FF}", 0);
|
||||
`);
|
||||
@@ -97,8 +101,10 @@ test.describe('WebFontsAddon', () => {
|
||||
deepStrictEqual(await getDocumentFonts(), [{ family: 'Kongtext', status: 'unloaded' }]);
|
||||
}
|
||||
|
||||
// good case: addon fixes layout for webfont in ctor
|
||||
// the relayout happens async, so wait a bit with a promise
|
||||
/*
|
||||
* good case: addon fixes layout for webfont in ctor
|
||||
* the relayout happens async, so wait a bit with a promise
|
||||
*/
|
||||
await ctx.page.evaluate(`
|
||||
window.helperTerm.dispose();
|
||||
window.helperTerm = new Terminal({fontFamily: '"Kongtext", ' + term.options.fontFamily});
|
||||
|
||||
@@ -73,8 +73,10 @@ export class LinkComputer {
|
||||
continue;
|
||||
}
|
||||
|
||||
// map string positions back to buffer positions
|
||||
// values are 0-based right side excluding
|
||||
/*
|
||||
* map string positions back to buffer positions
|
||||
* values are 0-based right side excluding
|
||||
*/
|
||||
const [startY, startX] = LinkComputer._mapStrIdx(terminal, startLineIndex, 0, match.index);
|
||||
const [endY, endX] = LinkComputer._mapStrIdx(terminal, startY, startX, text.length);
|
||||
|
||||
@@ -172,11 +174,13 @@ export class LinkComputer {
|
||||
if (width) {
|
||||
stringIndex -= chars.length || 1;
|
||||
|
||||
// correct stringIndex for early wrapped wide chars:
|
||||
// - currently only happens at last cell
|
||||
// - cells to the right are reset with chars='' and width=1 in InputHandler.print
|
||||
// - follow-up line must be wrapped and contain wide char at first cell
|
||||
// --> if all these conditions are met, correct stringIndex by +1
|
||||
/*
|
||||
* correct stringIndex for early wrapped wide chars:
|
||||
* - currently only happens at last cell
|
||||
* - cells to the right are reset with chars='' and width=1 in InputHandler.print
|
||||
* - follow-up line must be wrapped and contain wide char at first cell
|
||||
* --> if all these conditions are met, correct stringIndex by +1
|
||||
*/
|
||||
if (i === line.length - 1 && chars === '') {
|
||||
const line = buf.getLine(lineIndex + 1);
|
||||
if (line && line.isWrapped) {
|
||||
|
||||
@@ -7,17 +7,19 @@ import type { Terminal, ITerminalAddon, IDisposable } from '@xterm/xterm';
|
||||
import type { WebLinksAddon as IWebLinksApi } from '@xterm/addon-web-links';
|
||||
import { ILinkProviderOptions, WebLinkProvider } from './WebLinkProvider';
|
||||
|
||||
// consider everthing starting with http:// or https://
|
||||
// up to first whitespace, `"` or `'` as url
|
||||
// NOTE: The repeated end clause is needed to not match a dangling `:`
|
||||
// resembling the old (...)*([^:"\'\\s]) final path clause
|
||||
// additionally exclude early + final:
|
||||
// - unsafe from rfc3986: !*'()
|
||||
// - unsafe chars from rfc1738: {}|\^~[]` (minus [] as we need them for ipv6 adresses, also allow ~)
|
||||
// also exclude as finals:
|
||||
// - final interpunction like ,.!?
|
||||
// - any sort of brackets <>()[]{} (not spec conform, but often used to enclose urls)
|
||||
// - unsafe chars from rfc1738: {}|\^~[]`
|
||||
/*
|
||||
* consider everthing starting with http:// or https://
|
||||
* up to first whitespace, `"` or `'` as url
|
||||
* NOTE: The repeated end clause is needed to not match a dangling `:`
|
||||
* resembling the old (...)*([^:"\'\\s]) final path clause
|
||||
* additionally exclude early + final:
|
||||
* - unsafe from rfc3986: !*'()
|
||||
* - unsafe chars from rfc1738: {}|\^~[]` (minus [] as we need them for ipv6 adresses, also allow ~)
|
||||
* also exclude as finals:
|
||||
* - final interpunction like ,.!?
|
||||
* - any sort of brackets <>()[]{} (not spec conform, but often used to enclose urls)
|
||||
* - unsafe chars from rfc1738: {}|\^~[]`
|
||||
*/
|
||||
const strictUrlRegex = /(https?|HTTPS?):[/]{2}[^\s"'!*(){}|\\\^<>`]*[^\s"':,.!?{}|\\\^~\[\]`()<>]/;
|
||||
|
||||
|
||||
|
||||
@@ -47,8 +47,10 @@ export class CellColorResolver {
|
||||
this.result.bg = cell.bg;
|
||||
this.result.fg = cell.fg;
|
||||
this.result.ext = cell.bg & BgFlags.HAS_EXTENDED ? cell.extended.ext : 0;
|
||||
// Get any foreground/background overrides, this happens on the model to avoid spreading
|
||||
// override logic throughout the different sub-renderers
|
||||
/*
|
||||
* Get any foreground/background overrides, this happens on the model to avoid spreading
|
||||
* override logic throughout the different sub-renderers
|
||||
*/
|
||||
|
||||
// Reset overrides work variables
|
||||
$bg = 0;
|
||||
@@ -186,8 +188,10 @@ export class CellColorResolver {
|
||||
}
|
||||
});
|
||||
|
||||
// Convert any overrides from rgba to the fg/bg packed format. This resolves the inverse flag
|
||||
// ahead of time in order to use the correct cache key
|
||||
/*
|
||||
* Convert any overrides from rgba to the fg/bg packed format. This resolves the inverse flag
|
||||
* ahead of time in order to use the correct cache key
|
||||
*/
|
||||
if ($hasBg) {
|
||||
if ($isSelected) {
|
||||
// Non-RGB attributes from model + force non-dim + override + force RGB color mode
|
||||
@@ -202,8 +206,10 @@ export class CellColorResolver {
|
||||
$fg = (cell.fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | $fg | Attributes.CM_RGB;
|
||||
}
|
||||
|
||||
// Handle case where inverse was specified by only one of bg override or fg override was set,
|
||||
// resolving the other inverse color and setting the inverse flag if needed.
|
||||
/*
|
||||
* Handle case where inverse was specified by only one of bg override or fg override was set,
|
||||
* resolving the other inverse color and setting the inverse flag if needed.
|
||||
*/
|
||||
if (this.result.fg & FgFlags.INVERSE) {
|
||||
if ($hasBg && !$hasFg) {
|
||||
// Resolve bg color type (default color has a different meaning in fg vs bg)
|
||||
|
||||
@@ -13,8 +13,10 @@ import type { ILogService } from 'common/services/Services';
|
||||
interface ITextureAtlasCacheEntry {
|
||||
atlas: ITextureAtlas;
|
||||
config: ICharAtlasConfig;
|
||||
// N.B. This implementation potentially holds onto copies of the terminal forever, so
|
||||
// this may cause memory leaks.
|
||||
/*
|
||||
* N.B. This implementation potentially holds onto copies of the terminal forever, so
|
||||
* this may cause memory leaks.
|
||||
*/
|
||||
ownedBy: Terminal[];
|
||||
}
|
||||
|
||||
|
||||
@@ -25,8 +25,10 @@ export function generateConfig(deviceCellWidth: number, deviceCellHeight: number
|
||||
scrollbarSliderBackground: NULL_COLOR,
|
||||
scrollbarSliderHoverBackground: NULL_COLOR,
|
||||
scrollbarSliderActiveBackground: NULL_COLOR,
|
||||
// For the static char atlas, we only use the first 16 colors, but we need all 256 for the
|
||||
// dynamic character atlas.
|
||||
/*
|
||||
* For the static char atlas, we only use the first 16 colors, but we need all 256 for the
|
||||
* dynamic character atlas.
|
||||
*/
|
||||
ansi: colors.ansi.slice(),
|
||||
contrastCache: colors.contrastCache,
|
||||
halfContrastCache: colors.halfContrastCache
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
import { isFirefox, isLegacyEdge } from 'common/Platform';
|
||||
|
||||
export const DIM_OPACITY = 0.5;
|
||||
// The text baseline is set conditionally by browser. Using 'ideographic' for Firefox or Legacy Edge
|
||||
// would result in truncated text (Issue 3353). Using 'bottom' for Chrome would result in slightly
|
||||
// unaligned Powerline fonts (PR 3356#issuecomment-850928179).
|
||||
/*
|
||||
* The text baseline is set conditionally by browser. Using 'ideographic' for Firefox or Legacy Edge
|
||||
* would result in truncated text (Issue 3353). Using 'bottom' for Chrome would result in slightly
|
||||
* unaligned Powerline fonts (PR 3356#issuecomment-850928179).
|
||||
*/
|
||||
export const TEXT_BASELINE: CanvasTextBaseline = isFirefox || isLegacyEdge ? 'bottom' : 'ideographic';
|
||||
|
||||
@@ -87,13 +87,17 @@ export class CursorBlinkStateManager {
|
||||
this._blinkInterval = undefined;
|
||||
}
|
||||
|
||||
// Setup the initial timeout which will hide the cursor, this is done before
|
||||
// the regular interval is setup in order to support restarting the blink
|
||||
// animation in a lightweight way (without thrashing clearInterval and
|
||||
// setInterval).
|
||||
/*
|
||||
* Setup the initial timeout which will hide the cursor, this is done before
|
||||
* the regular interval is setup in order to support restarting the blink
|
||||
* animation in a lightweight way (without thrashing clearInterval and
|
||||
* setInterval).
|
||||
*/
|
||||
this._blinkStartTimeout = this._coreBrowserService.window.setTimeout(() => {
|
||||
// Check if another animation restart was requested while this was being
|
||||
// started
|
||||
/*
|
||||
* Check if another animation restart was requested while this was being
|
||||
* started
|
||||
*/
|
||||
if (this._animationTimeRestarted) {
|
||||
const time = Constants.BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted);
|
||||
this._animationTimeRestarted = undefined;
|
||||
@@ -114,8 +118,10 @@ export class CursorBlinkStateManager {
|
||||
this._blinkInterval = this._coreBrowserService.window.setInterval(() => {
|
||||
// Adjust the animation time if it was restarted
|
||||
if (this._animationTimeRestarted) {
|
||||
// calc time diff
|
||||
// Make restart interval do a setTimeout initially?
|
||||
/*
|
||||
* calc time diff
|
||||
* Make restart interval do a setTimeout initially?
|
||||
*/
|
||||
const time = Constants.BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted);
|
||||
this._animationTimeRestarted = undefined;
|
||||
this._restartInterval(time);
|
||||
|
||||
@@ -6,10 +6,12 @@
|
||||
import { toDisposable, IDisposable } from 'common/Lifecycle';
|
||||
|
||||
export function observeDevicePixelDimensions(element: HTMLElement, parentWindow: Window & typeof globalThis, callback: (deviceWidth: number, deviceHeight: number) => void): IDisposable {
|
||||
// Observe any resizes to the element and extract the actual pixel size of the element if the
|
||||
// devicePixelContentBoxSize API is supported. This allows correcting rounding errors when
|
||||
// converting between CSS pixels and device pixels which causes blurry rendering when device
|
||||
// pixel ratio is not a round number.
|
||||
/*
|
||||
* Observe any resizes to the element and extract the actual pixel size of the element if the
|
||||
* devicePixelContentBoxSize API is supported. This allows correcting rounding errors when
|
||||
* converting between CSS pixels and device pixels which causes blurry rendering when device
|
||||
* pixel ratio is not a round number.
|
||||
*/
|
||||
let observer: ResizeObserver | undefined = new parentWindow.ResizeObserver((entries) => {
|
||||
const entry = entries.find((entry) => entry.target === element);
|
||||
if (!entry) {
|
||||
|
||||
@@ -148,9 +148,11 @@ export class GlyphRenderer extends Disposable {
|
||||
gl.enableVertexAttribArray(VertexAttribLocations.UNIT_QUAD);
|
||||
gl.vertexAttribPointer(VertexAttribLocations.UNIT_QUAD, 2, this._gl.FLOAT, false, 0, 0);
|
||||
|
||||
// Setup the unit quad element array buffer, this points to indices in
|
||||
// unitQuadVertices to allow is to draw 2 triangles from the vertices via a
|
||||
// triangle strip
|
||||
/*
|
||||
* Setup the unit quad element array buffer, this points to indices in
|
||||
* unitQuadVertices to allow is to draw 2 triangles from the vertices via a
|
||||
* triangle strip
|
||||
*/
|
||||
const unitQuadElementIndices = new Uint8Array([0, 1, 2, 3]);
|
||||
const elementIndicesBuffer = gl.createBuffer();
|
||||
this._register(toDisposable(() => gl.deleteBuffer(elementIndicesBuffer)));
|
||||
@@ -189,8 +191,10 @@ export class GlyphRenderer extends Disposable {
|
||||
gl.uniform1iv(this._textureLocation, textureUnits);
|
||||
gl.uniformMatrix4fv(this._projectionLocation, false, PROJECTION_MATRIX);
|
||||
|
||||
// Setup 1x1 red pixel textures for all potential atlas pages, if one of these invalid textures
|
||||
// is ever drawn it will show characters as red rectangles.
|
||||
/*
|
||||
* Setup 1x1 red pixel textures for all potential atlas pages, if one of these invalid textures
|
||||
* is ever drawn it will show characters as red rectangles.
|
||||
*/
|
||||
this._atlasTextures = [];
|
||||
for (let i = 0; i < TextureAtlas.maxAtlasPages; i++) {
|
||||
const glTexture = new GLTexture(throwIfFalsy(gl.createTexture()));
|
||||
@@ -216,18 +220,22 @@ export class GlyphRenderer extends Disposable {
|
||||
}
|
||||
|
||||
public updateCell(x: number, y: number, code: number, bg: number, fg: number, ext: number, chars: string, width: number, lastBg: number): void {
|
||||
// Since this function is called for every cell (`rows*cols`), it must be very optimized. It
|
||||
// should not instantiate any variables unless a new glyph is drawn to the cache where the
|
||||
// slight slowdown is acceptable for the developer ergonomics provided as it's a once of for
|
||||
// each glyph.
|
||||
/*
|
||||
* Since this function is called for every cell (`rows*cols`), it must be very optimized. It
|
||||
* should not instantiate any variables unless a new glyph is drawn to the cache where the
|
||||
* slight slowdown is acceptable for the developer ergonomics provided as it's a once of for
|
||||
* each glyph.
|
||||
*/
|
||||
this._updateCell(this._vertices.attributes, x, y, code, bg, fg, ext, chars, width, lastBg);
|
||||
}
|
||||
|
||||
private _updateCell(array: Float32Array, x: number, y: number, code: number | undefined, bg: number, fg: number, ext: number, chars: string, width: number, lastBg: number): void {
|
||||
$i = (y * this._terminal.cols + x) * Constants.INDICES_PER_CELL;
|
||||
|
||||
// Exit early if this is a null character, allow space character to continue as it may have
|
||||
// underline/strikethrough styles
|
||||
/*
|
||||
* Exit early if this is a null character, allow space character to continue as it may have
|
||||
* underline/strikethrough styles
|
||||
*/
|
||||
if (code === NULL_CELL_CODE || code === undefined/* This is used for the right side of wide chars */) {
|
||||
array.fill(0, $i, $i + Constants.INDICES_PER_CELL - 1 - Constants.CELL_POSITION_INDICES);
|
||||
return;
|
||||
@@ -279,8 +287,10 @@ export class GlyphRenderer extends Disposable {
|
||||
}
|
||||
// a_cellpos only changes on resize
|
||||
|
||||
// Reduce scale horizontally for wide glyphs printed in cells that would overlap with the
|
||||
// following cell (ie. the width is not 2).
|
||||
/*
|
||||
* Reduce scale horizontally for wide glyphs printed in cells that would overlap with the
|
||||
* following cell (ie. the width is not 2).
|
||||
*/
|
||||
if (this._optionsService.rawOptions.rescaleOverlappingGlyphs) {
|
||||
if (allowRescaling(code, width, $glyph.size.x, this._dimensions.device.cell.width)) {
|
||||
array[$i + 2] = (this._dimensions.device.cell.width - 1) / this._dimensions.device.canvas.width; // - 1 to improve readability
|
||||
@@ -339,13 +349,15 @@ export class GlyphRenderer extends Disposable {
|
||||
this._activeBuffer = (this._activeBuffer + 1) % 2;
|
||||
const activeBuffer = this._vertices.attributesBuffers[this._activeBuffer];
|
||||
|
||||
// Copy data for each cell of each line up to its line length (the last non-whitespace cell)
|
||||
// from the attributes buffer into activeBuffer, which is the one that gets bound to the GPU.
|
||||
// The reasons for this are as follows:
|
||||
// - So the active buffer can be alternated so we don't get blocked on rendering finishing
|
||||
// - To copy either the normal attributes buffer or the selection attributes buffer when there
|
||||
// is a selection
|
||||
// - So we don't send vertices for all the line-ending whitespace to the GPU
|
||||
/*
|
||||
* Copy data for each cell of each line up to its line length (the last non-whitespace cell)
|
||||
* from the attributes buffer into activeBuffer, which is the one that gets bound to the GPU.
|
||||
* The reasons for this are as follows:
|
||||
* - So the active buffer can be alternated so we don't get blocked on rendering finishing
|
||||
* - To copy either the normal attributes buffer or the selection attributes buffer when there
|
||||
* is a selection
|
||||
* - So we don't send vertices for all the line-ending whitespace to the GPU
|
||||
*/
|
||||
let bufferLength = 0;
|
||||
for (let y = 0; y < renderModel.lineLengths.length; y++) {
|
||||
const si = y * this._terminal.cols * Constants.INDICES_PER_CELL;
|
||||
@@ -358,9 +370,11 @@ export class GlyphRenderer extends Disposable {
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, activeBuffer.subarray(0, bufferLength), gl.STREAM_DRAW);
|
||||
|
||||
// Bind the atlas page texture if they have changed. AtlasPage.version is globally
|
||||
// monotonic, so a page object swap at the same index (which happens after a page merge)
|
||||
// is detected by the same comparison.
|
||||
/*
|
||||
* Bind the atlas page texture if they have changed. AtlasPage.version is globally
|
||||
* monotonic, so a page object swap at the same index (which happens after a page merge)
|
||||
* is detected by the same comparison.
|
||||
*/
|
||||
for (let i = 0; i < this._atlas.pages.length; i++) {
|
||||
if (this._atlas.pages[i].version !== this._atlasTextures[i].version) {
|
||||
this._bindAtlasPageTexture(gl, this._atlas, i);
|
||||
|
||||
@@ -114,9 +114,11 @@ export class RectangleRenderer extends Disposable {
|
||||
gl.enableVertexAttribArray(VertexAttribLocations.UNIT_QUAD);
|
||||
gl.vertexAttribPointer(VertexAttribLocations.UNIT_QUAD, 2, this._gl.FLOAT, false, 0, 0);
|
||||
|
||||
// Setup the unit quad element array buffer, this points to indices in
|
||||
// unitQuadVertices to allow is to draw 2 triangles from the vertices via a
|
||||
// triangle strip
|
||||
/*
|
||||
* Setup the unit quad element array buffer, this points to indices in
|
||||
* unitQuadVertices to allow is to draw 2 triangles from the vertices via a
|
||||
* triangle strip
|
||||
*/
|
||||
const unitQuadElementIndices = new Uint8Array([0, 1, 2, 3]);
|
||||
const elementIndicesBuffer = gl.createBuffer();
|
||||
this._register(toDisposable(() => gl.deleteBuffer(elementIndicesBuffer)));
|
||||
|
||||
@@ -151,13 +151,17 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
}
|
||||
|
||||
private _createNewPage(): AtlasPage {
|
||||
// Try merge the set of the 4 most used pages of the largest size. This is is deferred to a
|
||||
// microtask to ensure it does not interrupt textures that will be rendered in the current
|
||||
// animation frame which would result in blank rendered areas. This is actually not that
|
||||
// expensive relative to drawing the glyphs, so there is no need to wait for an idle callback.
|
||||
/*
|
||||
* Try merge the set of the 4 most used pages of the largest size. This is is deferred to a
|
||||
* microtask to ensure it does not interrupt textures that will be rendered in the current
|
||||
* animation frame which would result in blank rendered areas. This is actually not that
|
||||
* expensive relative to drawing the glyphs, so there is no need to wait for an idle callback.
|
||||
*/
|
||||
if (TextureAtlas.maxAtlasPages && this._pages.length >= Math.max(4, TextureAtlas.maxAtlasPages)) {
|
||||
// Find the set of the largest 4 images, below the maximum size, with the highest
|
||||
// percentages used
|
||||
/*
|
||||
* Find the set of the largest 4 images, below the maximum size, with the highest
|
||||
* percentages used
|
||||
*/
|
||||
const pagesBySize = this._pages.filter(e => {
|
||||
return e.canvas.width * 2 <= (TextureAtlas.maxTextureSize || Constants.FORCED_MAX_TEXTURE_SIZE);
|
||||
}).sort((a, b) => {
|
||||
@@ -180,8 +184,10 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
// Gather details of the merge
|
||||
const mergingPages = pagesBySize.slice(sameSizeI, sameSizeI + 4);
|
||||
|
||||
// Only proceed with merge if we have exactly 4 same-sized pages. If not, we cannot
|
||||
// effectively reduce page count and merging would cause issues.
|
||||
/*
|
||||
* Only proceed with merge if we have exactly 4 same-sized pages. If not, we cannot
|
||||
* effectively reduce page count and merging would cause issues.
|
||||
*/
|
||||
if (mergingPages.length < 4 || mergingPages.some(p => p.canvas.width !== mergingPages[0].canvas.width)) {
|
||||
const newPage = new AtlasPage(this._document, this._textureSize);
|
||||
this._pages.push(newPage);
|
||||
@@ -294,9 +300,11 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
|
||||
private _getBackgroundColor(bgColorMode: number, bgColor: number, inverse: boolean, dim: boolean): IColor {
|
||||
if (this._config.allowTransparency) {
|
||||
// The background color might have some transparency, so we need to render it as fully
|
||||
// transparent in the atlas. Otherwise we'd end up drawing the transparent background twice
|
||||
// around the anti-aliased edges of the glyph, and it would look too dark.
|
||||
/*
|
||||
* The background color might have some transparency, so we need to render it as fully
|
||||
* transparent in the atlas. Otherwise we'd end up drawing the transparent background twice
|
||||
* around the anti-aliased edges of the glyph, and it would look too dark.
|
||||
*/
|
||||
return NULL_COLOR;
|
||||
}
|
||||
|
||||
@@ -418,8 +426,10 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
|
||||
const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, inverse);
|
||||
const fgRgba = this._resolveForegroundRgba(fgColorMode, fgColor, inverse, bold);
|
||||
// Dim cells only require half the contrast, otherwise they wouldn't be distinguishable from
|
||||
// non-dim cells
|
||||
/*
|
||||
* Dim cells only require half the contrast, otherwise they wouldn't be distinguishable from
|
||||
* non-dim cells
|
||||
*/
|
||||
const result = rgba.ensureContrastRatio(bgRgba, fgRgba, this._config.minimumContrastRatio / (dim ? 2 : 1));
|
||||
|
||||
if (!result) {
|
||||
@@ -447,20 +457,26 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
private _drawToCache(codeOrChars: number | string, bg: number, fg: number, ext: number, restrictToCellHeight: boolean, domContainer: HTMLElement | undefined): IRasterizedGlyph {
|
||||
const chars = typeof codeOrChars === 'number' ? String.fromCharCode(codeOrChars) : codeOrChars;
|
||||
|
||||
// Uncomment for debugging
|
||||
// console.log(`draw to cache "${chars}"`, bg, fg, ext);
|
||||
/*
|
||||
* Uncomment for debugging
|
||||
* console.log(`draw to cache "${chars}"`, bg, fg, ext);
|
||||
*/
|
||||
|
||||
// Attach the canvas to the DOM in order to inherit font-feature-settings
|
||||
// from the parent elements. This is necessary for ligatures and variants to
|
||||
// work.
|
||||
/*
|
||||
* Attach the canvas to the DOM in order to inherit font-feature-settings
|
||||
* from the parent elements. This is necessary for ligatures and variants to
|
||||
* work.
|
||||
*/
|
||||
if (domContainer && this._tmpCanvas.parentElement !== domContainer) {
|
||||
this._tmpCanvas.style.display = 'none';
|
||||
domContainer.append(this._tmpCanvas);
|
||||
}
|
||||
|
||||
// Allow 1 cell width per character, with a minimum of 2 (CJK), plus some padding. This is used
|
||||
// to draw the glyph to the canvas as well as to restrict the bounding box search to ensure
|
||||
// giant ligatures (eg. =====>) don't impact overall performance.
|
||||
/*
|
||||
* Allow 1 cell width per character, with a minimum of 2 (CJK), plus some padding. This is used
|
||||
* to draw the glyph to the canvas as well as to restrict the bounding box search to ensure
|
||||
* giant ligatures (eg. =====>) don't impact overall performance.
|
||||
*/
|
||||
const allowedWidth = Math.min(this._config.deviceCellWidth * Math.max(chars.length, 2) + TMP_CANVAS_GLYPH_PADDING * 2, this._config.deviceMaxTextureSize);
|
||||
if (this._tmpCanvas.width < allowedWidth) {
|
||||
this._tmpCanvas.width = allowedWidth;
|
||||
@@ -503,8 +519,10 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
|
||||
// draw the background
|
||||
const backgroundColor = this._getBackgroundColor(bgColorMode, bgColor, inverse, dim);
|
||||
// Use a 'copy' composite operation to clear any existing glyph out of _tmpCtxWithAlpha,
|
||||
// regardless of transparency in backgroundColor
|
||||
/*
|
||||
* Use a 'copy' composite operation to clear any existing glyph out of _tmpCtxWithAlpha,
|
||||
* regardless of transparency in backgroundColor
|
||||
*/
|
||||
this._tmpCtx.globalCompositeOperation = 'copy';
|
||||
this._tmpCtx.fillStyle = backgroundColor.css;
|
||||
this._tmpCtx.fillRect(0, 0, this._tmpCanvas.width, this._tmpCanvas.height);
|
||||
@@ -532,9 +550,11 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
customGlyph = tryDrawCustomGlyph(this._tmpCtx, chars, padding, padding, this._config.deviceCellWidth, this._config.deviceCellHeight, this._config.deviceCharWidth, this._config.deviceCharHeight, this._config.fontSize, this._config.devicePixelRatio, backgroundColor.css, variantOffset);
|
||||
}
|
||||
|
||||
// Whether to clear pixels based on a threshold difference between the glyph color and the
|
||||
// background color. This should be disabled when the glyph contains multiple colors such as
|
||||
// underline colors to prevent important colors could get cleared.
|
||||
/*
|
||||
* Whether to clear pixels based on a threshold difference between the glyph color and the
|
||||
* background color. This should be disabled when the glyph contains multiple colors such as
|
||||
* underline colors to prevent important colors could get cleared.
|
||||
*/
|
||||
let enableClearThresholdCheck = !powerlineGlyph;
|
||||
|
||||
let chWidth: number;
|
||||
@@ -599,8 +619,10 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
clipRegion.rect(xChLeft, yTop, this._config.deviceCellWidth, yBot - yTop);
|
||||
this._tmpCtx.clip(clipRegion);
|
||||
|
||||
// Draw a zigzag pattern, this is derived from the SVG used in monaco for the same
|
||||
// style. The viewbox is 6x3 so scale it using that.
|
||||
/*
|
||||
* Draw a zigzag pattern, this is derived from the SVG used in monaco for the same
|
||||
* style. The viewbox is 6x3 so scale it using that.
|
||||
*/
|
||||
const cellW = this._config.deviceCellWidth;
|
||||
const curlyH = (yBot - yTop);
|
||||
const scaleX = cellW / 6;
|
||||
@@ -671,15 +693,21 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
}
|
||||
this._tmpCtx.restore();
|
||||
|
||||
// Draw stroke in the background color for non custom characters in order to give an outline
|
||||
// between the text and the underline. Only do this when font size is >= 12 as the underline
|
||||
// looks odd when the font size is too small
|
||||
/*
|
||||
* Draw stroke in the background color for non custom characters in order to give an outline
|
||||
* between the text and the underline. Only do this when font size is >= 12 as the underline
|
||||
* looks odd when the font size is too small
|
||||
*/
|
||||
if (!customGlyph && this._config.fontSize >= 12) {
|
||||
// This only works when transparency is disabled because it's not clear how to clear stroked
|
||||
// text
|
||||
/*
|
||||
* This only works when transparency is disabled because it's not clear how to clear stroked
|
||||
* text
|
||||
*/
|
||||
if (!this._config.allowTransparency && chars !== ' ') {
|
||||
// Measure the text, only draw the stroke if there is a descent beyond an alphabetic text
|
||||
// baseline
|
||||
/*
|
||||
* Measure the text, only draw the stroke if there is a descent beyond an alphabetic text
|
||||
* baseline
|
||||
*/
|
||||
this._tmpCtx.save();
|
||||
this._tmpCtx.textBaseline = 'alphabetic';
|
||||
const metrics = this._tmpCtx.measureText(chars);
|
||||
@@ -687,9 +715,11 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
if ('actualBoundingBoxDescent' in metrics && metrics.actualBoundingBoxDescent > 0) {
|
||||
// This translates to 1/2 the line width in either direction
|
||||
this._tmpCtx.save();
|
||||
// Clip the region to only draw in valid pixels near the underline to avoid a slight
|
||||
// outline around the whole glyph, as well as additional pixels in the glyph at the top
|
||||
// which would increase GPU memory demands
|
||||
/*
|
||||
* Clip the region to only draw in valid pixels near the underline to avoid a slight
|
||||
* outline around the whole glyph, as well as additional pixels in the glyph at the top
|
||||
* which would increase GPU memory demands
|
||||
*/
|
||||
const clipRegion = new Path2D();
|
||||
clipRegion.rect(xLeft, yTop - Math.ceil(lineWidth / 2), this._config.deviceCellWidth * chWidth, yBot - yTop + Math.ceil(lineWidth / 2));
|
||||
this._tmpCtx.clip(clipRegion);
|
||||
@@ -719,8 +749,10 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
this._tmpCtx.fillText(chars, padding, padding + this._config.deviceCharHeight);
|
||||
}
|
||||
|
||||
// If this character is underscore and beyond the cell bounds, shift it up until it is visible
|
||||
// even on the bottom row, try for a maximum of 5 pixels.
|
||||
/*
|
||||
* If this character is underscore and beyond the cell bounds, shift it up until it is visible
|
||||
* even on the bottom row, try for a maximum of 5 pixels.
|
||||
*/
|
||||
if (chars === '_' && !this._config.allowTransparency) {
|
||||
let isBeyondCellBounds = clearColor(this._tmpCtx.getImageData(padding, padding, this._config.deviceCellWidth, this._config.deviceCellHeight), backgroundColor, foregroundColor, enableClearThresholdCheck);
|
||||
if (isBeyondCellBounds) {
|
||||
@@ -752,8 +784,10 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
|
||||
this._tmpCtx.restore();
|
||||
|
||||
// clear the background from the character to avoid issues with drawing over the previous
|
||||
// character if it extends past it's bounds
|
||||
/*
|
||||
* clear the background from the character to avoid issues with drawing over the previous
|
||||
* character if it extends past it's bounds
|
||||
*/
|
||||
const imageData = this._tmpCtx.getImageData(
|
||||
0, 0, this._tmpCanvas.width, this._tmpCanvas.height
|
||||
);
|
||||
@@ -796,12 +830,16 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This algorithm could be simplified:
|
||||
// - Search for the page with ROW_PIXEL_THRESHOLD in mind
|
||||
// - Keep track of current/fixed rows in a Map
|
||||
/*
|
||||
* TODO: This algorithm could be simplified:
|
||||
* - Search for the page with ROW_PIXEL_THRESHOLD in mind
|
||||
* - Keep track of current/fixed rows in a Map
|
||||
*/
|
||||
|
||||
// Replace the best current row with a fixed row if there is one at least as good as the
|
||||
// current row. Search in reverse to prioritize filling in older pages.
|
||||
/*
|
||||
* Replace the best current row with a fixed row if there is one at least as good as the
|
||||
* current row. Search in reverse to prioritize filling in older pages.
|
||||
*/
|
||||
for (let i = this._activePages.length - 1; i >= 0; i--) {
|
||||
for (const row of this._activePages[i].fixedRows) {
|
||||
if (row.height <= activeRow.height && rasterizedGlyph.size.y <= row.height) {
|
||||
@@ -832,12 +870,16 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
break;
|
||||
}
|
||||
|
||||
// Create a new page if too much vertical space would be wasted or there is not enough room
|
||||
// left in the page. The previous active row will become fixed in the process as it now has a
|
||||
// fixed height
|
||||
/*
|
||||
* Create a new page if too much vertical space would be wasted or there is not enough room
|
||||
* left in the page. The previous active row will become fixed in the process as it now has a
|
||||
* fixed height
|
||||
*/
|
||||
if (activeRow.y + rasterizedGlyph.size.y >= activePage.canvas.height || activeRow.height > rasterizedGlyph.size.y + Constants.ROW_PIXEL_THRESHOLD) {
|
||||
// Create the new fixed height row, creating a new page if there isn't enough room on the
|
||||
// current page
|
||||
/*
|
||||
* Create the new fixed height row, creating a new page if there isn't enough room on the
|
||||
* current page
|
||||
*/
|
||||
let wasPageAndRowFound = false;
|
||||
if (activePage.currentRow.y + activePage.currentRow.height + rasterizedGlyph.size.y >= activePage.canvas.height) {
|
||||
// Find the first page with room to create the new row on
|
||||
@@ -852,10 +894,12 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
if (candidatePage) {
|
||||
activePage = candidatePage;
|
||||
} else {
|
||||
// Before creating a new atlas page that would trigger a page merge, check if the
|
||||
// current active row is sufficient when ignoring the ROW_PIXEL_THRESHOLD. This will
|
||||
// improve texture utilization by using the available space before the page is merged
|
||||
// and becomes static.
|
||||
/*
|
||||
* Before creating a new atlas page that would trigger a page merge, check if the
|
||||
* current active row is sufficient when ignoring the ROW_PIXEL_THRESHOLD. This will
|
||||
* improve texture utilization by using the available space before the page is merged
|
||||
* and becomes static.
|
||||
*/
|
||||
if (
|
||||
TextureAtlas.maxAtlasPages &&
|
||||
this._pages.length >= TextureAtlas.maxAtlasPages &&
|
||||
@@ -923,8 +967,10 @@ export class TextureAtlas implements ITextureAtlas {
|
||||
rasterizedGlyph.sizeClipSpace.x /= activePage.canvas.width;
|
||||
rasterizedGlyph.sizeClipSpace.y /= activePage.canvas.height;
|
||||
|
||||
// Update atlas current row, for fixed rows the glyph height will never be larger than the row
|
||||
// height
|
||||
/*
|
||||
* Update atlas current row, for fixed rows the glyph height will never be larger than the row
|
||||
* height
|
||||
*/
|
||||
activeRow.height = Math.max(activeRow.height, rasterizedGlyph.size.y);
|
||||
activeRow.x += rasterizedGlyph.size.x;
|
||||
|
||||
@@ -1057,16 +1103,18 @@ class AtlasPage {
|
||||
public static nextVersion: number = 0;
|
||||
public version = ++AtlasPage.nextVersion;
|
||||
|
||||
// Texture atlas current positioning data. The texture packing strategy used is to fill from
|
||||
// left-to-right and top-to-bottom. When the glyph being written is less than half of the current
|
||||
// row's height, the following happens:
|
||||
//
|
||||
// - The current row becomes the fixed height row A
|
||||
// - A new fixed height row B the exact size of the glyph is created below the current row
|
||||
// - A new dynamic height current row is created below B
|
||||
//
|
||||
// This strategy does a good job preventing space being wasted for very short glyphs such as
|
||||
// underscores, hyphens etc. or those with underlines rendered.
|
||||
/*
|
||||
* Texture atlas current positioning data. The texture packing strategy used is to fill from
|
||||
* left-to-right and top-to-bottom. When the glyph being written is less than half of the current
|
||||
* row's height, the following happens:
|
||||
*
|
||||
* - The current row becomes the fixed height row A
|
||||
* - A new fixed height row B the exact size of the glyph is created below the current row
|
||||
* - A new dynamic height current row is created below B
|
||||
*
|
||||
* This strategy does a good job preventing space being wasted for very short glyphs such as
|
||||
* underscores, hyphens etc. or those with underlines rendered.
|
||||
*/
|
||||
public currentRow: ICharAtlasActiveRow = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
@@ -1086,9 +1134,11 @@ class AtlasPage {
|
||||
}
|
||||
}
|
||||
this.canvas = createCanvas(document, size, size);
|
||||
// The canvas needs alpha because we use clearColor to convert the background color to alpha.
|
||||
// It might also contain some characters with transparent backgrounds if allowTransparency is
|
||||
// set.
|
||||
/*
|
||||
* The canvas needs alpha because we use clearColor to convert the background color to alpha.
|
||||
* It might also contain some characters with transparent backgrounds if allowTransparency is
|
||||
* set.
|
||||
*/
|
||||
this.ctx = throwIfFalsy(this.canvas.getContext('2d', { alpha: true }));
|
||||
}
|
||||
|
||||
@@ -1116,12 +1166,14 @@ function clearColor(imageData: ImageData, bg: IColor, fg: IColor, enableThreshol
|
||||
const fgG = fg.rgba >>> 16 & 0xFF;
|
||||
const fgB = fg.rgba >>> 8 & 0xFF;
|
||||
|
||||
// Calculate a threshold that when below a color will be treated as transpart when the sum of
|
||||
// channel value differs. This helps improve rendering when glyphs overlap with others. This
|
||||
// threshold is calculated relative to the difference between the background and foreground to
|
||||
// ensure important details of the glyph are always shown, even when the contrast ratio is low.
|
||||
// The number 12 is largely arbitrary to ensure the pixels that escape the cell in the test case
|
||||
// were covered (fg=#8ae234, bg=#c4a000).
|
||||
/*
|
||||
* Calculate a threshold that when below a color will be treated as transpart when the sum of
|
||||
* channel value differs. This helps improve rendering when glyphs overlap with others. This
|
||||
* threshold is calculated relative to the difference between the background and foreground to
|
||||
* ensure important details of the glyph are always shown, even when the contrast ratio is low.
|
||||
* The number 12 is largely arbitrary to ensure the pixels that escape the cell in the test case
|
||||
* were covered (fg=#8ae234, bg=#c4a000).
|
||||
*/
|
||||
const threshold = Math.floor((Math.abs(r - fgR) + Math.abs(g - fgG) + Math.abs(b - fgB)) / 12);
|
||||
|
||||
// Set alpha channel of relevent pixels to 0
|
||||
|
||||
@@ -84,9 +84,11 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
) {
|
||||
super();
|
||||
|
||||
// IMPORTANT: Canvas initialization and fetching of the context must be first in order to
|
||||
// prevent possible listeners leaking and continuing to operate after the WebglRenderer has been
|
||||
// discarded.
|
||||
/*
|
||||
* IMPORTANT: Canvas initialization and fetching of the context must be first in order to
|
||||
* prevent possible listeners leaking and continuing to operate after the WebglRenderer has been
|
||||
* discarded.
|
||||
*/
|
||||
this._canvas = this._coreBrowserService.mainDocument.createElement('canvas');
|
||||
const contextAttributes = {
|
||||
antialias: false,
|
||||
@@ -125,8 +127,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
console.log('webglcontextlost event received');
|
||||
// Prevent the default behavior in order to enable WebGL context restoration.
|
||||
e.preventDefault();
|
||||
// Wait a few seconds to see if the 'webglcontextrestored' event is fired.
|
||||
// If not, dispatch the onContextLoss notification to observers.
|
||||
/*
|
||||
* Wait a few seconds to see if the 'webglcontextrestored' event is fired.
|
||||
* If not, dispatch the onContextLoss notification to observers.
|
||||
*/
|
||||
this._contextRestorationTimeout = setTimeout(() => {
|
||||
this._contextRestorationTimeout = undefined;
|
||||
console.warn('webgl context not restored; firing onContextLoss');
|
||||
@@ -137,8 +141,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
console.warn('webglcontextrestored event received');
|
||||
clearTimeout(this._contextRestorationTimeout);
|
||||
this._contextRestorationTimeout = undefined;
|
||||
// The texture atlas and glyph renderer must be fully reinitialized
|
||||
// because their contents have been lost.
|
||||
/*
|
||||
* The texture atlas and glyph renderer must be fully reinitialized
|
||||
* because their contents have been lost.
|
||||
*/
|
||||
removeTerminalFromCache(this._terminal);
|
||||
this._initializeWebGLState();
|
||||
this._requestRedrawViewport();
|
||||
@@ -178,8 +184,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
}
|
||||
|
||||
public handleDevicePixelRatioChange(): void {
|
||||
// If the device pixel ratio changed, the char atlas needs to be regenerated
|
||||
// and the terminal needs to refreshed
|
||||
/*
|
||||
* If the device pixel ratio changed, the char atlas needs to be regenerated
|
||||
* and the terminal needs to refreshed
|
||||
*/
|
||||
if (this._devicePixelRatio !== this._coreBrowserService.dpr) {
|
||||
this._devicePixelRatio = this._coreBrowserService.dpr;
|
||||
this.handleResize(this._terminal.cols, this._terminal.rows);
|
||||
@@ -215,8 +223,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
|
||||
this._refreshCharAtlas();
|
||||
|
||||
// Force a full refresh. Resizing `_glyphRenderer` should clear it already,
|
||||
// so there is no need to clear it again here.
|
||||
/*
|
||||
* Force a full refresh. Resizing `_glyphRenderer` should clear it already,
|
||||
* so there is no need to clear it again here.
|
||||
*/
|
||||
this._clearModel(false);
|
||||
|
||||
// Render synchronously to avoid flicker when the canvas is cleared
|
||||
@@ -368,9 +378,11 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
return;
|
||||
}
|
||||
|
||||
// Tell renderer the frame is beginning
|
||||
// upon a model clear also refresh the full viewport model
|
||||
// (also triggered by an atlas page merge, part of #4480)
|
||||
/*
|
||||
* Tell renderer the frame is beginning
|
||||
* upon a model clear also refresh the full viewport model
|
||||
* (also triggered by an atlas page merge, part of #4480)
|
||||
*/
|
||||
if (this._glyphRenderer.value.beginFrame()) {
|
||||
this._clearModel(true);
|
||||
this._updateModel(0, this._terminal.rows - 1);
|
||||
@@ -379,8 +391,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
this._updateModel(start, end);
|
||||
}
|
||||
|
||||
// A mid-update atlas page merge invalidates vertex data and may not bump the host
|
||||
// page's version, so re-run the update and force a full texture rebind.
|
||||
/*
|
||||
* A mid-update atlas page merge invalidates vertex data and may not bump the host
|
||||
* page's version, so re-run the update and force a full texture rebind.
|
||||
*/
|
||||
let merged = false;
|
||||
let mergeRetries = 0;
|
||||
while (this._charAtlas && this._glyphRenderer.value.beginFrame() && mergeRetries++ < Constants.MERGE_RETRY_LIMIT) {
|
||||
@@ -408,8 +422,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
} else {
|
||||
this._cursorBlinkStateManager.clear();
|
||||
}
|
||||
// Request a refresh from the terminal as management of rendering is being
|
||||
// moved back to the terminal
|
||||
/*
|
||||
* Request a refresh from the terminal as management of rendering is being
|
||||
* moved back to the terminal
|
||||
*/
|
||||
this._requestRedrawCursor();
|
||||
}
|
||||
|
||||
@@ -470,20 +486,26 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
// If true, indicates that the current character(s) to draw were joined.
|
||||
isJoined = false;
|
||||
|
||||
// Indicates whether this cell is part of a joined range that should be ignored as it cannot
|
||||
// be rendered entirely, like the selection state differs across the range.
|
||||
/*
|
||||
* Indicates whether this cell is part of a joined range that should be ignored as it cannot
|
||||
* be rendered entirely, like the selection state differs across the range.
|
||||
*/
|
||||
isValidJoinRange = (x >= skipJoinedCheckUntilX);
|
||||
|
||||
lastCharX = x;
|
||||
|
||||
// Process any joined character ranges as needed. Because of how the
|
||||
// ranges are produced, we know that they are valid for the characters
|
||||
// and attributes of our input.
|
||||
/*
|
||||
* Process any joined character ranges as needed. Because of how the
|
||||
* ranges are produced, we know that they are valid for the characters
|
||||
* and attributes of our input.
|
||||
*/
|
||||
if (joinedRanges.length > 0 && x === joinedRanges[0][0] && isValidJoinRange) {
|
||||
range = joinedRanges.shift()!;
|
||||
|
||||
// If the ligature's selection state is not consistent, don't join it. This helps the
|
||||
// selection render correctly regardless whether they should be joined.
|
||||
/*
|
||||
* If the ligature's selection state is not consistent, don't join it. This helps the
|
||||
* selection render correctly regardless whether they should be joined.
|
||||
*/
|
||||
const firstSelectionState = this._model.selection.isCellSelected(this._terminal, range[0], row);
|
||||
for (i = range[0] + 1; i < range[1]; i++) {
|
||||
isValidJoinRange &&= (firstSelectionState === this._model.selection.isCellSelected(this._terminal, i, row));
|
||||
@@ -495,8 +517,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
} else {
|
||||
isJoined = true;
|
||||
|
||||
// We already know the exact start and end column of the joined range,
|
||||
// so we get the string and width representing it directly.
|
||||
/*
|
||||
* We already know the exact start and end column of the joined range,
|
||||
* so we get the string and width representing it directly.
|
||||
*/
|
||||
cell = new JoinedCellData(
|
||||
cell,
|
||||
line!.translateToString(true, range[0], range[1]),
|
||||
@@ -586,8 +610,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
j = ((y * terminal.cols) + x) * RenderModelConstants.INDICIES_PER_CELL;
|
||||
this._glyphRenderer.value!.updateCell(x, y, NULL_CELL_CODE, 0, 0, 0, NULL_CELL_CHAR, 0, 0);
|
||||
this._model.cells[j] = NULL_CELL_CODE;
|
||||
// Don't re-resolve the cell color since multi-colored ligature backgrounds are not
|
||||
// supported
|
||||
/*
|
||||
* Don't re-resolve the cell color since multi-colored ligature backgrounds are not
|
||||
* supported
|
||||
*/
|
||||
this._model.cells[j + RenderModelConstants.BG_OFFSET] = this._cellColorResolver.result.bg;
|
||||
this._model.cells[j + RenderModelConstants.FG_OFFSET] = this._cellColorResolver.result.fg;
|
||||
this._model.cells[j + RenderModelConstants.EXT_OFFSET] = this._cellColorResolver.result.ext;
|
||||
@@ -631,48 +657,64 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate the device character width. Width is floored as it must be drawn to an integer grid
|
||||
// in order for the char atlas glyphs to not be blurry.
|
||||
/*
|
||||
* Calculate the device character width. Width is floored as it must be drawn to an integer grid
|
||||
* in order for the char atlas glyphs to not be blurry.
|
||||
*/
|
||||
this.dimensions.device.char.width = Math.floor(this._charSizeService.width * this._devicePixelRatio);
|
||||
|
||||
// Calculate the device character height. Height is ceiled in case devicePixelRatio is a
|
||||
// floating point number in order to ensure there is enough space to draw the character to the
|
||||
// cell.
|
||||
/*
|
||||
* Calculate the device character height. Height is ceiled in case devicePixelRatio is a
|
||||
* floating point number in order to ensure there is enough space to draw the character to the
|
||||
* cell.
|
||||
*/
|
||||
this.dimensions.device.char.height = Math.ceil(this._charSizeService.height * this._devicePixelRatio);
|
||||
|
||||
// Calculate the device cell height, if lineHeight is _not_ 1, the resulting value will be
|
||||
// floored since lineHeight can never be lower then 1, this guarentees the device cell height
|
||||
// will always be larger than device char height.
|
||||
/*
|
||||
* Calculate the device cell height, if lineHeight is _not_ 1, the resulting value will be
|
||||
* floored since lineHeight can never be lower then 1, this guarentees the device cell height
|
||||
* will always be larger than device char height.
|
||||
*/
|
||||
this.dimensions.device.cell.height = Math.floor(this.dimensions.device.char.height * this._optionsService.rawOptions.lineHeight);
|
||||
|
||||
// Calculate the y offset within a cell that glyph should draw at in order for it to be centered
|
||||
// correctly within the cell.
|
||||
/*
|
||||
* Calculate the y offset within a cell that glyph should draw at in order for it to be centered
|
||||
* correctly within the cell.
|
||||
*/
|
||||
this.dimensions.device.char.top = this._optionsService.rawOptions.lineHeight === 1 ? 0 : Math.round((this.dimensions.device.cell.height - this.dimensions.device.char.height) / 2);
|
||||
|
||||
// Calculate the device cell width, taking the letterSpacing into account.
|
||||
this.dimensions.device.cell.width = this.dimensions.device.char.width + Math.round(this._optionsService.rawOptions.letterSpacing);
|
||||
|
||||
// Calculate the x offset with a cell that text should draw from in order for it to be centered
|
||||
// correctly within the cell.
|
||||
/*
|
||||
* Calculate the x offset with a cell that text should draw from in order for it to be centered
|
||||
* correctly within the cell.
|
||||
*/
|
||||
this.dimensions.device.char.left = Math.floor(this._optionsService.rawOptions.letterSpacing / 2);
|
||||
|
||||
// Recalculate the canvas dimensions, the device dimensions define the actual number of pixel in
|
||||
// the canvas
|
||||
/*
|
||||
* Recalculate the canvas dimensions, the device dimensions define the actual number of pixel in
|
||||
* the canvas
|
||||
*/
|
||||
this.dimensions.device.canvas.height = this._terminal.rows * this.dimensions.device.cell.height;
|
||||
this.dimensions.device.canvas.width = this._terminal.cols * this.dimensions.device.cell.width;
|
||||
|
||||
// The the size of the canvas on the page. It's important that this rounds to nearest integer
|
||||
// and not ceils as browsers often have floating point precision issues where
|
||||
// `window.devicePixelRatio` ends up being something like `1.100000023841858` for example, when
|
||||
// it's actually 1.1. Ceiling may causes blurriness as the backing canvas image is 1 pixel too
|
||||
// large for the canvas element size.
|
||||
/*
|
||||
* The the size of the canvas on the page. It's important that this rounds to nearest integer
|
||||
* and not ceils as browsers often have floating point precision issues where
|
||||
* `window.devicePixelRatio` ends up being something like `1.100000023841858` for example, when
|
||||
* it's actually 1.1. Ceiling may causes blurriness as the backing canvas image is 1 pixel too
|
||||
* large for the canvas element size.
|
||||
*/
|
||||
this.dimensions.css.canvas.height = Math.round(this.dimensions.device.canvas.height / this._devicePixelRatio);
|
||||
this.dimensions.css.canvas.width = Math.round(this.dimensions.device.canvas.width / this._devicePixelRatio);
|
||||
|
||||
// Get the CSS dimensions of an individual cell. This needs to be derived from the calculated
|
||||
// device pixel canvas value above. CharMeasure.width/height by itself is insufficient when the
|
||||
// page is not at 100% zoom level as CharMeasure is measured in CSS pixels, but the actual char
|
||||
// size on the canvas can differ.
|
||||
/*
|
||||
* Get the CSS dimensions of an individual cell. This needs to be derived from the calculated
|
||||
* device pixel canvas value above. CharMeasure.width/height by itself is insufficient when the
|
||||
* page is not at 100% zoom level as CharMeasure is measured in CSS pixels, but the actual char
|
||||
* size on the canvas can differ.
|
||||
*/
|
||||
this.dimensions.css.cell.height = this.dimensions.device.cell.height / this._devicePixelRatio;
|
||||
this.dimensions.css.cell.width = this.dimensions.device.cell.width / this._devicePixelRatio;
|
||||
}
|
||||
@@ -681,8 +723,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
if (this._canvas.width === width && this._canvas.height === height) {
|
||||
return;
|
||||
}
|
||||
// While the actual canvas size has changed, keep device canvas dimensions as the value before
|
||||
// the change as it's an exact multiple of the cell sizes.
|
||||
/*
|
||||
* While the actual canvas size has changed, keep device canvas dimensions as the value before
|
||||
* the change as it's an exact multiple of the cell sizes.
|
||||
*/
|
||||
this._canvas.width = width;
|
||||
this._canvas.height = height;
|
||||
// Render synchronously to avoid flicker when the canvas is cleared
|
||||
@@ -702,8 +746,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
// TODO: Share impl with core
|
||||
export class JoinedCellData extends AttributeData implements ICellData {
|
||||
private _width: number;
|
||||
// .content carries no meaning for joined CellData, simply nullify it
|
||||
// thus we have to overload all other .content accessors
|
||||
/*
|
||||
* .content carries no meaning for joined CellData, simply nullify it
|
||||
* thus we have to overload all other .content accessors
|
||||
*/
|
||||
public content: number = 0;
|
||||
public fg: number;
|
||||
public bg: number;
|
||||
@@ -731,8 +777,10 @@ export class JoinedCellData extends AttributeData implements ICellData {
|
||||
}
|
||||
|
||||
public getCode(): number {
|
||||
// code always gets the highest possible fake codepoint (read as -1)
|
||||
// this is needed as code is used by caches as identifier
|
||||
/*
|
||||
* code always gets the highest possible fake codepoint (read as -1)
|
||||
* this is needed as code is used by caches as identifier
|
||||
*/
|
||||
return 0x1FFFFF;
|
||||
}
|
||||
|
||||
|
||||
@@ -274,15 +274,17 @@ export const customGlyphDefinitions: { [index: string]: CustomGlyphCharacterDefi
|
||||
|
||||
// #region Powerline Symbols (E0A0-E0BF)
|
||||
|
||||
// This contains the definitions of the primarily used box drawing characters as vector shapes.
|
||||
// The reason these characters are defined specially is to avoid common problems if a user's font
|
||||
// has not been patched with powerline characters and also to get pixel perfect rendering as
|
||||
// rendering issues can occur around AA/SPAA.
|
||||
//
|
||||
// The line variants draw beyond the cell and get clipped to ensure the end of the line is not
|
||||
// visible.
|
||||
//
|
||||
// Original symbols defined in https://github.com/powerline/fontpatcher
|
||||
/*
|
||||
* This contains the definitions of the primarily used box drawing characters as vector shapes.
|
||||
* The reason these characters are defined specially is to avoid common problems if a user's font
|
||||
* has not been patched with powerline characters and also to get pixel perfect rendering as
|
||||
* rendering issues can occur around AA/SPAA.
|
||||
*
|
||||
* The line variants draw beyond the cell and get clipped to ensure the end of the line is not
|
||||
* visible.
|
||||
*
|
||||
* Original symbols defined in https://github.com/powerline/fontpatcher
|
||||
*/
|
||||
|
||||
// Git branch
|
||||
'\u{E0A0}': { type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M.3,1 L.03,1 L.03,.88 C.03,.82,.06,.78,.11,.73 C.15,.7,.2,.68,.28,.65 L.43,.6 C.49,.58,.53,.56,.56,.53 C.59,.5,.6,.47,.6,.43 L.6,.27 L.4,.27 L.69,.1 L.98,.27 L.78,.27 L.78,.46 C.78,.52,.76,.56,.72,.61 C.68,.66,.63,.67,.56,.7 L.48,.72 C.42,.74,.38,.76,.35,.78 C.32,.8,.31,.84,.31,.88 L.31,1 M.3,.5 L.03,.59 L.03,.09 L.3,.09 L.3,.655', type: CustomGlyphVectorType.FILL } },
|
||||
@@ -355,8 +357,10 @@ export const customGlyphDefinitions: { [index: string]: CustomGlyphCharacterDefi
|
||||
|
||||
// #region Progress Indicators (EE00-EE0B)
|
||||
|
||||
// initially added in Fira Code and later Nerd Fonts
|
||||
// https://github.com/ryanoasis/nerd-fonts/pull/1733
|
||||
/*
|
||||
* initially added in Fira Code and later Nerd Fonts
|
||||
* https://github.com/ryanoasis/nerd-fonts/pull/1733
|
||||
*/
|
||||
|
||||
// Progress bars (EE00-EE05)
|
||||
'\u{EE00}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,0 L1,0 L1,.05 L.1,.05 L.1,.95 L1,.95 L1,1 L0,1 Z' }, // PROGRESS BAR EMPTY START
|
||||
@@ -378,8 +382,10 @@ export const customGlyphDefinitions: { [index: string]: CustomGlyphCharacterDefi
|
||||
|
||||
// #region Git Branch Symbols (F5D0-F60D)
|
||||
|
||||
// Initially added in Kitty (https://github.com/kovidgoyal/kitty/pull/7681)
|
||||
// then in Wezterm (https://github.com/wezterm/wezterm/issues/6328).
|
||||
/*
|
||||
* Initially added in Kitty (https://github.com/kovidgoyal/kitty/pull/7681)
|
||||
* then in Wezterm (https://github.com/wezterm/wezterm/issues/6328).
|
||||
*/
|
||||
|
||||
// Straight lines (F5D0-F5D9)
|
||||
'\u{F5D0}': GitBranchSymbolsParts.LINE_H, // Same as 2500
|
||||
@@ -433,8 +439,10 @@ export const customGlyphDefinitions: { [index: string]: CustomGlyphCharacterDefi
|
||||
'\u{F5FA}': [GitBranchSymbolsParts.NODE_FILL, GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_DOWN, GitBranchSymbolsParts.NODE_LINE_UP],
|
||||
'\u{F5FB}': [GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_DOWN, GitBranchSymbolsParts.NODE_LINE_UP],
|
||||
|
||||
// Extended Nodes (F5FC-F60D)
|
||||
// These were added a little later https://github.com/kovidgoyal/kitty/pull/7805
|
||||
/*
|
||||
* Extended Nodes (F5FC-F60D)
|
||||
* These were added a little later https://github.com/kovidgoyal/kitty/pull/7805
|
||||
*/
|
||||
'\u{F5FC}': [GitBranchSymbolsParts.NODE_FILL, GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_DOWN, GitBranchSymbolsParts.NODE_LINE_RIGHT],
|
||||
'\u{F5FD}': [GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_DOWN, GitBranchSymbolsParts.NODE_LINE_RIGHT],
|
||||
'\u{F5FE}': [GitBranchSymbolsParts.NODE_FILL, GitBranchSymbolsParts.NODE_STROKE, GitBranchSymbolsParts.NODE_LINE_DOWN, GitBranchSymbolsParts.NODE_LINE_LEFT],
|
||||
@@ -460,8 +468,10 @@ export const customGlyphDefinitions: { [index: string]: CustomGlyphCharacterDefi
|
||||
|
||||
// https://www.unicode.org/charts/PDF/U1FB00.pdf
|
||||
|
||||
// Block mosaic terminal graphic characters (1FB00-1FB3B)
|
||||
// The term "sextant" refers to block mosaics divided into six parts.
|
||||
/*
|
||||
* Block mosaic terminal graphic characters (1FB00-1FB3B)
|
||||
* The term "sextant" refers to block mosaics divided into six parts.
|
||||
*/
|
||||
'\u{1FB00}': sextant(0b000001), // BLOCK SEXTANT-1
|
||||
'\u{1FB01}': sextant(0b000010), // BLOCK SEXTANT-2
|
||||
'\u{1FB02}': sextant(0b000011), // BLOCK SEXTANT-12 (upper one third block)
|
||||
@@ -724,8 +734,10 @@ export const customGlyphDefinitions: { [index: string]: CustomGlyphCharacterDefi
|
||||
'\u{1FBB2}': { type: CustomGlyphDefinitionType.PATH, data: 'M.29,.27 L0.13,.56 L.22,.59 L.35,0.35 L.67,.35 L.57,.57 L.71,.76 L.22,.76 L.42,1 L.53,.98 L.43,.86 L.9,.86 L.71,.6 L1,.6 L1,.52 L.83,.52 L.92,.36 L1,.36 L1,.27Z M.99,.13 A.12,.12,0,1,1,.75,.13 A.12,.12,0,1,1,.99,.13' }, // LEFT HALF RUNNING MAN
|
||||
'\u{1FBB3}': { type: CustomGlyphDefinitionType.PATH, data: 'M0,.27 L.3,.27 L.55,.12 L.63,.18 L.33,.36 L0,.36 M0,.52 L.33,.52 L.59,.89 L.73,.89 L.73,.98 L.53,.98 L.28,.6 L0,.6' }, // RIGHT HALF RUNNING MAN
|
||||
|
||||
// Arrows (1FBB4-1FBB8)
|
||||
// TODO: Improve all arrows, use hybrid approach
|
||||
/*
|
||||
* Arrows (1FBB4-1FBB8)
|
||||
* TODO: Improve all arrows, use hybrid approach
|
||||
*/
|
||||
'\u{1FBB4}': { type: CustomGlyphDefinitionType.PATH_NEGATIVE, data: { d: 'M.15,.55 L.5,.4 L.5,.45 L.65,.45 L.65,.35 L.85,.35 L.85,.625 L.5,.625 L.5,.7 Z', type: CustomGlyphVectorType.FILL }, scaleType: CustomGlyphScaleType.CHAR }, // INVERSE DOWNWARDS ARROW WITH TIP LEFTWARDS
|
||||
'\u{1FBB5}': [ // LEFTWARDS ARROW AND UPPER AND LOWER ONE EIGHTH BLOCK
|
||||
{ type: CustomGlyphDefinitionType.VECTOR_SHAPE, data: { d: 'M0,0 L1,0 L1,.0625 L0,.0625 Z M0,.9375 L1,.9375 L1,1 L0,1 Z', type: CustomGlyphVectorType.FILL } },
|
||||
@@ -880,9 +892,11 @@ function sextant(pattern: number): { type: CustomGlyphDefinitionType.PATH_FUNCTI
|
||||
return {
|
||||
type: CustomGlyphDefinitionType.PATH_FUNCTION,
|
||||
data: () => {
|
||||
// Sextant grid: 2 columns, 3 rows
|
||||
// Row heights in 8ths: top=3, middle=2, bottom=3
|
||||
// Column widths: left=4, right=4
|
||||
/*
|
||||
* Sextant grid: 2 columns, 3 rows
|
||||
* Row heights in 8ths: top=3, middle=2, bottom=3
|
||||
* Column widths: left=4, right=4
|
||||
*/
|
||||
const rects: string[] = [];
|
||||
const colW = 0.5; // Each column is half width
|
||||
const rowH = [3 / 8, 2 / 8, 3 / 8]; // Row heights as fractions
|
||||
@@ -932,10 +946,12 @@ function sextant(pattern: number): { type: CustomGlyphDefinitionType.PATH_FUNCTI
|
||||
function segmentedDigit(pattern: number): string {
|
||||
const paths: string[] = [];
|
||||
|
||||
// Each segment should have approximately the same stroke width, this is somewhat difficult to be
|
||||
// precise since coordinates are 0-1 of the whole cell (percentage-based). To handle this, the
|
||||
// fact that terminal cells are typically sized at ~2:1 (height:width) is leveraged.
|
||||
// for horizontal vs vertical to make segments appear the same thickness
|
||||
/*
|
||||
* Each segment should have approximately the same stroke width, this is somewhat difficult to be
|
||||
* precise since coordinates are 0-1 of the whole cell (percentage-based). To handle this, the
|
||||
* fact that terminal cells are typically sized at ~2:1 (height:width) is leveraged.
|
||||
* for horizontal vs vertical to make segments appear the same thickness
|
||||
*/
|
||||
const segW = 0.15; // Width of vertical segments (fraction of cell width)
|
||||
const segH = 0.075; // Height of horizontal segments (fraction of cell height, ~half of segW for 2:1 cells)
|
||||
const padX = 0.05; // Horizontal padding from edge
|
||||
|
||||
@@ -489,8 +489,10 @@ function drawPatternChar(
|
||||
pattern = throwIfFalsy(ctx.createPattern(tmpCanvas, null));
|
||||
patternSet.set(fillStyle, pattern);
|
||||
}
|
||||
// Apply pattern offset to ensure seamless tiling across cells when cell dimensions are odd.
|
||||
// variantOffset encodes: bit 1 = x pixel shift, bit 0 = y pixel shift.
|
||||
/*
|
||||
* Apply pattern offset to ensure seamless tiling across cells when cell dimensions are odd.
|
||||
* variantOffset encodes: bit 1 = x pixel shift, bit 0 = y pixel shift.
|
||||
*/
|
||||
const dx = (variantOffset >> 1) & 1;
|
||||
const dy = variantOffset & 1;
|
||||
if (dx !== 0 || dy !== 0) {
|
||||
@@ -713,8 +715,10 @@ function translateArgs(args: string[], cellWidth: number, cellHeight: number, xO
|
||||
for (let x = 0; x < result.length; x += 2) {
|
||||
// Translate from 0-1 to 0-cellWidth
|
||||
result[x] *= cellWidth - (leftPadding * devicePixelRatio) - (rightPadding * devicePixelRatio);
|
||||
// Round to the nearest 0.5 to ensure a crisp line at 100% devicePixelRatio, and optionally
|
||||
// clamp to the cell bounds.
|
||||
/*
|
||||
* Round to the nearest 0.5 to ensure a crisp line at 100% devicePixelRatio, and optionally
|
||||
* clamp to the cell bounds.
|
||||
*/
|
||||
if (doClamp && result[x] !== 0) {
|
||||
const rounded = Math.round(result[x] + 0.5) - 0.5;
|
||||
result[x] = clampToCell ? clamp(rounded, cellWidth, 0) : rounded;
|
||||
@@ -726,8 +730,10 @@ function translateArgs(args: string[], cellWidth: number, cellHeight: number, xO
|
||||
for (let y = 1; y < result.length; y += 2) {
|
||||
// Translate from 0-1 to 0-cellHeight
|
||||
result[y] *= cellHeight;
|
||||
// Round to the nearest 0.5 to ensure a crisp line at 100% devicePixelRatio, and optionally
|
||||
// clamp to the cell bounds.
|
||||
/*
|
||||
* Round to the nearest 0.5 to ensure a crisp line at 100% devicePixelRatio, and optionally
|
||||
* clamp to the cell bounds.
|
||||
*/
|
||||
if (doClamp && result[y] !== 0) {
|
||||
const rounded = Math.round(result[y] + 0.5) - 0.5;
|
||||
result[y] = clampToCell ? clamp(rounded, cellHeight, 0) : rounded;
|
||||
|
||||
@@ -24,9 +24,11 @@ test.beforeAll(async ({ browser }) => {
|
||||
test.afterAll(async () => await ctx.page.close());
|
||||
|
||||
test.describe('WebGL Renderer Integration Tests', async () => {
|
||||
// HACK: webgl2 is often not supported in headless firefox on Linux
|
||||
// https://github.com/microsoft/playwright/issues/11566
|
||||
// also disable safari due to #5852
|
||||
/*
|
||||
* HACK: webgl2 is often not supported in headless firefox on Linux
|
||||
* https://github.com/microsoft/playwright/issues/11566
|
||||
* also disable safari due to #5852
|
||||
*/
|
||||
if (platform() === 'linux') {
|
||||
test.skip(({ browserName }) => browserName === 'firefox' || browserName === 'webkit');
|
||||
}
|
||||
|
||||
+13
-7
@@ -173,9 +173,11 @@ const createNewWindowButtonHandler: () => void = () => {
|
||||
terminalContainer.id = 'terminal-container';
|
||||
win.document.body.appendChild(terminalContainer);
|
||||
|
||||
// Stylesheets are needed to get the terminal in the popout window to render
|
||||
// correctly. We also need to wait for them to load before creating the
|
||||
// terminal, otherwise we will not compute the correct metrics when rendering.
|
||||
/*
|
||||
* Stylesheets are needed to get the terminal in the popout window to render
|
||||
* correctly. We also need to wait for them to load before creating the
|
||||
* terminal, otherwise we will not compute the correct metrics when rendering.
|
||||
*/
|
||||
let pendingStylesheets = 0;
|
||||
for (const linkNode of document.querySelectorAll('head link[rel=stylesheet]')) {
|
||||
const newLink = document.createElement('link');
|
||||
@@ -359,8 +361,10 @@ function createTerminal(): Terminal {
|
||||
|
||||
const resizeObserver = new ResizeObserver(entries => {
|
||||
if (optionsWindow.autoResize) {
|
||||
// In general this should be debounced to avoid excessive work on the main
|
||||
// thread by firing the expensive resize action repeatedly
|
||||
/*
|
||||
* In general this should be debounced to avoid excessive work on the main
|
||||
* thread by firing the expensive resize action repeatedly
|
||||
*/
|
||||
addons.fit.instance!.fit();
|
||||
}
|
||||
});
|
||||
@@ -547,8 +551,10 @@ function initAddons(term: Terminal): void {
|
||||
addon.instance = undefined;
|
||||
}
|
||||
if (name === 'ligatures') {
|
||||
// Recreate webgl when ligatures are toggled so texture atlas picks up any font feature
|
||||
// settings changes
|
||||
/*
|
||||
* Recreate webgl when ligatures are toggled so texture atlas picks up any font feature
|
||||
* settings changes
|
||||
*/
|
||||
if (addons.webgl.instance) {
|
||||
preDisposeWebgl();
|
||||
addons.webgl.instance.dispose();
|
||||
|
||||
@@ -165,16 +165,18 @@ export class AddonImageWindow extends BaseWindow implements IControlWindow {
|
||||
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;
|
||||
// }
|
||||
// }
|
||||
/*
|
||||
* 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;
|
||||
|
||||
@@ -66,11 +66,13 @@ export class AddonProgressWindow extends BaseWindow implements IControlWindow {
|
||||
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.
|
||||
/*
|
||||
* 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];
|
||||
|
||||
@@ -435,9 +435,11 @@ function customGlyphAlignmentHandler(term: Terminal): void {
|
||||
}
|
||||
|
||||
function customGlyphRangesHandler(term: Terminal): void {
|
||||
// Box Drawing
|
||||
// 2500-257F
|
||||
// https://www.unicode.org/charts/PDF/U2500.pdf
|
||||
/*
|
||||
* Box Drawing
|
||||
* 2500-257F
|
||||
* https://www.unicode.org/charts/PDF/U2500.pdf
|
||||
*/
|
||||
writeUnicodeTable(term, 'Box Drawing', 0x2500, 0x257F, [
|
||||
['Light and heavy solid lines', 0x2500, 0x2503],
|
||||
['Light and heavy dashed lines', 0x2504, 0x250B],
|
||||
@@ -450,39 +452,49 @@ function customGlyphRangesHandler(term: Terminal): void {
|
||||
['Light and heavy half lines', 0x2574, 0x257B],
|
||||
['Mixed light and heavy lines', 0x257C, 0x257F],
|
||||
]);
|
||||
// Box Elements
|
||||
// 2580-259F
|
||||
// https://www.unicode.org/charts/PDF/U2580.pdf
|
||||
/*
|
||||
* Box Elements
|
||||
* 2580-259F
|
||||
* https://www.unicode.org/charts/PDF/U2580.pdf
|
||||
*/
|
||||
writeUnicodeTable(term, 'Box Elements', 0x2580, 0x259F, [
|
||||
['Block elements', 0x2580, 0x2590],
|
||||
['Shade characters', 0x2591, 0x2593],
|
||||
['Block elements', 0x2594, 0x2595],
|
||||
['Terminal graphic characters', 0x2596, 0x259F],
|
||||
]);
|
||||
// Braille Patterns
|
||||
// 2800-28FF
|
||||
// https://www.unicode.org/charts/PDF/U2800.pdf
|
||||
/*
|
||||
* Braille Patterns
|
||||
* 2800-28FF
|
||||
* https://www.unicode.org/charts/PDF/U2800.pdf
|
||||
*/
|
||||
writeUnicodeTable(term, 'Braille patterns', 0x2800, 0x28FF, [
|
||||
['Braille patterns', 0x2800, 0x28FF],
|
||||
]);
|
||||
// Powerline Symbols
|
||||
// Range: E0A0–E0D4
|
||||
// https://github.com/ryanoasis/nerd-fonts
|
||||
/*
|
||||
* Powerline Symbols
|
||||
* Range: E0A0–E0D4
|
||||
* https://github.com/ryanoasis/nerd-fonts
|
||||
*/
|
||||
writeUnicodeTable(term, 'Powerline Symbols', 0xE0A0, 0xE0D4, [
|
||||
['Powerline symbols', 0xE0A0, 0xE0B3, [0xE0A4, 0xE0A5, 0xE0A6, 0xE0A7, 0xE0A8, 0xE0A9, 0xE0AA, 0xE0AB, 0xE0AC, 0xE0AD, 0xE0AE, 0xE0AF]],
|
||||
['Powerline extra symbols', 0xE0B4, 0xE0D4, [0xE0C9, 0xE0CB, 0xE0D3]],
|
||||
]);
|
||||
// Progress Indicators
|
||||
// Range: EE00-EE0B
|
||||
// https://github.com/tonsky/FiraCode
|
||||
/*
|
||||
* Progress Indicators
|
||||
* Range: EE00-EE0B
|
||||
* https://github.com/tonsky/FiraCode
|
||||
*/
|
||||
writeUnicodeTable(term, 'Progress Indicators', 0xEE00, 0xEE0B, [
|
||||
['Progress bars', 0xEE00, 0xEE05],
|
||||
['Progress spinners', 0xEE06, 0xEE0B],
|
||||
]);
|
||||
// https://github.com/ryanoasis/nerd-fonts/pull/1733
|
||||
// Git Branch Symbols
|
||||
// F5D0-F60D
|
||||
// https://github.com/xtermjs/xterm.js/issues/5477
|
||||
/*
|
||||
* https://github.com/ryanoasis/nerd-fonts/pull/1733
|
||||
* Git Branch Symbols
|
||||
* F5D0-F60D
|
||||
* https://github.com/xtermjs/xterm.js/issues/5477
|
||||
*/
|
||||
writeUnicodeTable(term, 'Git Branch Symbols', 0xF5D0, 0xF5FB, [
|
||||
['Straight lines', 0xF5D0, 0xF5D5],
|
||||
['Curved lines', 0xF5D6, 0xF5D9],
|
||||
@@ -490,9 +502,11 @@ function customGlyphRangesHandler(term: Terminal): void {
|
||||
['Nodes', 0xF5EE, 0xF5FB],
|
||||
['Extended nodes', 0xF5FC, 0xF60D],
|
||||
]);
|
||||
// Symbols for Legacy Computing
|
||||
// Range: 1FB00–1FBFF
|
||||
// https://www.unicode.org/charts/PDF/U1FB00.pdf
|
||||
/*
|
||||
* Symbols for Legacy Computing
|
||||
* Range: 1FB00–1FBFF
|
||||
* https://www.unicode.org/charts/PDF/U1FB00.pdf
|
||||
*/
|
||||
writeUnicodeTable(term, 'Symbols for Legacy Computing', 0x1FB00, 0x1FBFF, [
|
||||
['Block mosaic terminal graphic characters (Sextants)', 0x1FB00, 0x1FB3B],
|
||||
['Smooth mosaic terminal graphic characters', 0x1FB3C, 0x1FB6F],
|
||||
@@ -690,8 +704,10 @@ function loadTest(term: Terminal, addons: AddonCollection): void {
|
||||
for (let i = 1; i < count + 1; i++) {
|
||||
data[i] = 0x61 + Math.floor(Math.random() * (0x7A - 0x61));
|
||||
}
|
||||
// End each line with \r so the cursor remains constant, this is what ls/tree do and improves
|
||||
// performance significantly due to the cursor DOM element not needing to change
|
||||
/*
|
||||
* End each line with \r so the cursor remains constant, this is what ls/tree do and improves
|
||||
* performance significantly due to the cursor DOM element not needing to change
|
||||
*/
|
||||
data[data.length - 1] = 0x0D; // \r
|
||||
testData.push(data);
|
||||
}
|
||||
@@ -723,8 +739,10 @@ async function loadTestLongLines(term: Terminal, addons: AddonCollection): Promi
|
||||
for (let i = 1; i < count + 1; i++) {
|
||||
data[i] = 0x61 + Math.floor(Math.random() * (0x7A - 0x61));
|
||||
}
|
||||
// End each line with \r so the cursor remains constant, this is what ls/tree do and improves
|
||||
// performance significantly due to the cursor DOM element not needing to change
|
||||
/*
|
||||
* End each line with \r so the cursor remains constant, this is what ls/tree do and improves
|
||||
* performance significantly due to the cursor DOM element not needing to change
|
||||
*/
|
||||
data[data.length - 1] = 0x0D; // \r
|
||||
testData.push(data);
|
||||
}
|
||||
@@ -734,8 +752,10 @@ async function loadTestLongLines(term: Terminal, addons: AddonCollection): Promi
|
||||
try {
|
||||
term.write(d);
|
||||
} catch {
|
||||
// Flush events when cap is hit and try again(workaround for not having flow control in
|
||||
// demo)
|
||||
/*
|
||||
* Flush events when cap is hit and try again(workaround for not having flow control in
|
||||
* demo)
|
||||
*/
|
||||
await new Promise<void>(r => term.write('', () => r()));
|
||||
term.write(d);
|
||||
}
|
||||
|
||||
@@ -220,8 +220,10 @@ export function writeUnicodeTable(term: Terminal, name: string, start: number, e
|
||||
|
||||
term.write('\n\r');
|
||||
|
||||
// Render reserved labels that appear after the first label (below the row)
|
||||
// Show one label per non-contiguous reserved range
|
||||
/*
|
||||
* Render reserved labels that appear after the first label (below the row)
|
||||
* Show one label per non-contiguous reserved range
|
||||
*/
|
||||
const lateReserved = rowLabels.length > 0
|
||||
? rowReserved.filter(r => r.col >= rowLabels[0].col)
|
||||
: rowReserved;
|
||||
|
||||
@@ -180,9 +180,11 @@ function startServer(): void {
|
||||
}
|
||||
const send = (USE_BINARY ? bufferUtf8 : buffer)(ws, 3, 262144);
|
||||
|
||||
// WARNING: This is a naive implementation that will not throttle the flow of data. This means
|
||||
// it could flood the communication channel and make the terminal unresponsive. Learn more about
|
||||
// the problem and how to implement flow control at https://xtermjs.org/docs/guides/flowcontrol/
|
||||
/*
|
||||
* WARNING: This is a naive implementation that will not throttle the flow of data. This means
|
||||
* it could flood the communication channel and make the terminal unresponsive. Learn more about
|
||||
* the problem and how to implement flow control at https://xtermjs.org/docs/guides/flowcontrol/
|
||||
*/
|
||||
term.onData(function(data) {
|
||||
try {
|
||||
send(data as string & Buffer);
|
||||
@@ -209,8 +211,10 @@ function startServer(): void {
|
||||
app.listen(port, host, 0);
|
||||
}
|
||||
|
||||
// HACK: There is an EPIPE error thrown when reloading a page. This only seems to happen on Windows
|
||||
// and it's unclear why it happens. Suppressing the error here since this is just the demo server.
|
||||
/*
|
||||
* HACK: There is an EPIPE error thrown when reloading a page. This only seems to happen on Windows
|
||||
* and it's unclear why it happens. Suppressing the error here since this is just the demo server.
|
||||
*/
|
||||
process.on('uncaughtException', (error) => {
|
||||
if (process.platform === 'win32' && error.message === 'read EPIPE') {
|
||||
return;
|
||||
|
||||
@@ -88,6 +88,7 @@ export default tseslint.config(
|
||||
'jsdoc/check-param-names': 'warn',
|
||||
'jsdoc/no-multi-asterisks': 'warn',
|
||||
'keyword-spacing': 'warn',
|
||||
'multiline-comment-style': ['warn', 'starred-block'],
|
||||
'max-len': ['warn', {
|
||||
code: 1000,
|
||||
comments: 100,
|
||||
|
||||
@@ -21,8 +21,10 @@ const enum BoundaryPosition {
|
||||
BOTTOM
|
||||
}
|
||||
|
||||
// Turn this on to unhide the accessibility tree and display it under
|
||||
// (instead of overlapping with) the terminal.
|
||||
/*
|
||||
* Turn this on to unhide the accessibility tree and display it under
|
||||
* (instead of overlapping with) the terminal.
|
||||
*/
|
||||
const DEBUG = false;
|
||||
|
||||
export class AccessibilityManager extends Disposable {
|
||||
@@ -223,8 +225,10 @@ export class AccessibilityManager extends Disposable {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't scroll when the last focused item was not the second row (focus is going the other
|
||||
// direction)
|
||||
/*
|
||||
* Don't scroll when the last focused item was not the second row (focus is going the other
|
||||
* direction)
|
||||
*/
|
||||
if (e.relatedTarget !== beforeBoundaryElement) {
|
||||
return;
|
||||
}
|
||||
@@ -283,9 +287,11 @@ export class AccessibilityManager extends Disposable {
|
||||
}
|
||||
|
||||
if (selection.isCollapsed) {
|
||||
// Only do something when the anchorNode is inside the row container. This
|
||||
// behavior mirrors what we do with mouse --- if the mouse clicks
|
||||
// somewhere outside of the terminal, we don't clear the selection.
|
||||
/*
|
||||
* Only do something when the anchorNode is inside the row container. This
|
||||
* behavior mirrors what we do with mouse --- if the mouse clicks
|
||||
* somewhere outside of the terminal, we don't clear the selection.
|
||||
*/
|
||||
if (this._rowContainer.contains(selection.anchorNode)) {
|
||||
this._terminal.clearSelection();
|
||||
}
|
||||
|
||||
@@ -22,8 +22,10 @@ export function bracketTextForPaste(text: string, bracketedPasteMode: boolean):
|
||||
if (!bracketedPasteMode) {
|
||||
return text;
|
||||
}
|
||||
// Sanitize pasted text to prevent injected escape sequences (e.g. exiting bracketed paste)
|
||||
// by replacing ESC (\x1b) with its visible representation U+241B (␛).
|
||||
/*
|
||||
* Sanitize pasted text to prevent injected escape sequences (e.g. exiting bracketed paste)
|
||||
* by replacing ESC (\x1b) with its visible representation U+241B (␛).
|
||||
*/
|
||||
const sanitizedText = text.replace(/\x1b/g, '\u241b');
|
||||
return `\x1b[200~${sanitizedText}\x1b[201~`;
|
||||
}
|
||||
|
||||
@@ -323,8 +323,10 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
* Binds the desired blur behavior on a given terminal object.
|
||||
*/
|
||||
private _handleTextAreaBlur(): void {
|
||||
// Text can safely be removed on blur. Doing it earlier could interfere with
|
||||
// screen readers reading it out.
|
||||
/*
|
||||
* Text can safely be removed on blur. Doing it earlier could interfere with
|
||||
* screen readers reading it out.
|
||||
*/
|
||||
this.textarea!.value = '';
|
||||
this.refresh(this.buffer.y, this.buffer.y);
|
||||
if (this.coreService.decPrivateModes.sendFocus) {
|
||||
@@ -350,8 +352,10 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
const cursorTop = this.buffer.y * this._renderService.dimensions.css.cell.height;
|
||||
const cursorLeft = cursorX * this._renderService.dimensions.css.cell.width;
|
||||
|
||||
// Sync the textarea to the exact position of the composition view so the IME knows where the
|
||||
// text is.
|
||||
/*
|
||||
* Sync the textarea to the exact position of the composition view so the IME knows where the
|
||||
* text is.
|
||||
*/
|
||||
this.textarea.style.left = cursorLeft + 'px';
|
||||
this.textarea.style.top = cursorTop + 'px';
|
||||
this.textarea.style.width = cellWidth + 'px';
|
||||
@@ -368,8 +372,10 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
|
||||
// Bind clipboard functionality
|
||||
this._register(addDisposableListener(this.element!, 'copy', (event: ClipboardEvent) => {
|
||||
// If mouse events are active it means the selection manager is disabled and
|
||||
// copy should be handled by the host program.
|
||||
/*
|
||||
* If mouse events are active it means the selection manager is disabled and
|
||||
* copy should be handled by the host program.
|
||||
*/
|
||||
if (!this.hasSelection()) {
|
||||
return;
|
||||
}
|
||||
@@ -393,12 +399,16 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
}));
|
||||
}
|
||||
|
||||
// Move the textarea under the cursor when middle clicking on Linux to ensure
|
||||
// middle click to paste selection works. This only appears to work in Chrome
|
||||
// at the time is writing.
|
||||
/*
|
||||
* Move the textarea under the cursor when middle clicking on Linux to ensure
|
||||
* middle click to paste selection works. This only appears to work in Chrome
|
||||
* at the time is writing.
|
||||
*/
|
||||
if (Browser.isLinux) {
|
||||
// Use auxclick event over mousedown the latter doesn't seem to work. Note
|
||||
// that the regular click event doesn't fire for the middle mouse button.
|
||||
/*
|
||||
* Use auxclick event over mousedown the latter doesn't seem to work. Note
|
||||
* that the regular click event doesn't fire for the middle mouse button.
|
||||
*/
|
||||
this._register(addDisposableListener(this.element!, 'auxclick', (event: MouseEvent) => {
|
||||
if (event.button === 1) {
|
||||
moveTextAreaUnderMouseCursor(event, this.textarea!, this.screenElement!);
|
||||
@@ -415,11 +425,13 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
this._register(addDisposableListener(this.textarea!, 'keydown', (ev: KeyboardEvent) => this._keyDown(ev), true));
|
||||
this._register(addDisposableListener(this.textarea!, 'keypress', (ev: KeyboardEvent) => this._keyPress(ev), true));
|
||||
this._register(addDisposableListener(this.textarea!, 'compositionstart', () => {
|
||||
// Ensure the textarea is synced to the latest cursor location before composition begins. This
|
||||
// is to workaround a problem where highly dynamic TUIs like agentic CLIs reprint agressively
|
||||
// would cause the IME to appear in the wrong position. The theory is that when the IME is
|
||||
// triggered during a partial render the textarea position becomes locked and will not move
|
||||
// until it is hidden and a custom move occurs.
|
||||
/*
|
||||
* Ensure the textarea is synced to the latest cursor location before composition begins. This
|
||||
* is to workaround a problem where highly dynamic TUIs like agentic CLIs reprint agressively
|
||||
* would cause the IME to appear in the wrong position. The theory is that when the IME is
|
||||
* triggered during a partial render the textarea position becomes locked and will not move
|
||||
* until it is hidden and a custom move occurs.
|
||||
*/
|
||||
this._syncTextArea();
|
||||
this._compositionHelper!.compositionstart();
|
||||
this._compositionHelper!.updateCompositionElements();
|
||||
@@ -467,8 +479,10 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
this._register(this.optionsService.onSpecificOptionChange('allowTransparency', value => this.element!.classList.toggle('allow-transparency', value)));
|
||||
parent.appendChild(this.element);
|
||||
|
||||
// Performance: Use a document fragment to build the terminal
|
||||
// viewport and helper elements detached from the DOM
|
||||
/*
|
||||
* Performance: Use a document fragment to build the terminal
|
||||
* viewport and helper elements detached from the DOM
|
||||
*/
|
||||
const fragment = this._document.createDocumentFragment();
|
||||
this._viewportElement = this._document.createElement('div');
|
||||
this._viewportElement.classList.add('xterm-viewport');
|
||||
@@ -477,8 +491,10 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
this.screenElement = this._document.createElement('div');
|
||||
this.screenElement.classList.add('xterm-screen');
|
||||
this._register(addDisposableListener(this.screenElement, 'mousemove', (ev: MouseEvent) => this.updateCursorStyle(ev)));
|
||||
// Create the container that will hold helpers like the textarea for
|
||||
// capturing DOM Events. Then produce the helpers.
|
||||
/*
|
||||
* Create the container that will hold helpers like the textarea for
|
||||
* capturing DOM Events. Then produce the helpers.
|
||||
*/
|
||||
this._helperContainer = this._document.createElement('div');
|
||||
this._helperContainer.classList.add('xterm-helpers');
|
||||
this.screenElement.appendChild(this._helperContainer);
|
||||
@@ -488,8 +504,10 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
this.textarea.classList.add('xterm-helper-textarea');
|
||||
this.textarea.setAttribute('aria-label', Strings.promptLabel.get());
|
||||
if (!Browser.isChromeOS) {
|
||||
// ChromeVox on ChromeOS does not like this. See
|
||||
// https://issuetracker.google.com/issues/260170397
|
||||
/*
|
||||
* ChromeVox on ChromeOS does not like this. See
|
||||
* https://issuetracker.google.com/issues/260170397
|
||||
*/
|
||||
this.textarea.setAttribute('aria-multiline', 'false');
|
||||
}
|
||||
this.textarea.setAttribute('autocorrect', 'off');
|
||||
@@ -499,8 +517,10 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
this._register(this.optionsService.onSpecificOptionChange('disableStdin', () => textarea.readOnly = this.optionsService.rawOptions.disableStdin));
|
||||
this.textarea.readOnly = this.optionsService.rawOptions.disableStdin;
|
||||
|
||||
// Register the core browser service before the generic textarea handlers are registered so it
|
||||
// handles them first. Otherwise the renderers may use the wrong focus state.
|
||||
/*
|
||||
* Register the core browser service before the generic textarea handlers are registered so it
|
||||
* handles them first. Otherwise the renderers may use the wrong focus state.
|
||||
*/
|
||||
this._coreBrowserService = this._register(this._instantiationService.createInstance(CoreBrowserService,
|
||||
this.textarea,
|
||||
parent.ownerDocument.defaultView ?? window,
|
||||
@@ -598,9 +618,11 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
this._register(this._selectionService.onSelectionChange(() => this._onSelectionChange.fire()));
|
||||
this._register(this._selectionService.onRequestRedraw(e => this._renderService!.handleSelectionChanged(e.start, e.end, e.columnSelectMode)));
|
||||
this._register(this._selectionService.onLinuxMouseSelection(text => {
|
||||
// If there's a new selection, put it into the textarea, focus and select it
|
||||
// in order to register it as a selection on the OS. This event is fired
|
||||
// only on Linux to enable middle click to paste selection.
|
||||
/*
|
||||
* If there's a new selection, put it into the textarea, focus and select it
|
||||
* in order to register it as a selection on the OS. This event is fired
|
||||
* only on Linux to enable middle click to paste selection.
|
||||
*/
|
||||
this.textarea!.value = text;
|
||||
this.textarea!.focus();
|
||||
this.textarea!.select();
|
||||
@@ -625,8 +647,10 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
}
|
||||
|
||||
if (this.options.screenReaderMode) {
|
||||
// Note that this must be done *after* the renderer is created in order to
|
||||
// ensure the correct order of the dprchange event
|
||||
/*
|
||||
* Note that this must be done *after* the renderer is created in order to
|
||||
* ensure the correct order of the dprchange event
|
||||
*/
|
||||
this._accessibilityManager.value = this._instantiationService.createInstance(AccessibilityManager, this);
|
||||
}
|
||||
this._register(this.optionsService.onSpecificOptionChange('screenReaderMode', e => this._handleScreenReaderModeOptionChange(e)));
|
||||
@@ -651,8 +675,10 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
// Initialize global actions that need to be taken on the document.
|
||||
this._initGlobal();
|
||||
|
||||
// Listen for mouse events and translate
|
||||
// them into terminal mouse protocols.
|
||||
/*
|
||||
* Listen for mouse events and translate
|
||||
* them into terminal mouse protocols.
|
||||
*/
|
||||
this._mouseService.bindMouse({
|
||||
element: this.element!,
|
||||
screenElement: this.screenElement!,
|
||||
@@ -892,9 +918,11 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
return true;
|
||||
}
|
||||
|
||||
// HACK: Process A-Z in the keypress event to fix an issue with macOS IMEs where lower case
|
||||
// letters cannot be input while caps lock is on. Skip this hack when using kitty protocol
|
||||
// or Win32 input mode as they need to send proper sequences for all key events.
|
||||
/*
|
||||
* HACK: Process A-Z in the keypress event to fix an issue with macOS IMEs where lower case
|
||||
* letters cannot be input while caps lock is on. Skip this hack when using kitty protocol
|
||||
* or Win32 input mode as they need to send proper sequences for all key events.
|
||||
*/
|
||||
if (!this._keyboardService.useKitty && !this._keyboardService.useWin32InputMode && event.key && !event.ctrlKey && !event.altKey && !event.metaKey && event.key.length === 1) {
|
||||
if (event.key.charCodeAt(0) >= 65 && event.key.charCodeAt(0) <= 90) {
|
||||
return true;
|
||||
@@ -906,9 +934,11 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If ctrl+c or enter is being sent, clear out the textarea. This is done so that screen readers
|
||||
// will announce deleted characters. This will not work 100% of the time but it should cover
|
||||
// most scenarios.
|
||||
/*
|
||||
* If ctrl+c or enter is being sent, clear out the textarea. This is done so that screen readers
|
||||
* will announce deleted characters. This will not work 100% of the time but it should cover
|
||||
* most scenarios.
|
||||
*/
|
||||
if (result.key === C0.ETX || result.key === C0.CR) {
|
||||
this.textarea!.value = '';
|
||||
}
|
||||
@@ -918,10 +948,12 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
this._showCursor();
|
||||
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.
|
||||
/*
|
||||
* 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) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
@@ -1010,8 +1042,10 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
|
||||
this._keyPressHandled = true;
|
||||
|
||||
// The key was handled so clear the dead key state, otherwise certain keystrokes like arrow
|
||||
// keys could be ignored
|
||||
/*
|
||||
* The key was handled so clear the dead key state, otherwise certain keystrokes like arrow
|
||||
* keys could be ignored
|
||||
*/
|
||||
this._unprocessedDeadKey = false;
|
||||
|
||||
return true;
|
||||
@@ -1024,16 +1058,20 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
* @param ev The input event to be handled.
|
||||
*/
|
||||
protected _inputEvent(ev: InputEvent): boolean {
|
||||
// Only support emoji IMEs when screen reader mode is disabled as the event must bubble up to
|
||||
// support reading out character input which can doubling up input characters
|
||||
// Based on these event traces: https://github.com/xtermjs/xterm.js/issues/3679
|
||||
/*
|
||||
* Only support emoji IMEs when screen reader mode is disabled as the event must bubble up to
|
||||
* support reading out character input which can doubling up input characters
|
||||
* Based on these event traces: https://github.com/xtermjs/xterm.js/issues/3679
|
||||
*/
|
||||
if (ev.data && ev.inputType === 'insertText' && (!ev.composed || !this._keyDownSeen) && !this.optionsService.rawOptions.screenReaderMode) {
|
||||
if (this._keyPressHandled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The key was handled so clear the dead key state, otherwise certain keystrokes like arrow
|
||||
// keys could be ignored
|
||||
/*
|
||||
* The key was handled so clear the dead key state, otherwise certain keystrokes like arrow
|
||||
* keys could be ignored
|
||||
*/
|
||||
this._unprocessedDeadKey = false;
|
||||
|
||||
const text = ev.data;
|
||||
@@ -1083,8 +1121,10 @@ export class CoreBrowserTerminal extends CoreTerminal implements ITerminal {
|
||||
for (let i = 1; i < this.rows; i++) {
|
||||
this.buffer.lines.push(this.buffer.getBlankLine(DEFAULT_ATTR_DATA));
|
||||
}
|
||||
// IMPORTANT: Fire scroll event before viewport is reset. This ensures embedders get the clear
|
||||
// scroll event and that the viewport's state will be valid for immediate writes.
|
||||
/*
|
||||
* IMPORTANT: Fire scroll event before viewport is reset. This ensures embedders get the clear
|
||||
* scroll event and that the viewport's state will be valid for immediate writes.
|
||||
*/
|
||||
this._onScroll.fire({ position: this.buffer.ydisp });
|
||||
this.refresh(0, this.rows - 1);
|
||||
}
|
||||
|
||||
+26
-14
@@ -87,9 +87,11 @@ export class Linkifier extends Disposable implements ILinkifier2 {
|
||||
}
|
||||
|
||||
private _handleHover(position: IBufferCellPosition): void {
|
||||
// TODO: This currently does not cache link provider results across wrapped lines, activeLine
|
||||
// should be something like `activeRange: {startY, endY}`
|
||||
// Check if we need to clear the link
|
||||
/*
|
||||
* TODO: This currently does not cache link provider results across wrapped lines, activeLine
|
||||
* should be something like `activeRange: {startY, endY}`
|
||||
* Check if we need to clear the link
|
||||
*/
|
||||
if (this._activeLine !== position.y || this._wasResized) {
|
||||
this._clearCurrentLink();
|
||||
this._askForLink(position, false);
|
||||
@@ -125,9 +127,11 @@ export class Linkifier extends Disposable implements ILinkifier2 {
|
||||
const existingReply = this._activeProviderReplies?.get(i);
|
||||
// If there isn't a reply, the provider hasn't responded yet.
|
||||
|
||||
// TODO: If there isn't a reply yet it means that the provider is still resolving. Ensuring
|
||||
// provideLinks isn't triggered again saves ILink.hover firing twice though. This probably
|
||||
// needs promises to get fixed
|
||||
/*
|
||||
* TODO: If there isn't a reply yet it means that the provider is still resolving. Ensuring
|
||||
* provideLinks isn't triggered again saves ILink.hover firing twice though. This probably
|
||||
* needs promises to get fixed
|
||||
*/
|
||||
if (existingReply) {
|
||||
linkProvided = this._checkLinkProviderResult(i, position, linkProvided);
|
||||
}
|
||||
@@ -140,8 +144,10 @@ export class Linkifier extends Disposable implements ILinkifier2 {
|
||||
this._activeProviderReplies?.set(i, linksWithState);
|
||||
linkProvided = this._checkLinkProviderResult(i, position, linkProvided);
|
||||
|
||||
// If all providers have responded, remove lower priority links that intersect ranges of
|
||||
// higher priority links
|
||||
/*
|
||||
* If all providers have responded, remove lower priority links that intersect ranges of
|
||||
* higher priority links
|
||||
*/
|
||||
if (this._activeProviderReplies?.size === this._linkProviderService.linkProviders.length) {
|
||||
this._removeIntersectingLinks(position.y, this._activeProviderReplies);
|
||||
}
|
||||
@@ -187,8 +193,10 @@ export class Linkifier extends Disposable implements ILinkifier2 {
|
||||
}
|
||||
}
|
||||
|
||||
// If all providers with higher priority came back undefined, then this provider's link for
|
||||
// the position should be used
|
||||
/*
|
||||
* If all providers with higher priority came back undefined, then this provider's link for
|
||||
* the position should be used
|
||||
*/
|
||||
if (!hasLinkBefore && links) {
|
||||
const linkAtPosition = links.find(link => this._linkAtPosition(link.link, position));
|
||||
if (linkAtPosition) {
|
||||
@@ -296,15 +304,19 @@ export class Linkifier extends Disposable implements ILinkifier2 {
|
||||
}
|
||||
});
|
||||
|
||||
// Listen to viewport changes to re-render the link under the cursor (only when the line the
|
||||
// link is on changes)
|
||||
/*
|
||||
* Listen to viewport changes to re-render the link under the cursor (only when the line the
|
||||
* link is on changes)
|
||||
*/
|
||||
this._linkCacheDisposables.push(this._renderService.onRenderedViewportChange(e => {
|
||||
// Sanity check, this shouldn't happen in practice as this listener would be disposed
|
||||
if (!this._currentLink) {
|
||||
return;
|
||||
}
|
||||
// When start is 0 a scroll most likely occurred, make sure links above the fold also get
|
||||
// cleared.
|
||||
/*
|
||||
* When start is 0 a scroll most likely occurred, make sure links above the fold also get
|
||||
* cleared.
|
||||
*/
|
||||
const start = e.start === 0 ? 0 : e.start + 1 + this._bufferService.buffer.ydisp;
|
||||
const end = this._bufferService.buffer.ydisp + 1 + e.end;
|
||||
// Only clear the link if the viewport change happened on this line
|
||||
|
||||
@@ -33,8 +33,10 @@ export class OscLinkProvider implements ILinkProvider {
|
||||
let currentStart = -1;
|
||||
let finishLink = false;
|
||||
for (let x = 0; x < lineLength; x++) {
|
||||
// Minor optimization, only check for content if there isn't a link in case the link ends with
|
||||
// a null cell
|
||||
/*
|
||||
* Minor optimization, only check for content if there isn't a link in case the link ends with
|
||||
* a null cell
|
||||
*/
|
||||
if (currentStart === -1 && !line.hasContent(x)) {
|
||||
continue;
|
||||
}
|
||||
@@ -107,8 +109,10 @@ export class OscLinkProvider implements ILinkProvider {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Handle fetching and returning other link ranges to underline other links with the same
|
||||
// id
|
||||
/*
|
||||
* TODO: Handle fetching and returning other link ranges to underline other links with the same
|
||||
* id
|
||||
*/
|
||||
callback(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -576,14 +576,16 @@ describe('Terminal', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
term.clearSelection = () => { };
|
||||
// term.compositionHelper = {
|
||||
// isComposing: false,
|
||||
// keydown: {
|
||||
// bind: () => {
|
||||
// return () => { return true; };
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
/*
|
||||
* term.compositionHelper = {
|
||||
* isComposing: false,
|
||||
* keydown: {
|
||||
* bind: () => {
|
||||
* return () => { return true; };
|
||||
* }
|
||||
* }
|
||||
* };
|
||||
*/
|
||||
evKeyDown = {
|
||||
preventDefault: () => { },
|
||||
stopPropagation: () => { },
|
||||
@@ -1148,8 +1150,10 @@ describe('Terminal', () => {
|
||||
|
||||
// FIXME: move to common/CoreTerminal.test once the trimming is moved over
|
||||
describe('marker lifecycle', () => {
|
||||
// create a 10x5 terminal with markers on every line
|
||||
// to test marker lifecycle under various terminal actions
|
||||
/*
|
||||
* create a 10x5 terminal with markers on every line
|
||||
* to test marker lifecycle under various terminal actions
|
||||
*/
|
||||
let markers: IMarker[];
|
||||
let disposeStack: IMarker[];
|
||||
let term: TestTerminal;
|
||||
|
||||
@@ -43,8 +43,10 @@ export class TimeBasedDebouncer implements IRenderDebouncer {
|
||||
this._rowStart = this._rowStart !== undefined ? Math.min(this._rowStart, rowStart) : rowStart;
|
||||
this._rowEnd = this._rowEnd !== undefined ? Math.max(this._rowEnd, rowEnd) : rowEnd;
|
||||
|
||||
// Only refresh if the time since last refresh is above a threshold, otherwise wait for
|
||||
// enough time to pass before refreshing again.
|
||||
/*
|
||||
* Only refresh if the time since last refresh is above a threshold, otherwise wait for
|
||||
* enough time to pass before refreshing again.
|
||||
*/
|
||||
const refreshRequestTime: number = performance.now();
|
||||
if (refreshRequestTime - this._lastRefreshMs >= this._debounceThresholdMS) {
|
||||
// Enough time has lapsed since the last refresh; refresh immediately
|
||||
|
||||
@@ -201,8 +201,10 @@ export const DEFAULT_ANSI_COLORS = Object.freeze((() => {
|
||||
css.toColor('#eeeeec')
|
||||
];
|
||||
|
||||
// Fill in the remaining 240 ANSI colors.
|
||||
// Generate colors (16-231)
|
||||
/*
|
||||
* Fill in the remaining 240 ANSI colors.
|
||||
* Generate colors (16-231)
|
||||
*/
|
||||
const v = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff];
|
||||
for (let i = 0; i < 216; i++) {
|
||||
const r = v[(i / 36) % 6 | 0];
|
||||
|
||||
+21
-11
@@ -99,16 +99,20 @@ export class Viewport extends Disposable {
|
||||
|
||||
this._register(this._bufferService.onResize(() => this.queueSync()));
|
||||
this._register(this._bufferService.buffers.onBufferActivate(() => {
|
||||
// Reset _latestYDisp when switching buffers to prevent stale scroll position
|
||||
// from alt buffer contaminating normal buffer scroll position
|
||||
/*
|
||||
* Reset _latestYDisp when switching buffers to prevent stale scroll position
|
||||
* from alt buffer contaminating normal buffer scroll position
|
||||
*/
|
||||
this._latestYDisp = undefined;
|
||||
this.queueSync();
|
||||
}));
|
||||
this._register(this._bufferService.onScroll(() => this._sync()));
|
||||
|
||||
// Flush deferred viewport sync after a render completes (e.g. after ESU ends
|
||||
// synchronized output mode). This ensures DOM scroll position updates atomically
|
||||
// with the canvas render.
|
||||
/*
|
||||
* Flush deferred viewport sync after a render completes (e.g. after ESU ends
|
||||
* synchronized output mode). This ensures DOM scroll position updates atomically
|
||||
* with the canvas render.
|
||||
*/
|
||||
this._register(this._renderService.onRender(() => {
|
||||
if (this._needsSyncOnRender) {
|
||||
this._needsSyncOnRender = false;
|
||||
@@ -173,16 +177,20 @@ export class Viewport extends Disposable {
|
||||
if (!this._renderService || this._isSyncing) {
|
||||
return;
|
||||
}
|
||||
// Defer DOM scroll updates during synchronized output to prevent visible
|
||||
// scroll position flickering while the canvas content is frozen.
|
||||
/*
|
||||
* Defer DOM scroll updates during synchronized output to prevent visible
|
||||
* scroll position flickering while the canvas content is frozen.
|
||||
*/
|
||||
if (this._coreService.decPrivateModes.synchronizedOutput) {
|
||||
this._needsSyncOnRender = true;
|
||||
return;
|
||||
}
|
||||
this._isSyncing = true;
|
||||
|
||||
// Ignore any onScroll event that happens as a result of dimensions changing as this should
|
||||
// never cause a scrollLines call, only setScrollPosition can do that.
|
||||
/*
|
||||
* Ignore any onScroll event that happens as a result of dimensions changing as this should
|
||||
* never cause a scrollLines call, only setScrollPosition can do that.
|
||||
*/
|
||||
this._suppressOnScrollHandler = true;
|
||||
this._scrollableElement.setScrollDimensions({
|
||||
height: this._renderService.dimensions.css.canvas.height,
|
||||
@@ -190,8 +198,10 @@ export class Viewport extends Disposable {
|
||||
});
|
||||
this._suppressOnScrollHandler = false;
|
||||
|
||||
// If ydisp has been changed by some other component (input/buffer), then stop animating smooth
|
||||
// scroll and scroll there immediately.
|
||||
/*
|
||||
* If ydisp has been changed by some other component (input/buffer), then stop animating smooth
|
||||
* scroll and scroll there immediately.
|
||||
*/
|
||||
if (ydisp !== this._latestYDisp) {
|
||||
this._scrollableElement.setScrollPosition({
|
||||
scrollTop: ydisp * this._renderService.dimensions.css.cell.height
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
/*
|
||||
*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
*--------------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import { ICoreBrowserService, IRenderService } from 'browser/services/Services';
|
||||
import { Disposable, toDisposable } from 'common/Lifecycle';
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
/*
|
||||
*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
*--------------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import { IInternalDecoration } from 'common/services/Services';
|
||||
|
||||
@@ -32,9 +34,11 @@ interface IMinimalDecorationForColorZone {
|
||||
export class ColorZoneStore implements IColorZoneStore {
|
||||
private _zones: IColorZone[] = [];
|
||||
|
||||
// The zone pool is used to keep zone objects from being freed between clearing the color zone
|
||||
// store and fetching the zones. This helps reduce GC pressure since the color zones are
|
||||
// accumulated on potentially every scroll event.
|
||||
/*
|
||||
* The zone pool is used to keep zone objects from being freed between clearing the color zone
|
||||
* store and fetching the zones. This helps reduce GC pressure since the color zones are
|
||||
* accumulated on potentially every scroll event.
|
||||
*/
|
||||
private _zonePool: IColorZone[] = [];
|
||||
private _zonePoolIndex = 0;
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
/*
|
||||
*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
*--------------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import { ColorZoneStore, IColorZone, IColorZoneStore } from 'browser/decorations/ColorZoneStore';
|
||||
import { ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';
|
||||
@@ -12,8 +14,10 @@ const enum Constants {
|
||||
OVERVIEW_RULER_BORDER_WIDTH = 1
|
||||
}
|
||||
|
||||
// Helper objects to avoid excessive calculation and garbage collection during rendering. These are
|
||||
// static values for each render and can be accessed using the decoration position as the key.
|
||||
/*
|
||||
* Helper objects to avoid excessive calculation and garbage collection during rendering. These are
|
||||
* static values for each render and can be accessed using the decoration position as the key.
|
||||
*/
|
||||
const drawHeight = {
|
||||
full: 0,
|
||||
left: 0,
|
||||
|
||||
@@ -236,9 +236,11 @@ describe('CompositionHelper', () => {
|
||||
|
||||
it('Should insert middle composition and subsequent input without appending existing trailing text', (done) => {
|
||||
textarea.value = '一二';
|
||||
// screenReaderMode keeps textarea content/selection for assistive technologies (eg. screen
|
||||
// readers), so the caret can be moved within the textarea (eg. via arrow keys) before
|
||||
// starting composition.
|
||||
/*
|
||||
* screenReaderMode keeps textarea content/selection for assistive technologies (eg. screen
|
||||
* readers), so the caret can be moved within the textarea (eg. via arrow keys) before
|
||||
* starting composition.
|
||||
*/
|
||||
textarea.selectionStart = 1;
|
||||
textarea.selectionEnd = 1;
|
||||
|
||||
|
||||
@@ -72,8 +72,10 @@ export class CompositionHelper {
|
||||
*/
|
||||
public compositionstart(): void {
|
||||
this._isComposing = true;
|
||||
// It's important to use the selection here instead of textarea length to avoid conflicts with
|
||||
// screen reader mode
|
||||
/*
|
||||
* It's important to use the selection here instead of textarea length to avoid conflicts with
|
||||
* screen reader mode
|
||||
*/
|
||||
const start = this._textarea.selectionStart ?? this._textarea.value.length;
|
||||
const end = this._textarea.selectionEnd ?? start;
|
||||
this._compositionPosition.start = Math.min(start, end);
|
||||
@@ -89,8 +91,10 @@ export class CompositionHelper {
|
||||
* @param ev The event.
|
||||
*/
|
||||
public compositionupdate(ev: Pick<CompositionEvent, 'data'>): void {
|
||||
// Mark text as LTR, direction=rtl is used in CSS so the end of the text is followed for long
|
||||
// compositions
|
||||
/*
|
||||
* Mark text as LTR, direction=rtl is used in CSS so the end of the text is followed for long
|
||||
* compositions
|
||||
*/
|
||||
this._compositionView.textContent = `\u200E${ev.data}\u200E`;
|
||||
this.updateCompositionElements();
|
||||
setTimeout(() => {
|
||||
@@ -115,22 +119,28 @@ export class CompositionHelper {
|
||||
public keydown(ev: KeyboardEvent): boolean {
|
||||
if (this._isComposing || this._isSendingComposition) {
|
||||
if (ev.keyCode === 20 || ev.keyCode === 229) {
|
||||
// 20 is CapsLock, 229 is Enter
|
||||
// Continue composing if the keyCode is the "composition character"
|
||||
/*
|
||||
* 20 is CapsLock, 229 is Enter
|
||||
* Continue composing if the keyCode is the "composition character"
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
if (ev.keyCode === 16 || ev.keyCode === 17 || ev.keyCode === 18) {
|
||||
// Continue composing if the keyCode is a modifier key
|
||||
return false;
|
||||
}
|
||||
// Finish composition immediately. This is mainly here for the case where enter is
|
||||
// pressed and the handler needs to be triggered before the command is executed.
|
||||
/*
|
||||
* Finish composition immediately. This is mainly here for the case where enter is
|
||||
* pressed and the handler needs to be triggered before the command is executed.
|
||||
*/
|
||||
this._finalizeComposition(false);
|
||||
}
|
||||
|
||||
if (ev.keyCode === 229) {
|
||||
// If the "composition character" is used but gets to this point it means a non-composition
|
||||
// character (eg. numbers and punctuation) was pressed when the IME was active.
|
||||
/*
|
||||
* If the "composition character" is used but gets to this point it means a non-composition
|
||||
* character (eg. numbers and punctuation) was pressed when the IME was active.
|
||||
*/
|
||||
this._handleAnyTextareaChanges();
|
||||
return false;
|
||||
}
|
||||
@@ -156,39 +166,49 @@ export class CompositionHelper {
|
||||
const input = this._textarea.value.substring(this._compositionPosition.start, this._compositionPosition.end);
|
||||
this._coreService.triggerDataEvent(input, true);
|
||||
} else {
|
||||
// Make a deep copy of the composition position here as a new compositionstart event may
|
||||
// fire before the setTimeout executes.
|
||||
/*
|
||||
* Make a deep copy of the composition position here as a new compositionstart event may
|
||||
* fire before the setTimeout executes.
|
||||
*/
|
||||
const currentCompositionPosition = {
|
||||
start: this._compositionPosition.start,
|
||||
end: this._compositionPosition.end
|
||||
};
|
||||
const currentCompositionSuffix = this._compositionSuffix;
|
||||
|
||||
// Since composition* events happen before the changes take place in the textarea on most
|
||||
// browsers, use a setTimeout with 0ms time to allow the native compositionend event to
|
||||
// complete. This ensures the correct character is retrieved.
|
||||
// This solution was used because:
|
||||
// - The compositionend event's data property is unreliable, at least on Chromium
|
||||
// - The last compositionupdate event's data property does not always accurately describe
|
||||
// the character, a counter example being Korean where an ending consonsant can move to
|
||||
// the following character if the following input is a vowel.
|
||||
/*
|
||||
* Since composition* events happen before the changes take place in the textarea on most
|
||||
* browsers, use a setTimeout with 0ms time to allow the native compositionend event to
|
||||
* complete. This ensures the correct character is retrieved.
|
||||
* This solution was used because:
|
||||
* - The compositionend event's data property is unreliable, at least on Chromium
|
||||
* - The last compositionupdate event's data property does not always accurately describe
|
||||
* the character, a counter example being Korean where an ending consonsant can move to
|
||||
* the following character if the following input is a vowel.
|
||||
*/
|
||||
this._isSendingComposition = true;
|
||||
setTimeout(() => {
|
||||
// Ensure that the input has not already been sent
|
||||
if (this._isSendingComposition) {
|
||||
this._isSendingComposition = false;
|
||||
let input;
|
||||
// Add length of data already sent due to keydown event,
|
||||
// otherwise input characters can be duplicated. (Issue #3191)
|
||||
/*
|
||||
* Add length of data already sent due to keydown event,
|
||||
* otherwise input characters can be duplicated. (Issue #3191)
|
||||
*/
|
||||
currentCompositionPosition.start += this._dataAlreadySent.length;
|
||||
if (this._isComposing) {
|
||||
// Use the start position of the new composition to get the string
|
||||
// if a new composition has started.
|
||||
/*
|
||||
* Use the start position of the new composition to get the string
|
||||
* if a new composition has started.
|
||||
*/
|
||||
input = this._textarea.value.substring(currentCompositionPosition.start, this._compositionPosition.start);
|
||||
} else {
|
||||
// Keep support for non-composition characters typed immediately after composition end
|
||||
// while avoiding re-sending the trailing text that was already present
|
||||
// before composition started.
|
||||
/*
|
||||
* Keep support for non-composition characters typed immediately after composition end
|
||||
* while avoiding re-sending the trailing text that was already present
|
||||
* before composition started.
|
||||
*/
|
||||
const value = this._textarea.value;
|
||||
const valueEnd = currentCompositionSuffix.length > 0 && value.endsWith(currentCompositionSuffix)
|
||||
? value.length - currentCompositionSuffix.length
|
||||
@@ -260,14 +280,18 @@ export class CompositionHelper {
|
||||
this._compositionView.style.lineHeight = cellHeight + 'px';
|
||||
this._compositionView.style.fontFamily = this._optionsService.rawOptions.fontFamily;
|
||||
this._compositionView.style.fontSize = this._optionsService.rawOptions.fontSize + 'px';
|
||||
// Limit the composition view width to the space between the cursor and
|
||||
// the terminal's right edge, preventing it from overflowing the terminal.
|
||||
/*
|
||||
* Limit the composition view width to the space between the cursor and
|
||||
* the terminal's right edge, preventing it from overflowing the terminal.
|
||||
*/
|
||||
const maxWidth = this._bufferService.cols * this._renderService.dimensions.css.cell.width - cursorLeft;
|
||||
this._compositionView.style.maxWidth = maxWidth + 'px';
|
||||
this._compositionView.style.overflow = 'hidden';
|
||||
this._compositionView.style.direction = 'rtl';
|
||||
// Sync the textarea to the exact position of the composition view so the IME knows where the
|
||||
// text is.
|
||||
/*
|
||||
* Sync the textarea to the exact position of the composition view so the IME knows where the
|
||||
* text is.
|
||||
*/
|
||||
const compositionViewBounds = this._compositionView.getBoundingClientRect();
|
||||
this._textarea.style.left = cursorLeft + 'px';
|
||||
this._textarea.style.top = cursorTop + 'px';
|
||||
|
||||
@@ -44,9 +44,11 @@ export function getCoords(window: Pick<Window, 'getComputedStyle'>, event: Pick<
|
||||
coords[0] = Math.ceil((coords[0] + (isSelection ? cssCellWidth / 2 : 0)) / cssCellWidth);
|
||||
coords[1] = Math.ceil(coords[1] / cssCellHeight);
|
||||
|
||||
// Ensure coordinates are within the terminal viewport. Note that selections
|
||||
// need an addition point of precision to cover the end point (as characters
|
||||
// cover half of one char and half of the next).
|
||||
/*
|
||||
* Ensure coordinates are within the terminal viewport. Note that selections
|
||||
* need an addition point of precision to cover the end point (as characters
|
||||
* cover half of one char and half of the next).
|
||||
*/
|
||||
coords[0] = Math.min(Math.max(coords[0], 1), colCount + (isSelection ? 1 : 0));
|
||||
coords[1] = Math.min(Math.max(coords[1], 1), rowCount);
|
||||
|
||||
|
||||
@@ -54,9 +54,11 @@ export class Terminal extends Disposable implements ITerminalApi {
|
||||
}
|
||||
|
||||
private _checkReadonlyOptions(propName: string): void {
|
||||
// Throw an error if any constructor only option is modified
|
||||
// from terminal.options
|
||||
// Modifications from anywhere else are allowed
|
||||
/*
|
||||
* Throw an error if any constructor only option is modified
|
||||
* from terminal.options
|
||||
* Modifications from anywhere else are allowed
|
||||
*/
|
||||
if (CONSTRUCTOR_ONLY_OPTIONS.includes(propName)) {
|
||||
throw new Error(`Option "${propName}" can only be set in the constructor`);
|
||||
}
|
||||
|
||||
@@ -114,8 +114,10 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
this._register(toDisposable(() => {
|
||||
this._element.classList.remove(Constants.TERMINAL_CLASS_PREFIX + this._terminalClass);
|
||||
|
||||
// Outside influences such as React unmounts may manipulate the DOM before our disposal.
|
||||
// https://github.com/xtermjs/xterm.js/issues/2960
|
||||
/*
|
||||
* Outside influences such as React unmounts may manipulate the DOM before our disposal.
|
||||
* https://github.com/xtermjs/xterm.js/issues/2960
|
||||
*/
|
||||
this._rowContainer.remove();
|
||||
this._selectionContainer.remove();
|
||||
this._widthCache.dispose();
|
||||
@@ -184,9 +186,11 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
// Base CSS
|
||||
let styles =
|
||||
`${this._terminalSelector} .${Constants.ROW_CONTAINER_CLASS} {` +
|
||||
// Disabling pointer events circumvents a browser behavior that prevents `click` events from
|
||||
// being delivered if the target element is replaced during the click. This happened due to
|
||||
// refresh() being called during the mousedown handler to start a selection.
|
||||
/*
|
||||
* Disabling pointer events circumvents a browser behavior that prevents `click` events from
|
||||
* being delivered if the target element is replaced during the click. This happened due to
|
||||
* refresh() being called during the mousedown handler to start a selection.
|
||||
*/
|
||||
` pointer-events: none;` +
|
||||
` color: ${colors.foreground.css};` +
|
||||
`}`;
|
||||
@@ -257,9 +261,11 @@ export class DomRenderer extends Disposable implements IRenderer {
|
||||
`${this._terminalSelector} .${Constants.ROW_CONTAINER_CLASS}.${Constants.CURSOR_BLINK_IDLE_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS} {` +
|
||||
` animation: none !important;` +
|
||||
`}` +
|
||||
// !important helps fix an issue where the cursor will not render on top of the selection,
|
||||
// however it's very hard to fix this issue and retain the blink animation without the use of
|
||||
// !important. So this edge case fails when cursor blink is on.
|
||||
/*
|
||||
* !important helps fix an issue where the cursor will not render on top of the selection,
|
||||
* however it's very hard to fix this issue and retain the blink animation without the use of
|
||||
* !important. So this edge case fails when cursor blink is on.
|
||||
*/
|
||||
`${this._terminalSelector} .${Constants.ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS} {` +
|
||||
` background-color: ${colors.cursor.css};` +
|
||||
` color: ${colors.cursorAccent.css};` +
|
||||
|
||||
@@ -359,8 +359,10 @@ describe('DomRendererRowFactory', () => {
|
||||
});
|
||||
|
||||
describe('createRow with merged spans', () => {
|
||||
// for test purpose assume all in codepoints 0..255 are merging
|
||||
// const ALL_MERGING = new Uint8Array(FontMetrics.MAX);
|
||||
/*
|
||||
* for test purpose assume all in codepoints 0..255 are merging
|
||||
* const ALL_MERGING = new Uint8Array(FontMetrics.MAX);
|
||||
*/
|
||||
|
||||
beforeEach(() => {
|
||||
lineData = createEmptyLineData(10);
|
||||
|
||||
@@ -115,20 +115,26 @@ export class DomRendererRowFactory {
|
||||
// If true, indicates that the current character(s) to draw were joined.
|
||||
let isJoined = false;
|
||||
|
||||
// Indicates whether this cell is part of a joined range that should be ignored as it cannot
|
||||
// be rendered entirely, like the selection state differs across the range.
|
||||
/*
|
||||
* Indicates whether this cell is part of a joined range that should be ignored as it cannot
|
||||
* be rendered entirely, like the selection state differs across the range.
|
||||
*/
|
||||
let isValidJoinRange = (x >= skipJoinedCheckUntilX);
|
||||
|
||||
let lastCharX = x;
|
||||
|
||||
// Process any joined character ranges as needed. Because of how the
|
||||
// ranges are produced, we know that they are valid for the characters
|
||||
// and attributes of our input.
|
||||
/*
|
||||
* Process any joined character ranges as needed. Because of how the
|
||||
* ranges are produced, we know that they are valid for the characters
|
||||
* and attributes of our input.
|
||||
*/
|
||||
let cell: ICellData = this._workCell;
|
||||
if (joinedRanges.length > 0 && x === joinedRanges[0][0] && isValidJoinRange) {
|
||||
const range = joinedRanges.shift()!;
|
||||
// If the ligature's selection state is not consistent, don't join it. This helps the
|
||||
// selection render correctly regardless whether they should be joined.
|
||||
/*
|
||||
* If the ligature's selection state is not consistent, don't join it. This helps the
|
||||
* selection render correctly regardless whether they should be joined.
|
||||
*/
|
||||
const firstSelectionState = this._isCellInSelection(range[0], row);
|
||||
for (i = range[0] + 1; i < range[1]; i++) {
|
||||
isValidJoinRange &&= (firstSelectionState === this._isCellInSelection(i, row));
|
||||
@@ -140,8 +146,10 @@ export class DomRendererRowFactory {
|
||||
} else {
|
||||
isJoined = true;
|
||||
|
||||
// We already know the exact start and end column of the joined range,
|
||||
// so we get the string and width representing it directly
|
||||
/*
|
||||
* We already know the exact start and end column of the joined range,
|
||||
* so we get the string and width representing it directly
|
||||
*/
|
||||
cell = new JoinedCellData(
|
||||
this._workCell,
|
||||
lineData.translateToString(true, range[0], range[1]),
|
||||
@@ -243,9 +251,11 @@ export class DomRendererRowFactory {
|
||||
oldIsInSelection = isInSelection;
|
||||
|
||||
if (isJoined) {
|
||||
// The DOM renderer colors the background of the cursor but for ligatures all cells are
|
||||
// joined. The workaround here is to show a cursor around the whole ligature so it shows up,
|
||||
// the cursor looks the same when on any character of the ligature though
|
||||
/*
|
||||
* The DOM renderer colors the background of the cursor but for ligatures all cells are
|
||||
* joined. The workaround here is to show a cursor around the whole ligature so it shows up,
|
||||
* the cursor looks the same when on any character of the ligature though
|
||||
*/
|
||||
if (cursorX >= x && cursorX <= lastCharX) {
|
||||
cursorX = x;
|
||||
}
|
||||
@@ -333,8 +343,10 @@ export class DomRendererRowFactory {
|
||||
classes.push(RowCss.STRIKETHROUGH_CLASS);
|
||||
}
|
||||
|
||||
// apply link hover underline late, effectively overrides any previous text-decoration
|
||||
// settings
|
||||
/*
|
||||
* apply link hover underline late, effectively overrides any previous text-decoration
|
||||
* settings
|
||||
*/
|
||||
if (isLinkHover) {
|
||||
charElement.style.textDecoration = 'underline';
|
||||
}
|
||||
@@ -353,8 +365,10 @@ export class DomRendererRowFactory {
|
||||
bgColorMode = temp2;
|
||||
}
|
||||
|
||||
// Apply any decoration foreground/background overrides, this must happen after inverse has
|
||||
// been applied
|
||||
/*
|
||||
* Apply any decoration foreground/background overrides, this must happen after inverse has
|
||||
* been applied
|
||||
*/
|
||||
let bgOverride: IColor | undefined;
|
||||
let fgOverride: IColor | undefined;
|
||||
let isTop = false;
|
||||
@@ -377,15 +391,19 @@ export class DomRendererRowFactory {
|
||||
|
||||
// Apply selection
|
||||
if (!isTop && isInSelection) {
|
||||
// If in the selection, force the element to be above the selection to improve contrast and
|
||||
// support opaque selections. The applies background is not actually needed here as
|
||||
// selection is drawn in a seperate container, the main purpose of this to ensuring minimum
|
||||
// contrast ratio
|
||||
/*
|
||||
* If in the selection, force the element to be above the selection to improve contrast and
|
||||
* support opaque selections. The applies background is not actually needed here as
|
||||
* selection is drawn in a seperate container, the main purpose of this to ensuring minimum
|
||||
* contrast ratio
|
||||
*/
|
||||
bgOverride = this._coreBrowserService.isFocused ? colors.selectionBackgroundOpaque : colors.selectionInactiveBackgroundOpaque;
|
||||
bg = bgOverride.rgba >> 8 & 0xFFFFFF;
|
||||
bgColorMode = Attributes.CM_RGB;
|
||||
// Since an opaque selection is being rendered, the selection pretends to be a decoration to
|
||||
// ensure text is drawn above the selection.
|
||||
/*
|
||||
* Since an opaque selection is being rendered, the selection pretends to be a decoration to
|
||||
* ensure text is drawn above the selection.
|
||||
*/
|
||||
isTop = true;
|
||||
// Apply selection foreground if applicable
|
||||
if (colors.selectionForeground) {
|
||||
@@ -459,9 +477,11 @@ export class DomRendererRowFactory {
|
||||
}
|
||||
}
|
||||
|
||||
// apply CSS classes
|
||||
// slightly faster than using classList by omitting
|
||||
// checks for doubled entries (code above should not have doublets)
|
||||
/*
|
||||
* apply CSS classes
|
||||
* slightly faster than using classList by omitting
|
||||
* checks for doubled entries (code above should not have doublets)
|
||||
*/
|
||||
if (classes.length) {
|
||||
charElement.className = classes.join(' ');
|
||||
classes.length = 0;
|
||||
@@ -504,8 +524,10 @@ export class DomRendererRowFactory {
|
||||
|
||||
// Calculate and store in cache
|
||||
if (adjustedColor === undefined) {
|
||||
// Dim cells only require half the contrast, otherwise they wouldn't be distinguishable from
|
||||
// non-dim cells
|
||||
/*
|
||||
* Dim cells only require half the contrast, otherwise they wouldn't be distinguishable from
|
||||
* non-dim cells
|
||||
*/
|
||||
const ratio = this._optionsService.rawOptions.minimumContrastRatio / (cell.isDim() ? 2 : 1);
|
||||
adjustedColor = color.ensureContrastRatio(bgOverride ?? bg, fgOverride ?? fg, ratio);
|
||||
cache.setColor((bgOverride ?? bg).rgba, (fgOverride ?? fg).rgba, adjustedColor ?? null);
|
||||
|
||||
@@ -31,16 +31,20 @@ export interface IWidthCacheFontVariantCanvas {
|
||||
}
|
||||
|
||||
export class WidthCache implements IDisposable {
|
||||
// flat cache for regular variant up to CacheSettings.FLAT_SIZE
|
||||
// NOTE: ~4x faster access than holey (serving >>80% of terminal content)
|
||||
// It has a small memory footprint (only 1MB for full BMP caching),
|
||||
// still the sweet spot is not reached before touching 32k different codepoints,
|
||||
// thus we store the remaining <<20% of terminal data in a holey structure.
|
||||
/*
|
||||
* flat cache for regular variant up to CacheSettings.FLAT_SIZE
|
||||
* NOTE: ~4x faster access than holey (serving >>80% of terminal content)
|
||||
* It has a small memory footprint (only 1MB for full BMP caching),
|
||||
* still the sweet spot is not reached before touching 32k different codepoints,
|
||||
* thus we store the remaining <<20% of terminal data in a holey structure.
|
||||
*/
|
||||
protected _flat = new Float32Array(WidthCacheSettings.FLAT_SIZE);
|
||||
|
||||
// holey cache for bold, italic and bold&italic for any string
|
||||
// FIXME: can grow really big over time (~8.5 MB for full BMP caching),
|
||||
// so a shared API across terminals is needed
|
||||
/*
|
||||
* holey cache for bold, italic and bold&italic for any string
|
||||
* FIXME: can grow really big over time (~8.5 MB for full BMP caching),
|
||||
* so a shared API across terminals is needed
|
||||
*/
|
||||
protected _holey: Map<string, number> | undefined;
|
||||
|
||||
private _font = '';
|
||||
|
||||
@@ -13,8 +13,10 @@ describe('RendererUtils', () => {
|
||||
let line = 1;
|
||||
let variantOffset = 0;
|
||||
|
||||
// should line 1
|
||||
// =,_,=_,=_,
|
||||
/*
|
||||
* should line 1
|
||||
* =,_,=_,=_,
|
||||
*/
|
||||
let cells = [cellWidth, cellWidth, doubleCellWidth, doubleCellWidth];
|
||||
let result = [1, 0, 0, 0];
|
||||
for (let index = 0; index < cells.length; index++) {
|
||||
@@ -23,8 +25,10 @@ describe('RendererUtils', () => {
|
||||
assert.equal(variantOffset, result[index]);
|
||||
}
|
||||
|
||||
// should line 2
|
||||
// ==__==__==_,_==__==__==,__==__==__==__==__==__,==__==__==__==__==__==,
|
||||
/*
|
||||
* should line 2
|
||||
* ==__==__==_,_==__==__==,__==__==__==__==__==__,==__==__==__==__==__==,
|
||||
*/
|
||||
line = 2;
|
||||
variantOffset = 0;
|
||||
cells = [cellWidth, cellWidth, doubleCellWidth, doubleCellWidth];
|
||||
@@ -35,8 +39,10 @@ describe('RendererUtils', () => {
|
||||
assert.equal(variantOffset, result[index]);
|
||||
}
|
||||
|
||||
// should line 3
|
||||
// ===___===__,_===___===_,__===___===___===___==,=___===___===___===___,
|
||||
/*
|
||||
* should line 3
|
||||
* ===___===__,_===___===_,__===___===___===___==,=___===___===___===___,
|
||||
*/
|
||||
line = 3;
|
||||
variantOffset = 0;
|
||||
cells = [cellWidth, cellWidth, doubleCellWidth, doubleCellWidth];
|
||||
|
||||
@@ -13,9 +13,11 @@ export function throwIfFalsy<T>(value: T | undefined | null): T {
|
||||
}
|
||||
|
||||
export function isPowerlineGlyph(codepoint: number): boolean {
|
||||
// Only return true for Powerline symbols which require
|
||||
// different padding and should be excluded from minimum contrast
|
||||
// ratio standards
|
||||
/*
|
||||
* Only return true for Powerline symbols which require
|
||||
* different padding and should be excluded from minimum contrast
|
||||
* ratio standards
|
||||
*/
|
||||
return 0xE0A4 <= codepoint && codepoint <= 0xE0D6;
|
||||
}
|
||||
|
||||
@@ -48,8 +50,10 @@ export function allowRescaling(codepoint: number | undefined, width: number, gly
|
||||
return (
|
||||
// Is single cell width
|
||||
width === 1 &&
|
||||
// Glyph exceeds cell bounds, add 50% to avoid hurting readability by rescaling glyphs that
|
||||
// barely overlap
|
||||
/*
|
||||
* Glyph exceeds cell bounds, add 50% to avoid hurting readability by rescaling glyphs that
|
||||
* barely overlap
|
||||
*/
|
||||
glyphSizeX > Math.ceil(deviceCellWidth * 1.5) &&
|
||||
// Never rescale ascii
|
||||
codepoint !== undefined && codepoint > 0xFF &&
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
/*
|
||||
*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
*--------------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import * as dom from '../Dom';
|
||||
import { FastDomNode } from './fastDomNode';
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
/*
|
||||
*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
*--------------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
export class FastDomNode<T extends HTMLElement> {
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
/*
|
||||
*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
*--------------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import * as dom from '../Dom';
|
||||
import { DisposableStore, IDisposable, toDisposable } from 'common/Lifecycle';
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
/*
|
||||
*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
*--------------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import { AbstractScrollbar, ISimplifiedPointerEvent, IScrollbarHost } from './abstractScrollbar';
|
||||
import { IScrollableElementResolvedOptions } from './scrollableElementOptions';
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
/*
|
||||
*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
*--------------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import * as platform from 'common/Platform';
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
/*
|
||||
*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
*--------------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import { Emitter, IEvent } from 'common/Event';
|
||||
import { Disposable, IDisposable } from 'common/Lifecycle';
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
/*
|
||||
*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
*--------------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import * as dom from '../Dom';
|
||||
import { FastDomNode } from './fastDomNode';
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
/*
|
||||
*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
*--------------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import { ScrollbarVisibility } from './scrollable';
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
/*
|
||||
*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
*--------------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import { GlobalPointerMoveMonitor } from './globalPointerMoveMonitor';
|
||||
import { Widget } from './widget';
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
/*
|
||||
*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
*--------------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/**
|
||||
* The minimal size of the slider (such that it can still be clickable).
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
/*
|
||||
*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
*--------------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import { FastDomNode } from './fastDomNode';
|
||||
import { TimeoutTimer } from 'common/Async';
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
/*
|
||||
*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
*--------------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import * as DomUtils from '../Dom';
|
||||
import { Disposable, IDisposable, toDisposable } from 'common/Lifecycle';
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
/*
|
||||
*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
*--------------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import { AbstractScrollbar, ISimplifiedPointerEvent, IScrollbarHost } from './abstractScrollbar';
|
||||
import { IScrollableElementResolvedOptions } from './scrollableElementOptions';
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
/*
|
||||
*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
*--------------------------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
import * as dom from '../Dom';
|
||||
import { IMouseEvent, StandardMouseEvent } from './mouseEvent';
|
||||
|
||||
@@ -61,8 +61,10 @@ abstract class BaseMeasureStategy extends Disposable implements IMeasureStrategy
|
||||
protected _result: IMeasureResult = { width: 0, height: 0 };
|
||||
|
||||
protected _validateAndSet(width: number | undefined, height: number | undefined): void {
|
||||
// If values are 0 then the element is likely currently display:none, in which case we should
|
||||
// retain the previous value.
|
||||
/*
|
||||
* If values are 0 then the element is likely currently display:none, in which case we should
|
||||
* retain the previous value.
|
||||
*/
|
||||
if (width !== undefined && width > 0 && height !== undefined && height > 0) {
|
||||
this._result.width = width;
|
||||
this._result.height = height;
|
||||
|
||||
@@ -13,8 +13,10 @@ import { ICharacterJoinerService } from 'browser/services/Services';
|
||||
|
||||
export class JoinedCellData extends AttributeData implements ICellData {
|
||||
private _width: number;
|
||||
// .content carries no meaning for joined CellData, simply nullify it
|
||||
// thus we have to overload all other .content accessors
|
||||
/*
|
||||
* .content carries no meaning for joined CellData, simply nullify it
|
||||
* thus we have to overload all other .content accessors
|
||||
*/
|
||||
public content: number = 0;
|
||||
public fg: number;
|
||||
public bg: number;
|
||||
@@ -42,8 +44,10 @@ export class JoinedCellData extends AttributeData implements ICellData {
|
||||
}
|
||||
|
||||
public getCode(): number {
|
||||
// code always gets the highest possible fake codepoint (read as -1)
|
||||
// this is needed as code is used by caches as identifier
|
||||
/*
|
||||
* code always gets the highest possible fake codepoint (read as -1)
|
||||
* this is needed as code is used by caches as identifier
|
||||
*/
|
||||
return 0x1FFFFF;
|
||||
}
|
||||
|
||||
@@ -101,10 +105,12 @@ export class CharacterJoinerService implements ICharacterJoinerService {
|
||||
const ranges: [number, number][] = [];
|
||||
const lineStr = line.translateToString(true);
|
||||
|
||||
// Because some cells can be represented by multiple javascript characters,
|
||||
// we track the cell and the string indexes separately. This allows us to
|
||||
// translate the string ranges we get from the joiners back into cell ranges
|
||||
// for use when rendering
|
||||
/*
|
||||
* Because some cells can be represented by multiple javascript characters,
|
||||
* we track the cell and the string indexes separately. This allows us to
|
||||
* translate the string ranges we get from the joiners back into cell ranges
|
||||
* for use when rendering
|
||||
*/
|
||||
let rangeStartColumn = 0;
|
||||
let currentStringIndex = 0;
|
||||
let rangeStartStringIndex = 0;
|
||||
@@ -121,8 +127,10 @@ export class CharacterJoinerService implements ICharacterJoinerService {
|
||||
|
||||
// End of range
|
||||
if (this._workCell.fg !== rangeAttrFG || this._workCell.bg !== rangeAttrBG) {
|
||||
// If we ended up with a sequence of more than one character,
|
||||
// look for ranges to join.
|
||||
/*
|
||||
* If we ended up with a sequence of more than one character,
|
||||
* look for ranges to join.
|
||||
*/
|
||||
if (x - rangeStartColumn > 1) {
|
||||
const joinedRanges = this._getJoinedRanges(
|
||||
lineStr,
|
||||
@@ -173,9 +181,11 @@ export class CharacterJoinerService implements ICharacterJoinerService {
|
||||
*/
|
||||
private _getJoinedRanges(line: string, startIndex: number, endIndex: number, lineData: IBufferLine, startCol: number): [number, number][] {
|
||||
const text = line.substring(startIndex, endIndex);
|
||||
// At this point we already know that there is at least one joiner so
|
||||
// we can just pull its value and assign it directly rather than
|
||||
// merging it into an empty array, which incurs unnecessary writes.
|
||||
/*
|
||||
* At this point we already know that there is at least one joiner so
|
||||
* we can just pull its value and assign it directly rather than
|
||||
* merging it into an empty array, which incurs unnecessary writes.
|
||||
*/
|
||||
let allJoinedRanges: [number, number][] = [];
|
||||
try {
|
||||
allJoinedRanges = this._characterJoiners[0].handler(text);
|
||||
@@ -220,8 +230,10 @@ export class CharacterJoinerService implements ICharacterJoinerService {
|
||||
const width = line.getWidth(x);
|
||||
const length = line.getString(x).length || WHITESPACE_CELL_CHAR.length;
|
||||
|
||||
// We skip zero-width characters when creating the string to join the text
|
||||
// so we do the same here
|
||||
/*
|
||||
* We skip zero-width characters when creating the string to join the text
|
||||
* so we do the same here
|
||||
*/
|
||||
if (width === 0) {
|
||||
continue;
|
||||
}
|
||||
@@ -244,10 +256,12 @@ export class CharacterJoinerService implements ICharacterJoinerService {
|
||||
break;
|
||||
}
|
||||
|
||||
// Ranges can be on adjacent characters. Because the end index of the
|
||||
// ranges are exclusive, this means that the index for the start of a
|
||||
// range can be the same as the end index of the previous range. To
|
||||
// account for the start of the next range, we check here just in case.
|
||||
/*
|
||||
* Ranges can be on adjacent characters. Because the end index of the
|
||||
* ranges are exclusive, this means that the index for the start of a
|
||||
* range can be the same as the end index of the previous range. To
|
||||
* account for the start of the next range, we check here just in case.
|
||||
*/
|
||||
if (currentRange[0] <= currentStringIndex) {
|
||||
currentRange[0] = x;
|
||||
currentRangeStarted = true;
|
||||
@@ -256,13 +270,17 @@ export class CharacterJoinerService implements ICharacterJoinerService {
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust the string index based on the character length to line up with
|
||||
// the column adjustment
|
||||
/*
|
||||
* Adjust the string index based on the character length to line up with
|
||||
* the column adjustment
|
||||
*/
|
||||
currentStringIndex += length;
|
||||
}
|
||||
|
||||
// If there is still a range left at the end, it must extend all the way to
|
||||
// the end of the line.
|
||||
/*
|
||||
* If there is still a range left at the end, it must extend all the way to
|
||||
* the end of the line.
|
||||
*/
|
||||
if (currentRange) {
|
||||
currentRange[1] = this._bufferService.cols;
|
||||
}
|
||||
@@ -288,15 +306,19 @@ export class CharacterJoinerService implements ICharacterJoinerService {
|
||||
}
|
||||
|
||||
if (newRange[1] <= range[1]) {
|
||||
// Case 2: New range is either wholly contained within the
|
||||
// search range or overlaps with the front of it
|
||||
/*
|
||||
* Case 2: New range is either wholly contained within the
|
||||
* search range or overlaps with the front of it
|
||||
*/
|
||||
range[0] = Math.min(newRange[0], range[0]);
|
||||
return ranges;
|
||||
}
|
||||
|
||||
if (newRange[0] < range[1]) {
|
||||
// Case 3: New range either wholly contains the search range
|
||||
// or overlaps with the end of it
|
||||
/*
|
||||
* Case 3: New range either wholly contains the search range
|
||||
* or overlaps with the end of it
|
||||
*/
|
||||
range[0] = Math.min(newRange[0], range[0]);
|
||||
inRange = true;
|
||||
}
|
||||
@@ -305,22 +327,28 @@ export class CharacterJoinerService implements ICharacterJoinerService {
|
||||
continue;
|
||||
} else {
|
||||
if (newRange[1] <= range[0]) {
|
||||
// Case 5: New range extends from previous range but doesn't
|
||||
// reach the current one
|
||||
/*
|
||||
* Case 5: New range extends from previous range but doesn't
|
||||
* reach the current one
|
||||
*/
|
||||
ranges[i - 1][1] = newRange[1];
|
||||
return ranges;
|
||||
}
|
||||
|
||||
if (newRange[1] <= range[1]) {
|
||||
// Case 6: New range extends from prvious range into the
|
||||
// current range
|
||||
/*
|
||||
* Case 6: New range extends from prvious range into the
|
||||
* current range
|
||||
*/
|
||||
ranges[i - 1][1] = Math.max(newRange[1], range[1]);
|
||||
ranges.splice(i, 1);
|
||||
return ranges;
|
||||
}
|
||||
|
||||
// Case 7: New range extends from previous range past the
|
||||
// end of the current range
|
||||
/*
|
||||
* Case 7: New range extends from previous range past the
|
||||
* end of the current range
|
||||
*/
|
||||
ranges.splice(i, 1);
|
||||
i--;
|
||||
}
|
||||
|
||||
@@ -127,8 +127,10 @@ describe('MouseService _triggerMouseEvent', () => {
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.RIGHT, action: CoreMouseAction.DOWN }), true);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.UP }), true);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.MOVE }), true);
|
||||
// should not report in any case
|
||||
// invalid button + action combinations
|
||||
/*
|
||||
* should not report in any case
|
||||
* invalid button + action combinations
|
||||
*/
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.WHEEL, action: CoreMouseAction.MOVE }), false);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.DOWN }), false);
|
||||
assert.equal(trigger({ col: 0, row: 0, x: 0, y: 0, button: CoreMouseButton.NONE, action: CoreMouseAction.UP }), false);
|
||||
|
||||
@@ -147,8 +147,10 @@ export class MouseService implements IMouseService {
|
||||
return false;
|
||||
}
|
||||
|
||||
// exit if we cannot determine valid button/action values
|
||||
// do nothing for higher buttons than wheel
|
||||
/*
|
||||
* exit if we cannot determine valid button/action values
|
||||
* do nothing for higher buttons than wheel
|
||||
*/
|
||||
if (action === undefined || but === undefined || but > CoreMouseButton.WHEEL) {
|
||||
return false;
|
||||
}
|
||||
@@ -204,19 +206,23 @@ export class MouseService implements IMouseService {
|
||||
ev.preventDefault();
|
||||
ctx.focus();
|
||||
|
||||
// Don't send the mouse button to the pty if mouse events are disabled or
|
||||
// if the selection manager is having selection forced (ie. a modifier is
|
||||
// held).
|
||||
/*
|
||||
* Don't send the mouse button to the pty if mouse events are disabled or
|
||||
* if the selection manager is having selection forced (ie. a modifier is
|
||||
* held).
|
||||
*/
|
||||
if (!this._mouseStateService.areMouseEventsActive || this._selectionService.shouldForceSelection(ev)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._sendEvent(ctx, ev);
|
||||
|
||||
// Register additional global handlers which should keep reporting outside
|
||||
// of the terminal element.
|
||||
// Note: Other emulators also do this for 'mousedown' while a button
|
||||
// is held, we currently limit 'mousedown' to the terminal only.
|
||||
/*
|
||||
* Register additional global handlers which should keep reporting outside
|
||||
* of the terminal element.
|
||||
* Note: Other emulators also do this for 'mousedown' while a button
|
||||
* is held, we currently limit 'mousedown' to the terminal only.
|
||||
*/
|
||||
if (ctx.requestedEvents.mouseup) {
|
||||
ctx.target.document.addEventListener('mouseup', ctx.requestedEvents.mouseup);
|
||||
}
|
||||
@@ -236,12 +242,14 @@ export class MouseService implements IMouseService {
|
||||
}
|
||||
|
||||
if (!this._bufferService.buffer.hasScrollback) {
|
||||
// Convert wheel events into up/down events when the buffer does not have scrollback, this
|
||||
// enables scrolling in apps hosted in the alt buffer such as vim or tmux even when mouse
|
||||
// events are not enabled.
|
||||
// This used implementation used get the actual lines/partial lines scrolled from the
|
||||
// viewport but since moving to the new viewport implementation has been simplified to
|
||||
// simply send a single up or down sequence.
|
||||
/*
|
||||
* Convert wheel events into up/down events when the buffer does not have scrollback, this
|
||||
* enables scrolling in apps hosted in the alt buffer such as vim or tmux even when mouse
|
||||
* events are not enabled.
|
||||
* This used implementation used get the actual lines/partial lines scrolled from the
|
||||
* viewport but since moving to the new viewport implementation has been simplified to
|
||||
* simply send a single up or down sequence.
|
||||
*/
|
||||
|
||||
// Do nothing if there's no vertical scroll
|
||||
const deltaY = ev.deltaY;
|
||||
|
||||
@@ -88,9 +88,11 @@ export class RenderService extends Disposable implements IRenderService {
|
||||
this._register(this._optionsService.onOptionChange(() => this._handleOptionsChanged()));
|
||||
this._register(this._charSizeService.onCharSizeChange(() => this.handleCharSizeChanged()));
|
||||
|
||||
// Do a full refresh whenever any decoration is added or removed. This may not actually result
|
||||
// in changes but since decorations should be used sparingly or added/removed all in the same
|
||||
// frame this should have minimal performance impact.
|
||||
/*
|
||||
* Do a full refresh whenever any decoration is added or removed. This may not actually result
|
||||
* in changes but since decorations should be used sparingly or added/removed all in the same
|
||||
* frame this should have minimal performance impact.
|
||||
*/
|
||||
this._register(decorationService.onDecorationRegistered(() => this._fullRefresh()));
|
||||
this._register(decorationService.onDecorationRemoved(() => this._fullRefresh()));
|
||||
|
||||
@@ -124,8 +126,10 @@ export class RenderService extends Disposable implements IRenderService {
|
||||
}
|
||||
|
||||
private _registerIntersectionObserver(w: Window & typeof globalThis, screenElement: HTMLElement): void {
|
||||
// Detect whether IntersectionObserver is detected and enable renderer pause
|
||||
// and resume based on terminal visibility if so
|
||||
/*
|
||||
* Detect whether IntersectionObserver is detected and enable renderer pause
|
||||
* and resume based on terminal visibility if so
|
||||
*/
|
||||
if ('IntersectionObserver' in w) {
|
||||
const observer = new w.IntersectionObserver(e => this._handleIntersectionChange(e[e.length - 1]), { threshold: 0 });
|
||||
this._observerDisposable.value = toDisposable(() => {
|
||||
@@ -186,16 +190,20 @@ export class RenderService extends Disposable implements IRenderService {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip rendering if synchronized output mode is enabled. This check must happen here
|
||||
// (in addition to refreshRows) to handle renders that were queued before the mode was enabled.
|
||||
/*
|
||||
* Skip rendering if synchronized output mode is enabled. This check must happen here
|
||||
* (in addition to refreshRows) to handle renders that were queued before the mode was enabled.
|
||||
*/
|
||||
if (this._coreService.decPrivateModes.synchronizedOutput) {
|
||||
this._syncOutputHandler.bufferRows(start, end);
|
||||
return;
|
||||
}
|
||||
|
||||
// Since this is debounced, a resize event could have happened between the time a refresh was
|
||||
// requested and when this triggers. Clamp the values of start and end to ensure they're valid
|
||||
// given the current viewport state.
|
||||
/*
|
||||
* Since this is debounced, a resize event could have happened between the time a refresh was
|
||||
* requested and when this triggers. Clamp the values of start and end to ensure they're valid
|
||||
* given the current viewport state.
|
||||
*/
|
||||
start = Math.min(start, this._rowCount - 1);
|
||||
end = Math.min(end, this._rowCount - 1);
|
||||
|
||||
@@ -277,8 +285,10 @@ export class RenderService extends Disposable implements IRenderService {
|
||||
}
|
||||
|
||||
public handleDevicePixelRatioChange(): void {
|
||||
// Force char size measurement as DomMeasureStrategy(getBoundingClientRect) is not stable
|
||||
// when devicePixelRatio changes
|
||||
/*
|
||||
* Force char size measurement as DomMeasureStrategy(getBoundingClientRect) is not stable
|
||||
* when devicePixelRatio changes
|
||||
*/
|
||||
this._charSizeService.measure();
|
||||
|
||||
if (!this._renderer.value) {
|
||||
|
||||
@@ -257,8 +257,10 @@ describe('SelectionService', () => {
|
||||
])); // The a is here to prevent the space being trimmed in selectionText
|
||||
selectionService.selectWordAt([0, 0]);
|
||||
assert.equal(selectionService.selectionText, ' ');
|
||||
// ZWJ emojis do not combine in the terminal so the family emoji used here consumed 4 cells
|
||||
// The selection text should retain ZWJ chars despite not combining on the terminal
|
||||
/*
|
||||
* ZWJ emojis do not combine in the terminal so the family emoji used here consumed 4 cells
|
||||
* The selection text should retain ZWJ chars despite not combining on the terminal
|
||||
*/
|
||||
selectionService.selectWordAt([1, 0]);
|
||||
assert.equal(selectionService.selectionText, '👨👩👧👦');
|
||||
selectionService.selectWordAt([2, 0]);
|
||||
@@ -473,8 +475,10 @@ describe('SelectionService', () => {
|
||||
});
|
||||
|
||||
it('should select a column of text with double character emojis', () => {
|
||||
// TODO the case this is testing works for me in the demo webapp,
|
||||
// but doing it programmatically fails.
|
||||
/*
|
||||
* TODO the case this is testing works for me in the demo webapp,
|
||||
* but doing it programmatically fails.
|
||||
*/
|
||||
buffer.lines.length = 3;
|
||||
buffer.lines.set(0, stringToRow('a '));
|
||||
buffer.lines.set(1, stringArrayToRow(['😁', ' ']));
|
||||
|
||||
@@ -152,8 +152,10 @@ export class SelectionService extends Disposable implements ISelectionService {
|
||||
this._removeMouseDownListeners();
|
||||
}));
|
||||
|
||||
// Clear selection when resizing vertically. This experience could be improved, this is the
|
||||
// simple option to fix the buggy behavior. https://github.com/xtermjs/xterm.js/issues/5300
|
||||
/*
|
||||
* Clear selection when resizing vertically. This experience could be improved, this is the
|
||||
* simple option to fix the buggy behavior. https://github.com/xtermjs/xterm.js/issues/5300
|
||||
*/
|
||||
this._register(this._bufferService.onResize(e => {
|
||||
if (e.rowsChanged) {
|
||||
this.clearSelection();
|
||||
@@ -215,8 +217,10 @@ export class SelectionService extends Disposable implements ISelectionService {
|
||||
return '';
|
||||
}
|
||||
|
||||
// For column selection it's not enough to rely on final selection's swapping of reversed
|
||||
// values, it also needs the x coordinates to swap independently of the y coordinate is needed
|
||||
/*
|
||||
* For column selection it's not enough to rely on final selection's swapping of reversed
|
||||
* values, it also needs the x coordinates to swap independently of the y coordinate is needed
|
||||
*/
|
||||
const startCol = start[0] < end[0] ? start[0] : end[0];
|
||||
const endCol = start[0] < end[0] ? end[0] : start[0];
|
||||
for (let i = start[1]; i <= end[1]; i++) {
|
||||
@@ -251,8 +255,10 @@ export class SelectionService extends Disposable implements ISelectionService {
|
||||
}
|
||||
}
|
||||
|
||||
// Format string by replacing non-breaking space chars with regular spaces
|
||||
// and joining the array into a multi-line string.
|
||||
/*
|
||||
* Format string by replacing non-breaking space chars with regular spaces
|
||||
* and joining the array into a multi-line string.
|
||||
*/
|
||||
const formattedResult = result.map(line => {
|
||||
return line.replace(ALL_NON_BREAKING_SPACE_REGEX, ' ');
|
||||
}).join(Browser.isWindows ? '\r\n' : '\n');
|
||||
@@ -281,8 +287,10 @@ export class SelectionService extends Disposable implements ISelectionService {
|
||||
this._refreshAnimationFrame = this._coreBrowserService.window.requestAnimationFrame(() => this._refresh());
|
||||
}
|
||||
|
||||
// If the platform is Linux and the refresh call comes from a mouse event,
|
||||
// we need to update the selection for middle click to paste selection.
|
||||
/*
|
||||
* If the platform is Linux and the refresh call comes from a mouse event,
|
||||
* we need to update the selection for middle click to paste selection.
|
||||
*/
|
||||
if (Browser.isLinux && isLinuxMouseSelection) {
|
||||
const selectionText = this.selectionText;
|
||||
if (selectionText.length) {
|
||||
@@ -447,8 +455,10 @@ export class SelectionService extends Disposable implements ISelectionService {
|
||||
*/
|
||||
public handleMouseDown(event: MouseEvent): void {
|
||||
this._mouseDownTimeStamp = event.timeStamp;
|
||||
// If we have selection, we want the context menu on right click even if the
|
||||
// terminal is in mouse mode.
|
||||
/*
|
||||
* If we have selection, we want the context menu on right click even if the
|
||||
* terminal is in mouse mode.
|
||||
*/
|
||||
if (event.button === 2 && this.hasSelection) {
|
||||
return;
|
||||
}
|
||||
@@ -561,8 +571,10 @@ export class SelectionService extends Disposable implements ISelectionService {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the mouse is over the second half of a wide character, adjust the
|
||||
// selection to cover the whole character
|
||||
/*
|
||||
* If the mouse is over the second half of a wide character, adjust the
|
||||
* selection to cover the whole character
|
||||
*/
|
||||
if (line.hasWidth(this._model.selectionStart[0]) === 0) {
|
||||
this._model.selectionStart[0]++;
|
||||
}
|
||||
@@ -605,19 +617,25 @@ export class SelectionService extends Disposable implements ISelectionService {
|
||||
* @param event The mousemove event.
|
||||
*/
|
||||
private _handleMouseMove(event: MouseEvent): void {
|
||||
// If the mousemove listener is active it means that a selection is
|
||||
// currently being made, we should stop propagation to prevent mouse events
|
||||
// to be sent to the pty.
|
||||
/*
|
||||
* If the mousemove listener is active it means that a selection is
|
||||
* currently being made, we should stop propagation to prevent mouse events
|
||||
* to be sent to the pty.
|
||||
*/
|
||||
event.stopImmediatePropagation();
|
||||
|
||||
// Do nothing if there is no selection start, this can happen if the first
|
||||
// click in the terminal is an incremental click
|
||||
/*
|
||||
* Do nothing if there is no selection start, this can happen if the first
|
||||
* click in the terminal is an incremental click
|
||||
*/
|
||||
if (!this._model.selectionStart) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Record the previous position so we know whether to redraw the selection
|
||||
// at the end.
|
||||
/*
|
||||
* Record the previous position so we know whether to redraw the selection
|
||||
* at the end.
|
||||
*/
|
||||
const previousSelectionEnd = this._model.selectionEnd ? [this._model.selectionEnd[0], this._model.selectionEnd[1]] : null;
|
||||
|
||||
// Set the initial selection end based on the mouse coordinates
|
||||
@@ -641,9 +659,11 @@ export class SelectionService extends Disposable implements ISelectionService {
|
||||
// Determine the amount of scrolling that will happen.
|
||||
this._dragScrollAmount = this._getMouseEventScrollAmount(event);
|
||||
|
||||
// If the cursor was above or below the viewport, make sure it's at the
|
||||
// start or end of the viewport respectively. This should only happen when
|
||||
// NOT in column select mode.
|
||||
/*
|
||||
* If the cursor was above or below the viewport, make sure it's at the
|
||||
* start or end of the viewport respectively. This should only happen when
|
||||
* NOT in column select mode.
|
||||
*/
|
||||
if (this._activeSelectionMode !== SelectionMode.COLUMN) {
|
||||
if (this._dragScrollAmount > 0) {
|
||||
this._model.selectionEnd[0] = this._bufferService.cols;
|
||||
@@ -652,9 +672,11 @@ export class SelectionService extends Disposable implements ISelectionService {
|
||||
}
|
||||
}
|
||||
|
||||
// If the character is a wide character include the cell to the right in the
|
||||
// selection. Note that selections at the very end of the line will never
|
||||
// have a character.
|
||||
/*
|
||||
* If the character is a wide character include the cell to the right in the
|
||||
* selection. Note that selections at the very end of the line will never
|
||||
* have a character.
|
||||
*/
|
||||
const buffer = this._bufferService.buffer;
|
||||
if (this._model.selectionEnd[1] < buffer.lines.length) {
|
||||
const line = buffer.lines.get(this._model.selectionEnd[1]);
|
||||
@@ -683,10 +705,12 @@ export class SelectionService extends Disposable implements ISelectionService {
|
||||
}
|
||||
if (this._dragScrollAmount) {
|
||||
this._onRequestScrollLines.fire({ amount: this._dragScrollAmount, suppressScrollEvent: false });
|
||||
// Re-evaluate selection
|
||||
// If the cursor was above or below the viewport, make sure it's at the
|
||||
// start or end of the viewport respectively. This should only happen when
|
||||
// NOT in column select mode.
|
||||
/*
|
||||
* Re-evaluate selection
|
||||
* If the cursor was above or below the viewport, make sure it's at the
|
||||
* start or end of the viewport respectively. This should only happen when
|
||||
* NOT in column select mode.
|
||||
*/
|
||||
const buffer = this._bufferService.buffer;
|
||||
if (this._dragScrollAmount > 0) {
|
||||
if (this._activeSelectionMode !== SelectionMode.COLUMN) {
|
||||
@@ -765,10 +789,12 @@ export class SelectionService extends Disposable implements ISelectionService {
|
||||
|
||||
private _handleBufferActivate(e: {activeBuffer: IBuffer, inactiveBuffer: IBuffer}): void {
|
||||
this.clearSelection();
|
||||
// Only adjust the selection on trim, shiftElements is rarely used (only in
|
||||
// reverseIndex) and delete in a splice is only ever used when the same
|
||||
// number of elements was just added. Given this is could actually be
|
||||
// beneficial to leave the selection as is for these cases.
|
||||
/*
|
||||
* Only adjust the selection on trim, shiftElements is rarely used (only in
|
||||
* reverseIndex) and delete in a splice is only ever used when the same
|
||||
* number of elements was just added. Given this is could actually be
|
||||
* beneficial to leave the selection as is for these cases.
|
||||
*/
|
||||
this._trimListener.dispose();
|
||||
this._trimListener = e.activeBuffer.lines.onTrim(amount => this._handleTrim(amount));
|
||||
}
|
||||
@@ -784,13 +810,17 @@ export class SelectionService extends Disposable implements ISelectionService {
|
||||
for (let i = 0; x >= i; i++) {
|
||||
const length = bufferLine.loadCell(i, this._workCell).getChars().length;
|
||||
if (this._workCell.getWidth() === 0) {
|
||||
// Wide characters aren't included in the line string so decrement the
|
||||
// index so the index is back on the wide character.
|
||||
/*
|
||||
* Wide characters aren't included in the line string so decrement the
|
||||
* index so the index is back on the wide character.
|
||||
*/
|
||||
charIndex--;
|
||||
} else if (length > 1 && x !== i) {
|
||||
// Emojis take up multiple characters, so adjust accordingly. For these
|
||||
// we don't want ot include the character at the column as we're
|
||||
// returning the start index in the string, not the end index.
|
||||
/*
|
||||
* Emojis take up multiple characters, so adjust accordingly. For these
|
||||
* we don't want ot include the character at the column as we're
|
||||
* returning the start index in the string, not the end index.
|
||||
*/
|
||||
charIndex += length - 1;
|
||||
}
|
||||
}
|
||||
@@ -853,15 +883,19 @@ export class SelectionService extends Disposable implements ISelectionService {
|
||||
endIndex++;
|
||||
}
|
||||
} else {
|
||||
// Expand until whitespace is hit. This algorithm works by scanning left
|
||||
// and right from the starting position, keeping both the index format
|
||||
// (line) and the column format (bufferLine) in sync. When a wide
|
||||
// character is hit, it is recorded and the column index is adjusted.
|
||||
/*
|
||||
* Expand until whitespace is hit. This algorithm works by scanning left
|
||||
* and right from the starting position, keeping both the index format
|
||||
* (line) and the column format (bufferLine) in sync. When a wide
|
||||
* character is hit, it is recorded and the column index is adjusted.
|
||||
*/
|
||||
let startCol = coords[0];
|
||||
let endCol = coords[0];
|
||||
|
||||
// Consider the initial position, skip it and increment the wide char
|
||||
// variable
|
||||
/*
|
||||
* Consider the initial position, skip it and increment the wide char
|
||||
* variable
|
||||
*/
|
||||
if (bufferLine.getWidth(startCol) === 0) {
|
||||
leftWideCharCount++;
|
||||
startCol--;
|
||||
@@ -887,8 +921,10 @@ export class SelectionService extends Disposable implements ISelectionService {
|
||||
leftWideCharCount++;
|
||||
startCol--;
|
||||
} else if (length > 1) {
|
||||
// If the next character's string is longer than 1 char (eg. emoji),
|
||||
// adjust the index
|
||||
/*
|
||||
* If the next character's string is longer than 1 char (eg. emoji),
|
||||
* adjust the index
|
||||
*/
|
||||
leftLongCharOffset += length - 1;
|
||||
startIndex -= length - 1;
|
||||
}
|
||||
@@ -903,8 +939,10 @@ export class SelectionService extends Disposable implements ISelectionService {
|
||||
rightWideCharCount++;
|
||||
endCol++;
|
||||
} else if (length > 1) {
|
||||
// If the next character's string is longer than 1 char (eg. emoji),
|
||||
// adjust the index
|
||||
/*
|
||||
* If the next character's string is longer than 1 char (eg. emoji),
|
||||
* adjust the index
|
||||
*/
|
||||
rightLongCharOffset += length - 1;
|
||||
endIndex += length - 1;
|
||||
}
|
||||
@@ -916,16 +954,20 @@ export class SelectionService extends Disposable implements ISelectionService {
|
||||
// Incremenet the end index so it is at the start of the next character
|
||||
endIndex++;
|
||||
|
||||
// Calculate the start _column_, converting the the string indexes back to
|
||||
// column coordinates.
|
||||
/*
|
||||
* Calculate the start _column_, converting the the string indexes back to
|
||||
* column coordinates.
|
||||
*/
|
||||
let start =
|
||||
startIndex // The index of the selection's start char in the line string
|
||||
+ charOffset // The difference between the initial char's column and index
|
||||
- leftWideCharCount // The number of wide chars left of the initial char
|
||||
+ leftLongCharOffset; // The number of additional chars left of the initial char added by columns with strings longer than 1 (emojis)
|
||||
|
||||
// Calculate the length in _columns_, converting the the string indexes back
|
||||
// to column coordinates.
|
||||
/*
|
||||
* Calculate the length in _columns_, converting the the string indexes back
|
||||
* to column coordinates.
|
||||
*/
|
||||
let length = Math.min(this._bufferService.cols, // Disallow lengths larger than the terminal cols
|
||||
endIndex // The index of the selection's end char in the line string
|
||||
- startIndex // The index of the selection's start char in the line string
|
||||
@@ -1002,8 +1044,10 @@ export class SelectionService extends Disposable implements ISelectionService {
|
||||
endRow--;
|
||||
}
|
||||
|
||||
// Adjust wrapped length value, this only needs to happen when values are reversed as in that
|
||||
// case we're interested in the start of the word, not the end
|
||||
/*
|
||||
* Adjust wrapped length value, this only needs to happen when values are reversed as in that
|
||||
* case we're interested in the start of the word, not the end
|
||||
*/
|
||||
if (!this._model.areSelectionValuesReversed()) {
|
||||
while (wordPosition.start + wordPosition.length > this._bufferService.cols) {
|
||||
wordPosition.length -= this._bufferService.cols;
|
||||
@@ -1021,8 +1065,10 @@ export class SelectionService extends Disposable implements ISelectionService {
|
||||
* @param cell The cell to check.
|
||||
*/
|
||||
private _isCharWordSeparator(cell: ICellData): boolean {
|
||||
// Zero width characters are never separators as they are always to the
|
||||
// right of wide characters
|
||||
/*
|
||||
* Zero width characters are never separators as they are always to the
|
||||
* right of wide characters
|
||||
*/
|
||||
if (cell.getWidth() === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -52,8 +52,10 @@ export class CircularList<T> extends Disposable implements ICircularList<T> {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reconstruct array, starting at index 0. Only transfer values from the
|
||||
// indexes 0 to length.
|
||||
/*
|
||||
* Reconstruct array, starting at index 0. Only transfer values from the
|
||||
* indexes 0 to length.
|
||||
*/
|
||||
const newArray = new Array<T | undefined>(newMaxLength);
|
||||
for (let i = 0; i < Math.min(newMaxLength, this.length); i++) {
|
||||
newArray[i] = this._array[this._getCyclicIndex(i)];
|
||||
|
||||
+22
-12
@@ -27,9 +27,11 @@ export namespace channels {
|
||||
}
|
||||
|
||||
export function toRgba(r: number, g: number, b: number, a: number = 0xFF): number {
|
||||
// Note: The aggregated number is RGBA32 (BE), thus needs to be converted to ABGR32
|
||||
// on LE systems, before it can be used for direct 32-bit buffer writes.
|
||||
// >>> 0 forces an unsigned int
|
||||
/*
|
||||
* Note: The aggregated number is RGBA32 (BE), thus needs to be converted to ABGR32
|
||||
* on LE systems, before it can be used for direct 32-bit buffer writes.
|
||||
* >>> 0 forces an unsigned int
|
||||
*/
|
||||
return (r << 24 | g << 16 | b << 8 | a) >>> 0;
|
||||
}
|
||||
|
||||
@@ -197,8 +199,10 @@ export namespace css {
|
||||
throw new Error('css.toColor: Unsupported css format');
|
||||
}
|
||||
|
||||
// Validate the color using canvas fillStyle
|
||||
// See https://html.spec.whatwg.org/multipage/canvas.html#fill-and-stroke-styles
|
||||
/*
|
||||
* Validate the color using canvas fillStyle
|
||||
* See https://html.spec.whatwg.org/multipage/canvas.html#fill-and-stroke-styles
|
||||
*/
|
||||
$ctx.fillStyle = $litmusColor;
|
||||
$ctx.fillStyle = css;
|
||||
if (typeof $ctx.fillStyle !== 'string') {
|
||||
@@ -213,9 +217,11 @@ export namespace css {
|
||||
throw new Error('css.toColor: Unsupported css format');
|
||||
}
|
||||
|
||||
// Extract the color from the canvas' fillStyle property which exposes the color value in rgba()
|
||||
// format
|
||||
// See https://html.spec.whatwg.org/multipage/canvas.html#serialisation-of-a-color
|
||||
/*
|
||||
* Extract the color from the canvas' fillStyle property which exposes the color value in rgba()
|
||||
* format
|
||||
* See https://html.spec.whatwg.org/multipage/canvas.html#serialisation-of-a-color
|
||||
*/
|
||||
return {
|
||||
rgba: channels.toRgba($r, $g, $b, $a),
|
||||
css
|
||||
@@ -321,8 +327,10 @@ export namespace rgba {
|
||||
}
|
||||
|
||||
export function reduceLuminance(bgRgba: number, fgRgba: number, ratio: number): number {
|
||||
// This is a naive but fast approach to reducing luminance as converting to
|
||||
// HSL and back is expensive
|
||||
/*
|
||||
* This is a naive but fast approach to reducing luminance as converting to
|
||||
* HSL and back is expensive
|
||||
*/
|
||||
const bgR = (bgRgba >> 24) & 0xFF;
|
||||
const bgG = (bgRgba >> 16) & 0xFF;
|
||||
const bgB = (bgRgba >> 8) & 0xFF;
|
||||
@@ -341,8 +349,10 @@ export namespace rgba {
|
||||
}
|
||||
|
||||
export function increaseLuminance(bgRgba: number, fgRgba: number, ratio: number): number {
|
||||
// This is a naive but fast approach to increasing luminance as converting to
|
||||
// HSL and back is expensive
|
||||
/*
|
||||
* This is a naive but fast approach to increasing luminance as converting to
|
||||
* HSL and back is expensive
|
||||
*/
|
||||
const bgR = (bgRgba >> 24) & 0xFF;
|
||||
const bgG = (bgRgba >> 16) & 0xFF;
|
||||
const bgB = (bgRgba >> 8) & 0xFF;
|
||||
|
||||
@@ -176,8 +176,10 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
|
||||
x = Math.max(x, BufferServiceConstants.MINIMUM_COLS);
|
||||
y = Math.max(y, BufferServiceConstants.MINIMUM_ROWS);
|
||||
|
||||
// Flush pending writes before resize to avoid race conditions where async
|
||||
// writes are processed with incorrect dimensions
|
||||
/*
|
||||
* Flush pending writes before resize to avoid race conditions where async
|
||||
* writes are processed with incorrect dimensions
|
||||
*/
|
||||
this._writeBuffer.flushSync();
|
||||
|
||||
this._bufferService.resize(x, y);
|
||||
|
||||
@@ -613,8 +613,10 @@ describe('InputHandler', () => {
|
||||
await inputHandler.parseP('a'.repeat(bufferService.cols + 9)); // line 1 and 2
|
||||
for (let i = 3; i < bufferService.rows; ++i) await inputHandler.parseP('a'.repeat(bufferService.cols));
|
||||
|
||||
// params[1] left and above with wrap
|
||||
// confirm precondition that line 2 is wrapped
|
||||
/*
|
||||
* params[1] left and above with wrap
|
||||
* confirm precondition that line 2 is wrapped
|
||||
*/
|
||||
assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, true);
|
||||
bufferService.buffer.y = 2;
|
||||
bufferService.buffer.x = 40;
|
||||
@@ -628,8 +630,10 @@ describe('InputHandler', () => {
|
||||
await inputHandler.parseP('a'.repeat(bufferService.cols + 9)); // line 1 and 2
|
||||
for (let i = 3; i < bufferService.rows; ++i) await inputHandler.parseP('a'.repeat(bufferService.cols));
|
||||
|
||||
// params[1] left and above with wrap
|
||||
// confirm precondition that line 2 is wrapped
|
||||
/*
|
||||
* params[1] left and above with wrap
|
||||
* confirm precondition that line 2 is wrapped
|
||||
*/
|
||||
assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, true);
|
||||
bufferService.buffer.y = 1;
|
||||
bufferService.buffer.x = 90; // Cursor is beyond last column
|
||||
|
||||
+203
-121
@@ -33,10 +33,12 @@ const GLEVEL: { [key: string]: number } = { '(': 0, ')': 1, '*': 2, '+': 3, '-':
|
||||
/**
|
||||
* Document xterm VT features here that are currently unsupported
|
||||
*/
|
||||
// @vt: #N DCS DECUDK "User Defined Keys" "DCS Ps ; Ps \| Pt ST" "Definitions for user-defined keys."
|
||||
// @vt: #N DCS XTGETTCAP "Request Terminfo String" "DCS + q Pt ST" "Request Terminfo String."
|
||||
// @vt: #N DCS XTSETTCAP "Set Terminfo Data" "DCS + p Pt ST" "Set Terminfo Data."
|
||||
// @vt: #N OSC 1 "Set Icon Name" "OSC 1 ; Pt BEL" "Set icon name."
|
||||
/*
|
||||
* @vt: #N DCS DECUDK "User Defined Keys" "DCS Ps ; Ps \| Pt ST" "Definitions for user-defined keys."
|
||||
* @vt: #N DCS XTGETTCAP "Request Terminfo String" "DCS + q Pt ST" "Request Terminfo String."
|
||||
* @vt: #N DCS XTSETTCAP "Set Terminfo Data" "DCS + p Pt ST" "Set Terminfo Data."
|
||||
* @vt: #N OSC 1 "Set Icon Name" "OSC 1 ; Pt BEL" "Set icon name."
|
||||
*/
|
||||
|
||||
/**
|
||||
* Max length of the UTF32 input buffer. Real memory consumption is 4 times higher.
|
||||
@@ -288,13 +290,17 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
this._parser.registerOscHandler(1, new OscHandler(data => this.setIconName(data)));
|
||||
// 2 - title
|
||||
this._parser.registerOscHandler(2, new OscHandler(data => this.setTitle(data)));
|
||||
// 3 - set property X in the form "prop=value"
|
||||
// 4 - Change Color Number
|
||||
/*
|
||||
* 3 - set property X in the form "prop=value"
|
||||
* 4 - Change Color Number
|
||||
*/
|
||||
this._parser.registerOscHandler(4, new OscHandler(data => this.setOrReportIndexedColor(data)));
|
||||
// 5 - Change Special Color Number
|
||||
// 6 - Enable/disable Special Color Number c
|
||||
// 7 - current directory? (not in xterm spec, see https://gitlab.com/gnachman/iterm2/issues/3939)
|
||||
// 8 - create hyperlink (not in xterm spec, see https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda)
|
||||
/*
|
||||
* 5 - Change Special Color Number
|
||||
* 6 - Enable/disable Special Color Number c
|
||||
* 7 - current directory? (not in xterm spec, see https://gitlab.com/gnachman/iterm2/issues/3939)
|
||||
* 8 - create hyperlink (not in xterm spec, see https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda)
|
||||
*/
|
||||
this._parser.registerOscHandler(8, new OscHandler(data => this.setHyperlink(data)));
|
||||
// 10 - Change VT100 text foreground color to Pt.
|
||||
this._parser.registerOscHandler(10, new OscHandler(data => this.setOrReportFgColor(data)));
|
||||
@@ -302,34 +308,40 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
this._parser.registerOscHandler(11, new OscHandler(data => this.setOrReportBgColor(data)));
|
||||
// 12 - Change text cursor color to Pt.
|
||||
this._parser.registerOscHandler(12, new OscHandler(data => this.setOrReportCursorColor(data)));
|
||||
// 13 - Change mouse foreground color to Pt.
|
||||
// 14 - Change mouse background color to Pt.
|
||||
// 15 - Change Tektronix foreground color to Pt.
|
||||
// 16 - Change Tektronix background color to Pt.
|
||||
// 17 - Change highlight background color to Pt.
|
||||
// 18 - Change Tektronix cursor color to Pt.
|
||||
// 19 - Change highlight foreground color to Pt.
|
||||
// 46 - Change Log File to Pt.
|
||||
// 50 - Set Font to Pt.
|
||||
// 51 - reserved for Emacs shell.
|
||||
// 52 - Manipulate Selection Data.
|
||||
// 104 ; c - Reset Color Number c.
|
||||
/*
|
||||
* 13 - Change mouse foreground color to Pt.
|
||||
* 14 - Change mouse background color to Pt.
|
||||
* 15 - Change Tektronix foreground color to Pt.
|
||||
* 16 - Change Tektronix background color to Pt.
|
||||
* 17 - Change highlight background color to Pt.
|
||||
* 18 - Change Tektronix cursor color to Pt.
|
||||
* 19 - Change highlight foreground color to Pt.
|
||||
* 46 - Change Log File to Pt.
|
||||
* 50 - Set Font to Pt.
|
||||
* 51 - reserved for Emacs shell.
|
||||
* 52 - Manipulate Selection Data.
|
||||
* 104 ; c - Reset Color Number c.
|
||||
*/
|
||||
this._parser.registerOscHandler(104, new OscHandler(data => this.restoreIndexedColor(data)));
|
||||
// 105 ; c - Reset Special Color Number c.
|
||||
// 106 ; c; f - Enable/disable Special Color Number c.
|
||||
// 110 - Reset VT100 text foreground color.
|
||||
/*
|
||||
* 105 ; c - Reset Special Color Number c.
|
||||
* 106 ; c; f - Enable/disable Special Color Number c.
|
||||
* 110 - Reset VT100 text foreground color.
|
||||
*/
|
||||
this._parser.registerOscHandler(110, new OscHandler(data => this.restoreFgColor(data)));
|
||||
// 111 - Reset VT100 text background color.
|
||||
this._parser.registerOscHandler(111, new OscHandler(data => this.restoreBgColor(data)));
|
||||
// 112 - Reset text cursor color.
|
||||
this._parser.registerOscHandler(112, new OscHandler(data => this.restoreCursorColor(data)));
|
||||
// 113 - Reset mouse foreground color.
|
||||
// 114 - Reset mouse background color.
|
||||
// 115 - Reset Tektronix foreground color.
|
||||
// 116 - Reset Tektronix background color.
|
||||
// 117 - Reset highlight color.
|
||||
// 118 - Reset Tektronix cursor color.
|
||||
// 119 - Reset highlight foreground color.
|
||||
/*
|
||||
* 113 - Reset mouse foreground color.
|
||||
* 114 - Reset mouse background color.
|
||||
* 115 - Reset Tektronix foreground color.
|
||||
* 116 - Reset Tektronix background color.
|
||||
* 117 - Reset highlight color.
|
||||
* 118 - Reset Tektronix cursor color.
|
||||
* 119 - Reset highlight foreground color.
|
||||
*/
|
||||
|
||||
/**
|
||||
* ESC handlers
|
||||
@@ -466,8 +478,10 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the dirty row service so we know which lines changed as a result of parsing
|
||||
// Important: do not clear between async calls, otherwise we lost pending update information.
|
||||
/*
|
||||
* Clear the dirty row service so we know which lines changed as a result of parsing
|
||||
* Important: do not clear between async calls, otherwise we lost pending update information.
|
||||
*/
|
||||
if (!wasPaused) {
|
||||
this._dirtyRowTracker.clearRange();
|
||||
}
|
||||
@@ -502,8 +516,10 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
this._onCursorMove.fire();
|
||||
}
|
||||
|
||||
// Refresh any dirty rows accumulated as part of parsing, fire only for rows within the
|
||||
// _viewport_ which is relative to ydisp, not relative to ybase.
|
||||
/*
|
||||
* Refresh any dirty rows accumulated as part of parsing, fire only for rows within the
|
||||
* _viewport_ which is relative to ydisp, not relative to ybase.
|
||||
*/
|
||||
const viewportEnd = this._dirtyRowTracker.end + (this._bufferService.buffer.ybase - this._bufferService.buffer.ydisp);
|
||||
const viewportStart = this._dirtyRowTracker.start + (this._bufferService.buffer.ybase - this._bufferService.buffer.ydisp);
|
||||
if (viewportStart < this._bufferService.rows) {
|
||||
@@ -525,8 +541,10 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
const curAttr = this._curAttrData;
|
||||
let bufferRow = this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y);
|
||||
|
||||
// Defensive check: bufferRow can be undefined if a resize occurred mid-write due to async
|
||||
// scheduling gaps in WriteBuffer. See https://github.com/xtermjs/xterm.js/issues/5597
|
||||
/*
|
||||
* Defensive check: bufferRow can be undefined if a resize occurred mid-write due to async
|
||||
* scheduling gaps in WriteBuffer. See https://github.com/xtermjs/xterm.js/issues/5597
|
||||
*/
|
||||
if (!bufferRow) {
|
||||
return;
|
||||
}
|
||||
@@ -542,15 +560,19 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
for (let pos = start; pos < end; ++pos) {
|
||||
code = data[pos];
|
||||
|
||||
// Soft hyphen's (U+00AD) behavior is ambiguous and differs across terminals. We opt to treat
|
||||
// it as a zero-width hint to text layout engines and simply ignore it.
|
||||
/*
|
||||
* Soft hyphen's (U+00AD) behavior is ambiguous and differs across terminals. We opt to treat
|
||||
* it as a zero-width hint to text layout engines and simply ignore it.
|
||||
*/
|
||||
if (code === 0xAD) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// get charset replacement character
|
||||
// charset is only defined for ASCII, therefore we only
|
||||
// search for an replacement char if code < 127
|
||||
/*
|
||||
* get charset replacement character
|
||||
* charset is only defined for ASCII, therefore we only
|
||||
* search for an replacement char if code < 127
|
||||
*/
|
||||
if (code < 127 && charset) {
|
||||
const ch = charset[String.fromCharCode(code)];
|
||||
if (ch) {
|
||||
@@ -571,12 +593,16 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
this._oscLinkService.addLineToLink(this._getCurrentLinkId(), this._activeBuffer.ybase + this._activeBuffer.y);
|
||||
}
|
||||
|
||||
// goto next line if ch would overflow
|
||||
// NOTE: To avoid costly width checks here,
|
||||
// the terminal does not allow a cols < 2.
|
||||
/*
|
||||
* goto next line if ch would overflow
|
||||
* NOTE: To avoid costly width checks here,
|
||||
* the terminal does not allow a cols < 2.
|
||||
*/
|
||||
if (this._activeBuffer.x + chWidth - oldWidth > cols) {
|
||||
// autowrap - DECAWM
|
||||
// automatically wraps to the beginning of the next line
|
||||
/*
|
||||
* autowrap - DECAWM
|
||||
* automatically wraps to the beginning of the next line
|
||||
*/
|
||||
if (wraparoundMode) {
|
||||
const oldRow = bufferRow;
|
||||
let oldCol = this._activeBuffer.x - oldWidth;
|
||||
@@ -589,8 +615,10 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
if (this._activeBuffer.y >= this._bufferService.rows) {
|
||||
this._activeBuffer.y = this._bufferService.rows - 1;
|
||||
}
|
||||
// The line already exists (eg. the initial viewport), mark it as a
|
||||
// wrapped line
|
||||
/*
|
||||
* The line already exists (eg. the initial viewport), mark it as a
|
||||
* wrapped line
|
||||
*/
|
||||
this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!.isWrapped = true;
|
||||
}
|
||||
// row changed, get it again
|
||||
@@ -599,8 +627,10 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
return;
|
||||
}
|
||||
if (oldWidth > 0 && bufferRow instanceof BufferLine) {
|
||||
// Combining character widens 1 column to 2.
|
||||
// Move old character to next line.
|
||||
/*
|
||||
* Combining character widens 1 column to 2.
|
||||
* Move old character to next line.
|
||||
*/
|
||||
bufferRow.copyCellsFrom(oldRow as BufferLine,
|
||||
oldCol, 0, oldWidth, false);
|
||||
}
|
||||
@@ -611,22 +641,28 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
} else {
|
||||
this._activeBuffer.x = cols - 1;
|
||||
if (chWidth === 2) {
|
||||
// FIXME: check for xterm behavior
|
||||
// What to do here? We got a wide char that does not fit into last cell
|
||||
/*
|
||||
* FIXME: check for xterm behavior
|
||||
* What to do here? We got a wide char that does not fit into last cell
|
||||
*/
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// insert combining char at last cursor position
|
||||
// this._activeBuffer.x should never be 0 for a combining char
|
||||
// since they always follow a cell consuming char
|
||||
// therefore we can test for this._activeBuffer.x to avoid overflow left
|
||||
/*
|
||||
* insert combining char at last cursor position
|
||||
* this._activeBuffer.x should never be 0 for a combining char
|
||||
* since they always follow a cell consuming char
|
||||
* therefore we can test for this._activeBuffer.x to avoid overflow left
|
||||
*/
|
||||
if (shouldJoin && this._activeBuffer.x) {
|
||||
const offset = bufferRow.getWidth(this._activeBuffer.x - 1) ? 1 : 2;
|
||||
// if empty cell after fullwidth, need to go 2 cells back
|
||||
// it is save to step 2 cells back here
|
||||
// since an empty cell is only set by fullwidth chars
|
||||
/*
|
||||
* if empty cell after fullwidth, need to go 2 cells back
|
||||
* it is save to step 2 cells back here
|
||||
* since an empty cell is only set by fullwidth chars
|
||||
*/
|
||||
bufferRow.addCodepointToCell(this._activeBuffer.x - offset,
|
||||
code, chWidth);
|
||||
for (let delta = chWidth - oldWidth; --delta >= 0;) {
|
||||
@@ -639,9 +675,11 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
if (insertMode) {
|
||||
// right shift cells according to the width
|
||||
bufferRow.insertCells(this._activeBuffer.x, chWidth - oldWidth, this._activeBuffer.getNullCell(curAttr));
|
||||
// test last cell - since the last cell has only room for
|
||||
// a halfwidth char any fullwidth shifted there is lost
|
||||
// and will be set to empty cell
|
||||
/*
|
||||
* test last cell - since the last cell has only room for
|
||||
* a halfwidth char any fullwidth shifted there is lost
|
||||
* and will be set to empty cell
|
||||
*/
|
||||
if (bufferRow.getWidth(cols - 1) === 2) {
|
||||
bufferRow.setCellFromCodepoint(cols - 1, NULL_CELL_CODE, NULL_CELL_WIDTH, curAttr);
|
||||
}
|
||||
@@ -650,9 +688,11 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
// write current char to buffer and advance cursor
|
||||
bufferRow.setCellFromCodepoint(this._activeBuffer.x++, code, chWidth, curAttr);
|
||||
|
||||
// fullwidth char - also set next cell to placeholder stub and advance cursor
|
||||
// for graphemes bigger than fullwidth we can simply loop to zero
|
||||
// we already made sure above, that this._activeBuffer.x + chWidth will not overflow right
|
||||
/*
|
||||
* fullwidth char - also set next cell to placeholder stub and advance cursor
|
||||
* for graphemes bigger than fullwidth we can simply loop to zero
|
||||
* we already made sure above, that this._activeBuffer.x + chWidth will not overflow right
|
||||
*/
|
||||
if (chWidth > 0) {
|
||||
while (--chWidth) {
|
||||
// other than a regular empty cell a cell following a wide char has no width
|
||||
@@ -750,11 +790,13 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
} else if (this._activeBuffer.y >= this._bufferService.rows) {
|
||||
this._activeBuffer.y = this._bufferService.rows - 1;
|
||||
} else {
|
||||
// There was an explicit line feed (not just a carriage return), so clear the wrapped state of
|
||||
// the line. This is particularly important on conpty/Windows where revisiting lines to
|
||||
// reprint is common, especially on resize. Note that the windowsMode wrapped line heuristics
|
||||
// can mess with this so windowsMode should be disabled, which is recommended on Windows build
|
||||
// 21376 and above.
|
||||
/*
|
||||
* There was an explicit line feed (not just a carriage return), so clear the wrapped state of
|
||||
* the line. This is particularly important on conpty/Windows where revisiting lines to
|
||||
* reprint is common, especially on resize. Note that the windowsMode wrapped line heuristics
|
||||
* can mess with this so windowsMode should be disabled, which is recommended on Windows build
|
||||
* 21376 and above.
|
||||
*/
|
||||
this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!.isWrapped = false;
|
||||
}
|
||||
// If the end of the line is hit, prevent this action from wrapping around to the next line.
|
||||
@@ -799,9 +841,11 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
return true;
|
||||
}
|
||||
|
||||
// reverse wrap-around is enabled
|
||||
// other than for normal operation mode, reverse wrap-around allows the cursor
|
||||
// to be at x=cols to be able to address the last cell of a row by BS
|
||||
/*
|
||||
* reverse wrap-around is enabled
|
||||
* other than for normal operation mode, reverse wrap-around allows the cursor
|
||||
* to be at x=cols to be able to address the last cell of a row by BS
|
||||
*/
|
||||
this._restrictCursor(this._bufferService.cols);
|
||||
|
||||
if (this._activeBuffer.x > 0) {
|
||||
@@ -822,17 +866,21 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!.isWrapped = false;
|
||||
this._activeBuffer.y--;
|
||||
this._activeBuffer.x = this._bufferService.cols - 1;
|
||||
// find last taken cell - last cell can have 3 different states:
|
||||
// - hasContent(true) + hasWidth(1): narrow char - we are done
|
||||
// - hasWidth(0): second part of wide char - we are done
|
||||
// - hasContent(false) + hasWidth(1): empty cell due to early wrapping wide char, go one
|
||||
// cell further back
|
||||
/*
|
||||
* find last taken cell - last cell can have 3 different states:
|
||||
* - hasContent(true) + hasWidth(1): narrow char - we are done
|
||||
* - hasWidth(0): second part of wide char - we are done
|
||||
* - hasContent(false) + hasWidth(1): empty cell due to early wrapping wide char, go one
|
||||
* cell further back
|
||||
*/
|
||||
const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!;
|
||||
if (line.hasWidth(this._activeBuffer.x) && !line.hasContent(this._activeBuffer.x)) {
|
||||
this._activeBuffer.x--;
|
||||
// We do this only once, since width=1 + hasContent=false currently happens only once
|
||||
// before early wrapping of a wide char.
|
||||
// This needs to be fixed once we support graphemes taking more than 2 cells.
|
||||
/*
|
||||
* We do this only once, since width=1 + hasContent=false currently happens only once
|
||||
* before early wrapping of a wide char.
|
||||
* This needs to be fixed once we support graphemes taking more than 2 cells.
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -913,8 +961,10 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* Set relative cursor position.
|
||||
*/
|
||||
private _moveCursor(x: number, y: number): void {
|
||||
// for relative changes we have to make sure we are within 0 .. cols/rows - 1
|
||||
// before calculating the new position
|
||||
/*
|
||||
* for relative changes we have to make sure we are within 0 .. cols/rows - 1
|
||||
* before calculating the new position
|
||||
*/
|
||||
this._restrictCursor();
|
||||
this._setCursor(this._activeBuffer.x + x, this._activeBuffer.y + y);
|
||||
}
|
||||
@@ -1354,8 +1404,10 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
const scrollBottomRowsOffset = this._bufferService.rows - 1 - this._activeBuffer.scrollBottom;
|
||||
const scrollBottomAbsolute = this._bufferService.rows - 1 + this._activeBuffer.ybase - scrollBottomRowsOffset + 1;
|
||||
while (param--) {
|
||||
// test: echo -e '\e[44m\e[1L\e[0m'
|
||||
// blankLine(true) - xterm/linux behavior
|
||||
/*
|
||||
* test: echo -e '\e[44m\e[1L\e[0m'
|
||||
* blankLine(true) - xterm/linux behavior
|
||||
*/
|
||||
this._activeBuffer.lines.splice(scrollBottomAbsolute - 1, 1);
|
||||
this._activeBuffer.lines.splice(row, 0, this._activeBuffer.getBlankLine(this._eraseAttrData()));
|
||||
}
|
||||
@@ -1388,8 +1440,10 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
j = this._bufferService.rows - 1 - this._activeBuffer.scrollBottom;
|
||||
j = this._bufferService.rows - 1 + this._activeBuffer.ybase - j;
|
||||
while (param--) {
|
||||
// test: echo -e '\e[44m\e[1M\e[0m'
|
||||
// blankLine(true) - xterm/linux behavior
|
||||
/*
|
||||
* test: echo -e '\e[44m\e[1M\e[0m'
|
||||
* blankLine(true) - xterm/linux behavior
|
||||
*/
|
||||
this._activeBuffer.lines.splice(row, 1);
|
||||
this._activeBuffer.lines.splice(j, 0, this._activeBuffer.getBlankLine(this._eraseAttrData()));
|
||||
}
|
||||
@@ -1737,16 +1791,20 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
if (params.params[0] > 0) {
|
||||
return true;
|
||||
}
|
||||
// xterm and urxvt
|
||||
// seem to spit this
|
||||
// out around ~370 times (?).
|
||||
/*
|
||||
* xterm and urxvt
|
||||
* seem to spit this
|
||||
* out around ~370 times (?).
|
||||
*/
|
||||
if (this._is('xterm')) {
|
||||
this._coreService.triggerDataEvent(C0.ESC + '[>0;276;0c');
|
||||
} else if (this._is('rxvt-unicode')) {
|
||||
this._coreService.triggerDataEvent(C0.ESC + '[>85;95;0c');
|
||||
} else if (this._is('linux')) {
|
||||
// not supported by linux console.
|
||||
// linux console echoes parameters.
|
||||
/*
|
||||
* not supported by linux console.
|
||||
* linux console echoes parameters.
|
||||
*/
|
||||
this._coreService.triggerDataEvent(params.params[0] + 'c');
|
||||
} else if (this._is('screen')) {
|
||||
this._coreService.triggerDataEvent(C0.ESC + '[>83;40003;0c');
|
||||
@@ -1979,13 +2037,17 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
this._mouseStateService.activeProtocol = 'DRAG';
|
||||
break;
|
||||
case 1003: // any event mouse
|
||||
// any event - sends motion events,
|
||||
// even if there is no button held down.
|
||||
/*
|
||||
* any event - sends motion events,
|
||||
* even if there is no button held down.
|
||||
*/
|
||||
this._mouseStateService.activeProtocol = 'ANY';
|
||||
break;
|
||||
case 1004: // send focusin/focusout events
|
||||
// focusin: ^[[I
|
||||
// focusout: ^[[O
|
||||
/*
|
||||
* focusin: ^[[I
|
||||
* focusout: ^[[O
|
||||
*/
|
||||
this._coreService.decPrivateModes.sendFocus = true;
|
||||
this._onRequestSendFocus.fire();
|
||||
break;
|
||||
@@ -2408,10 +2470,12 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* Returns advance for params index.
|
||||
*/
|
||||
private _extractColor(params: IParams, pos: number, attr: IAttributeData): number {
|
||||
// normalize params
|
||||
// meaning: [target, CM, ign, val, val, val]
|
||||
// RGB : [ 38/48, 2, ign, r, g, b]
|
||||
// P256 : [ 38/48, 5, ign, v, ign, ign]
|
||||
/*
|
||||
* normalize params
|
||||
* meaning: [target, CM, ign, val, val, val]
|
||||
* RGB : [ 38/48, 2, ign, r, g, b]
|
||||
* P256 : [ 38/48, 5, ign, v, ign, ign]
|
||||
*/
|
||||
const accu = [0, 0, -1, 0, 0, 0];
|
||||
|
||||
// alignment placeholder for non color space sequences
|
||||
@@ -2477,8 +2541,10 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* 4:5 - dashed underline
|
||||
*/
|
||||
private _processUnderline(style: number, attr: IAttributeData): void {
|
||||
// treat extended attrs as immutable, thus always clone from old one
|
||||
// this is needed since the buffer only holds references to it
|
||||
/*
|
||||
* treat extended attrs as immutable, thus always clone from old one
|
||||
* this is needed since the buffer only holds references to it
|
||||
*/
|
||||
attr.extended = attr.extended.clone();
|
||||
|
||||
// default to 1 == single underline
|
||||
@@ -2501,8 +2567,10 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
attr.fg = DEFAULT_ATTR_DATA.fg;
|
||||
attr.bg = DEFAULT_ATTR_DATA.bg;
|
||||
attr.extended = attr.extended.clone();
|
||||
// Reset underline style and color. Note that we don't want to reset other
|
||||
// fields such as the url id.
|
||||
/*
|
||||
* Reset underline style and color. Note that we don't want to reset other
|
||||
* fields such as the url id.
|
||||
*/
|
||||
attr.extended.underlineStyle = UnderlineStyle.NONE;
|
||||
attr.extended.underlineColor &= ~(Attributes.CM_MASK | Attributes.RGB_MASK);
|
||||
attr.updateExtended();
|
||||
@@ -2641,8 +2709,10 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
// blink
|
||||
attr.fg |= FgFlags.BLINK;
|
||||
} else if (p === 7) {
|
||||
// inverse and positive
|
||||
// test with: echo -e '\e[31m\e[42mhello\e[7mworld\e[27mhi\e[m'
|
||||
/*
|
||||
* inverse and positive
|
||||
* test with: echo -e '\e[31m\e[42mhello\e[7mworld\e[27mhi\e[m'
|
||||
*/
|
||||
attr.fg |= FgFlags.INVERSE;
|
||||
} else if (p === 8) {
|
||||
// invisible
|
||||
@@ -2756,8 +2826,10 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
|
||||
// @vt: #P[Only CPR is supported.] CSI DECDSR "DEC Device Status Report" "CSI ? Ps n" "Only CPR is supported (same as DSR)."
|
||||
public deviceStatusPrivate(params: IParams): boolean {
|
||||
// modern xterm doesnt seem to
|
||||
// respond to any of these except ?6, 6, and 5
|
||||
/*
|
||||
* modern xterm doesnt seem to
|
||||
* respond to any of these except ?6, 6, and 5
|
||||
*/
|
||||
switch (params.params[0]) {
|
||||
case 6:
|
||||
// cursor position
|
||||
@@ -2766,20 +2838,28 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
this._coreService.triggerDataEvent(`${C0.ESC}[?${y};${x}R`);
|
||||
break;
|
||||
case 15:
|
||||
// no printer
|
||||
// this.handler(C0.ESC + '[?11n');
|
||||
/*
|
||||
* no printer
|
||||
* this.handler(C0.ESC + '[?11n');
|
||||
*/
|
||||
break;
|
||||
case 25:
|
||||
// dont support user defined keys
|
||||
// this.handler(C0.ESC + '[?21n');
|
||||
/*
|
||||
* dont support user defined keys
|
||||
* this.handler(C0.ESC + '[?21n');
|
||||
*/
|
||||
break;
|
||||
case 26:
|
||||
// north american keyboard
|
||||
// this.handler(C0.ESC + '[?27;1;0;0n');
|
||||
/*
|
||||
* north american keyboard
|
||||
* this.handler(C0.ESC + '[?27;1;0;0n');
|
||||
*/
|
||||
break;
|
||||
case 53:
|
||||
// no dec locator/mouse
|
||||
// this.handler(C0.ESC + '[?50n');
|
||||
/*
|
||||
* no dec locator/mouse
|
||||
* this.handler(C0.ESC + '[?50n');
|
||||
*/
|
||||
break;
|
||||
case 996:
|
||||
// color scheme query (https://contour-terminal.org/vt-extensions/color-palette-update-notifications/)
|
||||
@@ -3401,9 +3481,11 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
public reverseIndex(): boolean {
|
||||
this._restrictCursor();
|
||||
if (this._activeBuffer.y === this._activeBuffer.scrollTop) {
|
||||
// possibly move the code below to term.reverseScroll();
|
||||
// test: echo -ne '\e[1;1H\e[44m\eM\e[0m'
|
||||
// blankLine(true) is xterm/linux behavior
|
||||
/*
|
||||
* possibly move the code below to term.reverseScroll();
|
||||
* test: echo -ne '\e[1;1H\e[44m\eM\e[0m'
|
||||
* blankLine(true) is xterm/linux behavior
|
||||
*/
|
||||
const scrollRegionHeight = this._activeBuffer.scrollBottom - this._activeBuffer.scrollTop;
|
||||
this._activeBuffer.lines.shiftElements(this._activeBuffer.ybase + this._activeBuffer.y, scrollRegionHeight, 1);
|
||||
this._activeBuffer.lines.set(this._activeBuffer.ybase + this._activeBuffer.y, this._activeBuffer.getBlankLine(this._eraseAttrData()));
|
||||
|
||||
+14
-8
@@ -9,14 +9,18 @@ interface INavigator {
|
||||
platform: string;
|
||||
}
|
||||
|
||||
// We're declaring a navigator global here as we expect it in all runtimes (node and browser), but
|
||||
// we want this module to live in common.
|
||||
/*
|
||||
* We're declaring a navigator global here as we expect it in all runtimes (node and browser), but
|
||||
* we want this module to live in common.
|
||||
*/
|
||||
declare const navigator: INavigator;
|
||||
declare const process: unknown;
|
||||
|
||||
// navigator.userAgent is also checked here because bundling with the process module can cause
|
||||
// issues otherwise. Note that navigator exists in Node.js 21+ but the userAgent is
|
||||
// "Node.js/<version>".
|
||||
/*
|
||||
* navigator.userAgent is also checked here because bundling with the process module can cause
|
||||
* issues otherwise. Note that navigator exists in Node.js 21+ but the userAgent is
|
||||
* "Node.js/<version>".
|
||||
*/
|
||||
export const isNode = (typeof process !== 'undefined' && 'title' in (process as any) && (typeof navigator === 'undefined' || navigator.userAgent.startsWith('Node.js/'))) ? true : false;
|
||||
const userAgent = (isNode) ? 'node' : navigator.userAgent;
|
||||
const platform = (isNode) ? 'node' : navigator.platform;
|
||||
@@ -44,9 +48,11 @@ export function getSafariVersion(): number {
|
||||
return parseInt(majorVersion[1]);
|
||||
}
|
||||
|
||||
// Find the users platform. We use this to interpret the meta key
|
||||
// and ISO third level shifts.
|
||||
// http://stackoverflow.com/q/19877924/577598
|
||||
/*
|
||||
* Find the users platform. We use this to interpret the meta key
|
||||
* and ISO third level shifts.
|
||||
* http://stackoverflow.com/q/19877924/577598
|
||||
*/
|
||||
export const isMac = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K'].includes(platform);
|
||||
export const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].includes(platform);
|
||||
export const isLinux = platform.indexOf('Linux') >= 0;
|
||||
|
||||
@@ -191,8 +191,10 @@ export class SortedList<T> {
|
||||
return mid;
|
||||
}
|
||||
}
|
||||
// key not in list
|
||||
// still return closest min (also used as insert position)
|
||||
/*
|
||||
* key not in list
|
||||
* still return closest min (also used as insert position)
|
||||
*/
|
||||
return min;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user