Compare commits

...
31 Commits
Author SHA1 Message Date
Daniel Imms 9ba6c00a19 Merge pull request #5021 from Tyriar/tyriar5_5
v5.5
2024-04-05 07:00:05 -07:00
Daniel Imms 78297e52aa v5.5 2024-04-05 06:50:30 -07:00
Daniel Imms 53bc9f8442 Merge pull request #5013 from Tyriar/tyriar/ascii
Don't rescale ascii
2024-03-20 10:09:19 -07:00
Daniel Imms 4c90009a0e Don't rescale ascii
See microsoft/vscode#208102
2024-03-20 10:02:53 -07:00
Daniel Imms bee4fd0914 Merge pull request #5012 from Tyriar/tyriar/208102
Only scale when over 50% of following cell
2024-03-19 09:03:36 -07:00
Daniel Imms f8ae7edaa0 Only scale when over 50% of following cell
See microsoft/vscode#208102
2024-03-19 08:56:13 -07:00
Daniel Imms 9ec9dca5f8 Merge pull request #5009 from Tyriar/tyriar/4987
Fix dom cursor blink animation and improve readability
2024-03-16 11:12:19 -07:00
Daniel Imms a4f1299f42 Special case cursor block blink to not use important
Fixes #4987
2024-03-16 11:07:05 -07:00
Daniel Imms 7b76ca7e49 Make dom rendere animation classes more readable 2024-03-16 09:57:23 -07:00
Daniel Imms 18147b2134 Fix bar blink animation 2024-03-16 09:55:20 -07:00
Daniel Imms c78af2b448 Fix dom cursor blink animation
Fixes #4987
Doesn't seem to regress #4773 which caused this issue
2024-03-16 09:49:36 -07:00
Daniel Imms e2dc01cf6e Merge pull request #5008 from Tyriar/tyriar/4994
Fix search not destroying cache on linefeed
2024-03-16 09:38:54 -07:00
Daniel Imms 14fbdd3e9c Adopt MutableDisposable in SearchAddon 2024-03-16 09:32:49 -07:00
Daniel Imms 3e5d0015af Fix search not destroying cache on linefeed
Fixes #4994
2024-03-16 09:30:25 -07:00
Daniel Imms b4c1007075 Merge pull request #5007 from Tyriar/5005
Don't rescale unless it exceeds 25% of following cell
2024-03-16 09:24:35 -07:00
Daniel Imms e0533db4d9 Merge pull request #5006 from Tyriar/tyriar/4995
Set up shared context on browser even if process exists
2024-03-16 09:18:02 -07:00
Daniel Imms 4071f41f52 Don't rescale unless it exceeds 25% of following cell
Fixes #5005
2024-03-16 09:16:31 -07:00
Daniel Imms 37efeeb044 Make process node check stricter
Fixes #4995
2024-03-16 09:08:54 -07:00
Daniel Imms 47409f39f6 Set up shared context on browser even if process exists
Fixes #4995
2024-03-16 09:05:45 -07:00
Daniel Imms b7b9d27627 Merge pull request #5001 from Tyriar/powerline_nf
Don't rescale powerline or nerd fonts
2024-03-15 08:36:58 -07:00
Daniel Imms 6b3d485d77 Note new excluded glyphs in API 2024-03-15 08:30:55 -07:00
Daniel Imms 48c6e96a78 Don't rescale powerline or nerd fonts
Part of #4969
2024-03-15 08:18:26 -07:00
Daniel Imms 44feecfc9a Merge pull request #4997 from Tyriar/4969
Introduce opt-in glyph scaling to achieve GB18030 compliance
2024-03-14 09:50:13 -07:00
Daniel Imms df559e3444 Rescale overlapping in canvas 2024-03-14 07:26:25 -07:00
Daniel Imms 33a7a9b6a2 Implement rescaleOverlappingGlyphs in webgl
Part of #4969
2024-03-14 07:14:13 -07:00
Daniel Imms 0658719d23 Merge pull request #4993 from Tyriar/tyriar/4992
Add default to api d.ts
2024-03-07 05:30:09 -08:00
Daniel Imms c32313a471 Add default to api d.ts
Fixes #4992
2024-03-07 05:21:59 -08:00
jerch 1da0db435f Merge pull request #4977 from dstein64/master
Add @xterm scope to packages
2024-03-03 12:57:24 +01:00
Daniel Steinberg 17fd7389c4 Add @xterm scope to packages 2024-03-02 03:46:10 +00:00
Daniel Imms 9da5f1401f Merge pull request #4976 from Tyriar/tyriar/readme
Update script/css import in readme
2024-03-01 09:24:49 -08:00
Daniel Imms da3aad4497 Update script/css import in readme
Part of #4859
2024-03-01 09:18:29 -08:00
27 changed files with 164 additions and 52 deletions
+4 -4
View File
@@ -21,7 +21,7 @@ Xterm.js is a front-end component written in TypeScript that lets applications b
First, you need to install the module, we ship exclusively through [npm](https://www.npmjs.com/), so you need that installed and then add xterm.js as a dependency by running: First, you need to install the module, we ship exclusively through [npm](https://www.npmjs.com/), so you need that installed and then add xterm.js as a dependency by running:
```bash ```bash
npm install xterm npm install @xterm/xterm
``` ```
To start using xterm.js on your browser, add the `xterm.js` and `xterm.css` to the head of your HTML page. Then create a `<div id="terminal"></div>` onto which xterm can attach itself. Finally, instantiate the `Terminal` object and then call the `open` function with the DOM object of the `div`. To start using xterm.js on your browser, add the `xterm.js` and `xterm.css` to the head of your HTML page. Then create a `<div id="terminal"></div>` onto which xterm can attach itself. Finally, instantiate the `Terminal` object and then call the `open` function with the DOM object of the `div`.
@@ -30,8 +30,8 @@ To start using xterm.js on your browser, add the `xterm.js` and `xterm.css` to t
<!doctype html> <!doctype html>
<html> <html>
<head> <head>
<link rel="stylesheet" href="node_modules/xterm/css/xterm.css" /> <link rel="stylesheet" href="node_modules/@xterm/xterm/css/xterm.css" />
<script src="node_modules/xterm/lib/xterm.js"></script> <script src="node_modules/@xterm/xterm/lib/xterm.js"></script>
</head> </head>
<body> <body>
<div id="terminal"></div> <div id="terminal"></div>
@@ -113,7 +113,7 @@ All current and past releases are available on this repo's [Releases page](https
Our CI releases beta builds to npm for every change that goes into master. Install the latest beta build with: Our CI releases beta builds to npm for every change that goes into master. Install the latest beta build with:
```bash ```bash
npm install -S xterm@beta npm install -S @xterm/xterm@beta
``` ```
These should generally be stable, but some bugs may slip in. We recommend using the beta build primarily to test out new features and to verify bug fixes. These should generally be stable, but some bugs may slip in. We recommend using the beta build primarily to test out new features and to verify bug fixes.
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@xterm/addon-attach", "name": "@xterm/addon-attach",
"version": "0.10.0", "version": "0.11.0",
"author": { "author": {
"name": "The xterm.js authors", "name": "The xterm.js authors",
"url": "https://xtermjs.org/" "url": "https://xtermjs.org/"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@xterm/addon-canvas", "name": "@xterm/addon-canvas",
"version": "0.6.0", "version": "0.7.0",
"author": { "author": {
"name": "The xterm.js authors", "name": "The xterm.js authors",
"url": "https://xtermjs.org/" "url": "https://xtermjs.org/"
+14 -2
View File
@@ -8,7 +8,7 @@ import { CellColorResolver } from 'browser/renderer/shared/CellColorResolver';
import { acquireTextureAtlas } from 'browser/renderer/shared/CharAtlasCache'; import { acquireTextureAtlas } from 'browser/renderer/shared/CharAtlasCache';
import { TEXT_BASELINE } from 'browser/renderer/shared/Constants'; import { TEXT_BASELINE } from 'browser/renderer/shared/Constants';
import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs';
import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; import { allowRescaling, throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
import { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRenderModel'; import { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRenderModel';
import { IRasterizedGlyph, IRenderDimensions, ISelectionRenderModel, ITextureAtlas } from 'browser/renderer/shared/Types'; import { IRasterizedGlyph, IRenderDimensions, ISelectionRenderModel, ITextureAtlas } from 'browser/renderer/shared/Types';
import { ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
@@ -365,6 +365,8 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer
*/ */
protected _drawChars(cell: ICellData, x: number, y: number): void { protected _drawChars(cell: ICellData, x: number, y: number): void {
const chars = cell.getChars(); const chars = cell.getChars();
const code = cell.getCode();
const width = cell.getWidth();
this._cellColorResolver.resolve(cell, x, this._bufferService.buffer.ydisp + y, this._deviceCellWidth); this._cellColorResolver.resolve(cell, x, this._bufferService.buffer.ydisp + y, this._deviceCellWidth);
if (!this._charAtlas) { if (!this._charAtlas) {
@@ -400,6 +402,16 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer
this._bitmapGenerator[glyph.texturePage]!.refresh(); this._bitmapGenerator[glyph.texturePage]!.refresh();
this._bitmapGenerator[glyph.texturePage]!.version = this._charAtlas.pages[glyph.texturePage].version; this._bitmapGenerator[glyph.texturePage]!.version = this._charAtlas.pages[glyph.texturePage].version;
} }
// Reduce scale horizontally for wide glyphs printed in cells that would overlap with the
// following cell (ie. the width is not 2).
let renderWidth = glyph.size.x;
if (this._optionsService.rawOptions.rescaleOverlappingGlyphs) {
if (allowRescaling(code, width, glyph.size.x, this._deviceCellWidth)) {
renderWidth = this._deviceCellWidth - 1; // - 1 to improve readability
}
}
this._ctx.drawImage( this._ctx.drawImage(
this._bitmapGenerator[glyph.texturePage]?.bitmap || this._charAtlas!.pages[glyph.texturePage].canvas, this._bitmapGenerator[glyph.texturePage]?.bitmap || this._charAtlas!.pages[glyph.texturePage].canvas,
glyph.texturePosition.x, glyph.texturePosition.x,
@@ -408,7 +420,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer
glyph.size.y, glyph.size.y,
x * this._deviceCellWidth + this._deviceCharLeft - glyph.offset.x, x * this._deviceCellWidth + this._deviceCharLeft - glyph.offset.x,
y * this._deviceCellHeight + this._deviceCharTop - glyph.offset.y, y * this._deviceCellHeight + this._deviceCharTop - glyph.offset.y,
glyph.size.x, renderWidth,
glyph.size.y glyph.size.y
); );
this._ctx.restore(); this._ctx.restore();
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@xterm/addon-fit", "name": "@xterm/addon-fit",
"version": "0.9.0", "version": "0.10.0",
"author": { "author": {
"name": "The xterm.js authors", "name": "The xterm.js authors",
"url": "https://xtermjs.org/" "url": "https://xtermjs.org/"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@xterm/addon-image", "name": "@xterm/addon-image",
"version": "0.7.0", "version": "0.8.0",
"author": { "author": {
"name": "The xterm.js authors", "name": "The xterm.js authors",
"url": "https://xtermjs.org/" "url": "https://xtermjs.org/"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@xterm/addon-ligatures", "name": "@xterm/addon-ligatures",
"version": "0.8.0", "version": "0.9.0",
"description": "Add support for programming ligatures to xterm.js", "description": "Add support for programming ligatures to xterm.js",
"author": { "author": {
"name": "The xterm.js authors", "name": "The xterm.js authors",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@xterm/addon-search", "name": "@xterm/addon-search",
"version": "0.14.0", "version": "0.15.0",
"author": { "author": {
"name": "The xterm.js authors", "name": "The xterm.js authors",
"url": "https://xtermjs.org/" "url": "https://xtermjs.org/"
+8 -13
View File
@@ -6,7 +6,7 @@
import type { Terminal, IDisposable, ITerminalAddon, IDecoration } from '@xterm/xterm'; import type { Terminal, IDisposable, ITerminalAddon, IDecoration } from '@xterm/xterm';
import type { SearchAddon as ISearchApi } from '@xterm/addon-search'; import type { SearchAddon as ISearchApi } from '@xterm/addon-search';
import { EventEmitter } from 'common/EventEmitter'; import { EventEmitter } from 'common/EventEmitter';
import { Disposable, toDisposable, disposeArray, MutableDisposable } from 'common/Lifecycle'; import { Disposable, toDisposable, disposeArray, MutableDisposable, getDisposeArrayDisposable } from 'common/Lifecycle';
export interface ISearchOptions { export interface ISearchOptions {
regex?: boolean; regex?: boolean;
@@ -78,8 +78,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA
*/ */
private _linesCache: LineCacheEntry[] | undefined; private _linesCache: LineCacheEntry[] | undefined;
private _linesCacheTimeoutId = 0; private _linesCacheTimeoutId = 0;
private _cursorMoveListener: IDisposable | undefined; private _linesCacheDisposables = new MutableDisposable();
private _resizeListener: IDisposable | undefined;
private readonly _onDidChangeResults = this.register(new EventEmitter<{ resultIndex: number, resultCount: number }>()); private readonly _onDidChangeResults = this.register(new EventEmitter<{ resultIndex: number, resultCount: number }>());
public readonly onDidChangeResults = this._onDidChangeResults.event; public readonly onDidChangeResults = this._onDidChangeResults.event;
@@ -427,8 +426,11 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA
const terminal = this._terminal!; const terminal = this._terminal!;
if (!this._linesCache) { if (!this._linesCache) {
this._linesCache = new Array(terminal.buffer.active.length); this._linesCache = new Array(terminal.buffer.active.length);
this._cursorMoveListener = terminal.onCursorMove(() => this._destroyLinesCache()); this._linesCacheDisposables.value = getDisposeArrayDisposable([
this._resizeListener = terminal.onResize(() => this._destroyLinesCache()); terminal.onLineFeed(() => this._destroyLinesCache()),
terminal.onCursorMove(() => this._destroyLinesCache()),
terminal.onResize(() => this._destroyLinesCache())
]);
} }
window.clearTimeout(this._linesCacheTimeoutId); window.clearTimeout(this._linesCacheTimeoutId);
@@ -437,14 +439,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA
private _destroyLinesCache(): void { private _destroyLinesCache(): void {
this._linesCache = undefined; this._linesCache = undefined;
if (this._cursorMoveListener) { this._linesCacheDisposables.clear();
this._cursorMoveListener.dispose();
this._cursorMoveListener = undefined;
}
if (this._resizeListener) {
this._resizeListener.dispose();
this._resizeListener = undefined;
}
if (this._linesCacheTimeoutId) { if (this._linesCacheTimeoutId) {
window.clearTimeout(this._linesCacheTimeoutId); window.clearTimeout(this._linesCacheTimeoutId);
this._linesCacheTimeoutId = 0; this._linesCacheTimeoutId = 0;
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@xterm/addon-serialize", "name": "@xterm/addon-serialize",
"version": "0.12.0", "version": "0.13.0",
"author": { "author": {
"name": "The xterm.js authors", "name": "The xterm.js authors",
"url": "https://xtermjs.org/" "url": "https://xtermjs.org/"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@xterm/addon-unicode-graphemes", "name": "@xterm/addon-unicode-graphemes",
"version": "0.2.0", "version": "0.3.0",
"author": { "author": {
"name": "The xterm.js authors", "name": "The xterm.js authors",
"url": "https://xtermjs.org/" "url": "https://xtermjs.org/"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@xterm/addon-unicode11", "name": "@xterm/addon-unicode11",
"version": "0.7.0", "version": "0.8.0",
"author": { "author": {
"name": "The xterm.js authors", "name": "The xterm.js authors",
"url": "https://xtermjs.org/" "url": "https://xtermjs.org/"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@xterm/addon-web-links", "name": "@xterm/addon-web-links",
"version": "0.10.0", "version": "0.11.0",
"author": { "author": {
"name": "The xterm.js authors", "name": "The xterm.js authors",
"url": "https://xtermjs.org/" "url": "https://xtermjs.org/"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@xterm/addon-webgl", "name": "@xterm/addon-webgl",
"version": "0.17.0", "version": "0.18.0",
"author": { "author": {
"name": "The xterm.js authors", "name": "The xterm.js authors",
"url": "https://xtermjs.org/" "url": "https://xtermjs.org/"
+15 -5
View File
@@ -3,7 +3,7 @@
* @license MIT * @license MIT
*/ */
import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; import { allowRescaling, throwIfFalsy } from 'browser/renderer/shared/RendererUtils';
import { TextureAtlas } from 'browser/renderer/shared/TextureAtlas'; import { TextureAtlas } from 'browser/renderer/shared/TextureAtlas';
import { IRasterizedGlyph, IRenderDimensions, ITextureAtlas } from 'browser/renderer/shared/Types'; import { IRasterizedGlyph, IRenderDimensions, ITextureAtlas } from 'browser/renderer/shared/Types';
import { NULL_CELL_CODE } from 'common/buffer/Constants'; import { NULL_CELL_CODE } from 'common/buffer/Constants';
@@ -11,6 +11,7 @@ import { Disposable, toDisposable } from 'common/Lifecycle';
import { Terminal } from '@xterm/xterm'; import { Terminal } from '@xterm/xterm';
import { IRenderModel, IWebGL2RenderingContext, IWebGLVertexArrayObject } from './Types'; import { IRenderModel, IWebGL2RenderingContext, IWebGLVertexArrayObject } from './Types';
import { createProgram, GLTexture, PROJECTION_MATRIX } from './WebglUtils'; import { createProgram, GLTexture, PROJECTION_MATRIX } from './WebglUtils';
import type { IOptionsService } from 'common/services/Services';
interface IVertices { interface IVertices {
attributes: Float32Array; attributes: Float32Array;
@@ -111,7 +112,8 @@ export class GlyphRenderer extends Disposable {
constructor( constructor(
private readonly _terminal: Terminal, private readonly _terminal: Terminal,
private readonly _gl: IWebGL2RenderingContext, private readonly _gl: IWebGL2RenderingContext,
private _dimensions: IRenderDimensions private _dimensions: IRenderDimensions,
private readonly _optionsService: IOptionsService
) { ) {
super(); super();
@@ -212,15 +214,15 @@ export class GlyphRenderer extends Disposable {
return this._atlas ? this._atlas.beginFrame() : true; return this._atlas ? this._atlas.beginFrame() : true;
} }
public updateCell(x: number, y: number, code: number, bg: number, fg: number, ext: number, chars: string, lastBg: number): void { 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 // 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 // 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 // slight slowdown is acceptable for the developer ergonomics provided as it's a once of for
// each glyph. // each glyph.
this._updateCell(this._vertices.attributes, x, y, code, bg, fg, ext, chars, lastBg); 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, lastBg: number): void { 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) * INDICES_PER_CELL; $i = (y * this._terminal.cols + x) * INDICES_PER_CELL;
// Exit early if this is a null character, allow space character to continue as it may have // Exit early if this is a null character, allow space character to continue as it may have
@@ -275,6 +277,14 @@ export class GlyphRenderer extends Disposable {
array[$i + 8] = $glyph.sizeClipSpace.y; array[$i + 8] = $glyph.sizeClipSpace.y;
} }
// a_cellpos only changes on resize // 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).
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
}
}
} }
public clear(): void { public clear(): void {
+7 -4
View File
@@ -36,7 +36,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
private _observerDisposable = this.register(new MutableDisposable()); private _observerDisposable = this.register(new MutableDisposable());
private _model: RenderModel = new RenderModel(); private _model: RenderModel = new RenderModel();
private _workCell: CellData = new CellData(); private _workCell: ICellData = new CellData();
private _workCell2: ICellData = new CellData();
private _cellColorResolver: CellColorResolver; private _cellColorResolver: CellColorResolver;
private _canvas: HTMLCanvasElement; private _canvas: HTMLCanvasElement;
@@ -245,7 +246,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
*/ */
private _initializeWebGLState(): [RectangleRenderer, GlyphRenderer] { private _initializeWebGLState(): [RectangleRenderer, GlyphRenderer] {
this._rectangleRenderer.value = new RectangleRenderer(this._terminal, this._gl, this.dimensions, this._themeService); this._rectangleRenderer.value = new RectangleRenderer(this._terminal, this._gl, this.dimensions, this._themeService);
this._glyphRenderer.value = new GlyphRenderer(this._terminal, this._gl, this.dimensions); this._glyphRenderer.value = new GlyphRenderer(this._terminal, this._gl, this.dimensions, this._optionsService);
// Update dimensions and acquire char atlas // Update dimensions and acquire char atlas
this.handleCharSizeChanged(); this.handleCharSizeChanged();
@@ -388,6 +389,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
let range: [number, number]; let range: [number, number];
let chars: string; let chars: string;
let code: number; let code: number;
let width: number;
let i: number; let i: number;
let x: number; let x: number;
let j: number; let j: number;
@@ -500,7 +502,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
this._model.cells[i + RENDER_MODEL_FG_OFFSET] = this._cellColorResolver.result.fg; this._model.cells[i + RENDER_MODEL_FG_OFFSET] = this._cellColorResolver.result.fg;
this._model.cells[i + RENDER_MODEL_EXT_OFFSET] = this._cellColorResolver.result.ext; this._model.cells[i + RENDER_MODEL_EXT_OFFSET] = this._cellColorResolver.result.ext;
this._glyphRenderer.value!.updateCell(x, y, code, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, chars, lastBg); width = cell.getWidth();
this._glyphRenderer.value!.updateCell(x, y, code, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, chars, width, lastBg);
if (isJoined) { if (isJoined) {
// Restore work cell // Restore work cell
@@ -509,7 +512,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
// Null out non-first cells // Null out non-first cells
for (x++; x < lastCharX; x++) { for (x++; x < lastCharX; x++) {
j = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL; j = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL;
this._glyphRenderer.value!.updateCell(x, y, NULL_CELL_CODE, 0, 0, 0, NULL_CELL_CHAR, 0); this._glyphRenderer.value!.updateCell(x, y, NULL_CELL_CODE, 0, 0, 0, NULL_CELL_CHAR, 0, 0);
this._model.cells[j] = NULL_CELL_CODE; this._model.cells[j] = NULL_CELL_CODE;
this._model.cells[j + RENDER_MODEL_BG_OFFSET] = this._cellColorResolver.result.bg; this._model.cells[j + RENDER_MODEL_BG_OFFSET] = this._cellColorResolver.result.bg;
this._model.cells[j + RENDER_MODEL_FG_OFFSET] = this._cellColorResolver.result.fg; this._model.cells[j + RENDER_MODEL_FG_OFFSET] = this._cellColorResolver.result.fg;
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "@xterm/xterm", "name": "@xterm/xterm",
"description": "Full xterm terminal, in your browser", "description": "Full xterm terminal, in your browser",
"version": "5.4.0", "version": "5.5.0",
"main": "lib/xterm.js", "main": "lib/xterm.js",
"style": "css/xterm.css", "style": "css/xterm.css",
"types": "typings/xterm.d.ts", "types": "typings/xterm.d.ts",
+24 -5
View File
@@ -183,14 +183,23 @@ export class DomRenderer extends Disposable implements IRenderer {
` font-style: italic;` + ` font-style: italic;` +
`}`; `}`;
// Blink animation // Blink animation
const blinkAnimationUnderlineId = `blink_underline_${this._terminalClass}`;
const blinkAnimationBarId = `blink_bar_${this._terminalClass}`;
const blinkAnimationBlockId = `blink_block_${this._terminalClass}`;
styles += styles +=
`@keyframes blink_box_shadow` + `_` + this._terminalClass + ` {` + `@keyframes ${blinkAnimationUnderlineId} {` +
` 50% {` + ` 50% {` +
` border-bottom-style: hidden;` + ` border-bottom-style: hidden;` +
` }` + ` }` +
`}`; `}`;
styles += styles +=
`@keyframes blink_block` + `_` + this._terminalClass + ` {` + `@keyframes ${blinkAnimationBarId} {` +
` 50% {` +
` box-shadow: none;` +
` }` +
`}`;
styles +=
`@keyframes ${blinkAnimationBlockId} {` +
` 0% {` + ` 0% {` +
` background-color: ${colors.cursor.css};` + ` background-color: ${colors.cursor.css};` +
` color: ${colors.cursorAccent.css};` + ` color: ${colors.cursorAccent.css};` +
@@ -202,13 +211,23 @@ export class DomRenderer extends Disposable implements IRenderer {
`}`; `}`;
// Cursor // Cursor
styles += styles +=
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS}:not(.${RowCss.CURSOR_STYLE_BLOCK_CLASS}) {` + `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS}.${RowCss.CURSOR_STYLE_UNDERLINE_CLASS} {` +
` animation: blink_box_shadow` + `_` + this._terminalClass + ` 1s step-end infinite;` + ` animation: ${blinkAnimationUnderlineId} 1s step-end infinite;` +
`}` +
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS}.${RowCss.CURSOR_STYLE_BAR_CLASS} {` +
` animation: ${blinkAnimationBarId} 1s step-end infinite;` +
`}` + `}` +
`${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS} {` + `${this._terminalSelector} .${ROW_CONTAINER_CLASS}.${FOCUS_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_BLINK_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS} {` +
` animation: blink_block` + `_` + this._terminalClass + ` 1s step-end infinite;` + ` animation: ${blinkAnimationBlockId} 1s step-end infinite;` +
`}` + `}` +
// !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} .${ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS} {` + `${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS} {` +
` background-color: ${colors.cursor.css};` +
` color: ${colors.cursorAccent.css};` +
`}` +
`${this._terminalSelector} .${ROW_CONTAINER_CLASS} .${RowCss.CURSOR_CLASS}.${RowCss.CURSOR_STYLE_BLOCK_CLASS}:not(.${RowCss.CURSOR_BLINK_CLASS}) {` +
` background-color: ${colors.cursor.css} !important;` + ` background-color: ${colors.cursor.css} !important;` +
` color: ${colors.cursorAccent.css} !important;` + ` color: ${colors.cursorAccent.css} !important;` +
`}` + `}` +
@@ -23,10 +23,43 @@ export function isRestrictedPowerlineGlyph(codepoint: number): boolean {
return 0xE0B0 <= codepoint && codepoint <= 0xE0B7; return 0xE0B0 <= codepoint && codepoint <= 0xE0B7;
} }
function isNerdFontGlyph(codepoint: number): boolean {
return 0xE000 <= codepoint && codepoint <= 0xF8FF;
}
function isBoxOrBlockGlyph(codepoint: number): boolean { function isBoxOrBlockGlyph(codepoint: number): boolean {
return 0x2500 <= codepoint && codepoint <= 0x259F; return 0x2500 <= codepoint && codepoint <= 0x259F;
} }
export function isEmoji(codepoint: number): boolean {
return (
codepoint >= 0x1F600 && codepoint <= 0x1F64F || // Emoticons
codepoint >= 0x1F300 && codepoint <= 0x1F5FF || // Misc Symbols and Pictographs
codepoint >= 0x1F680 && codepoint <= 0x1F6FF || // Transport and Map
codepoint >= 0x2600 && codepoint <= 0x26FF || // Misc symbols
codepoint >= 0x2700 && codepoint <= 0x27BF || // Dingbats
codepoint >= 0xFE00 && codepoint <= 0xFE0F || // Variation Selectors
codepoint >= 0x1F900 && codepoint <= 0x1F9FF || // Supplemental Symbols and Pictographs
codepoint >= 0x1F1E6 && codepoint <= 0x1F1FF
);
}
export function allowRescaling(codepoint: number | undefined, width: number, glyphSizeX: number, deviceCellWidth: number): boolean {
return (
// Is single cell width
width === 1 &&
// 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 &&
// Never rescale emoji
!isEmoji(codepoint) &&
// Never rescale powerline or nerd fonts
!isPowerlineGlyph(codepoint) && !isNerdFontGlyph(codepoint)
);
}
export function treatGlyphAsBackgroundColor(codepoint: number): boolean { export function treatGlyphAsBackgroundColor(codepoint: number): boolean {
return isPowerlineGlyph(codepoint) || isBoxOrBlockGlyph(codepoint); return isPowerlineGlyph(codepoint) || isBoxOrBlockGlyph(codepoint);
} }
+2 -1
View File
@@ -87,7 +87,8 @@ export class RenderService extends Disposable implements IRenderService {
'fontSize', 'fontSize',
'fontWeight', 'fontWeight',
'fontWeightBold', 'fontWeightBold',
'minimumContrastRatio' 'minimumContrastRatio',
'rescaleOverlappingGlyphs'
], () => { ], () => {
this.clear(); this.clear();
this.handleResize(bufferService.cols, bufferService.rows); this.handleResize(bufferService.cols, bufferService.rows);
+5 -2
View File
@@ -3,7 +3,6 @@
* @license MIT * @license MIT
*/ */
import { isNode } from 'common/Platform';
import { IColor, IColorRGB } from 'common/Types'; import { IColor, IColorRGB } from 'common/Types';
let $r = 0; let $r = 0;
@@ -117,9 +116,10 @@ export namespace color {
* '#rrggbbaa'). * '#rrggbbaa').
*/ */
export namespace css { export namespace css {
// Attempt to set get the shared canvas context
let $ctx: CanvasRenderingContext2D | undefined; let $ctx: CanvasRenderingContext2D | undefined;
let $litmusColor: CanvasGradient | undefined; let $litmusColor: CanvasGradient | undefined;
if (!isNode) { try {
// This is guaranteed to run in the first window, so document should be correct // This is guaranteed to run in the first window, so document should be correct
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas');
canvas.width = 1; canvas.width = 1;
@@ -133,6 +133,9 @@ export namespace css {
$litmusColor = $ctx.createLinearGradient(0, 0, 1, 1); $litmusColor = $ctx.createLinearGradient(0, 0, 1, 1);
} }
} }
catch {
// noop
}
/** /**
* Converts a css string to an IColor, this should handle all valid CSS color strings and will * Converts a css string to an IColor, this should handle all valid CSS color strings and will
+1 -1
View File
@@ -14,7 +14,7 @@ interface INavigator {
declare const navigator: INavigator; declare const navigator: INavigator;
declare const process: unknown; declare const process: unknown;
export const isNode = (typeof process !== 'undefined') ? true : false; export const isNode = (typeof process !== 'undefined' && 'title' in (process as any)) ? true : false;
const userAgent = (isNode) ? 'node' : navigator.userAgent; const userAgent = (isNode) ? 'node' : navigator.userAgent;
const platform = (isNode) ? 'node' : navigator.platform; const platform = (isNode) ? 'node' : navigator.platform;
+1
View File
@@ -44,6 +44,7 @@ export const DEFAULT_OPTIONS: Readonly<Required<ITerminalOptions>> = {
allowTransparency: false, allowTransparency: false,
tabStopWidth: 8, tabStopWidth: 8,
theme: {}, theme: {},
rescaleOverlappingGlyphs: false,
rightClickSelectsWord: isMac, rightClickSelectsWord: isMac,
windowOptions: {}, windowOptions: {},
windowsMode: false, windowsMode: false,
+1
View File
@@ -234,6 +234,7 @@ export interface ITerminalOptions {
macOptionIsMeta?: boolean; macOptionIsMeta?: boolean;
macOptionClickForcesSelection?: boolean; macOptionClickForcesSelection?: boolean;
minimumContrastRatio?: number; minimumContrastRatio?: number;
rescaleOverlappingGlyphs?: boolean;
rightClickSelectsWord?: boolean; rightClickSelectsWord?: boolean;
rows?: number; rows?: number;
screenReaderMode?: boolean; screenReaderMode?: boolean;
+1 -1
View File
@@ -6,7 +6,7 @@
import { IImage32, decodePng } from '@lunapaint/png-codec'; import { IImage32, decodePng } from '@lunapaint/png-codec';
import { LocatorScreenshotOptions, test } from '@playwright/test'; import { LocatorScreenshotOptions, test } from '@playwright/test';
import { ITheme } from '@xterm/xterm'; import { ITheme } from '@xterm/xterm';
import { ITestContext, MaybeAsync, openTerminal, pollFor, pollForApproximate, timeout } from './TestUtils'; import { ITestContext, MaybeAsync, openTerminal, pollFor, pollForApproximate } from './TestUtils';
export interface ISharedRendererTestContext { export interface ISharedRendererTestContext {
value: ITestContext; value: ITestContext;
+18 -1
View File
@@ -140,6 +140,23 @@ declare module '@xterm/headless' {
*/ */
minimumContrastRatio?: number; minimumContrastRatio?: number;
/**
* Whether to rescale glyphs horizontally that are a single cell wide but
* have glyphs that would overlap following cell(s). This typically happens
* for ambiguous width characters (eg. the roman numeral characters U+2160+)
* which aren't featured in monospace fonts. This is an important feature
* for achieving GB18030 compliance.
*
* The following glyphs will never be rescaled:
*
* - Emoji glyphs
* - Powerline glyphs
* - Nerd font glyphs
*
* Note that this doesn't work with the DOM renderer. The default is false.
*/
rescaleOverlappingGlyphs?: boolean;
/** /**
* Whether to select the word under the cursor on right click, this is * Whether to select the word under the cursor on right click, this is
* standard behavior in a lot of macOS applications. * standard behavior in a lot of macOS applications.
@@ -156,7 +173,7 @@ declare module '@xterm/headless' {
/** /**
* The amount of scrollback in the terminal. Scrollback is the amount of * The amount of scrollback in the terminal. Scrollback is the amount of
* rows that are retained when lines are scrolled beyond the initial * rows that are retained when lines are scrolled beyond the initial
* viewport. * viewport. Defaults to 1000.
*/ */
scrollback?: number; scrollback?: number;
+18 -1
View File
@@ -209,6 +209,23 @@ declare module '@xterm/xterm' {
*/ */
minimumContrastRatio?: number; minimumContrastRatio?: number;
/**
* Whether to rescale glyphs horizontally that are a single cell wide but
* have glyphs that would overlap following cell(s). This typically happens
* for ambiguous width characters (eg. the roman numeral characters U+2160+)
* which aren't featured in monospace fonts. This is an important feature
* for achieving GB18030 compliance.
*
* The following glyphs will never be rescaled:
*
* - Emoji glyphs
* - Powerline glyphs
* - Nerd font glyphs
*
* Note that this doesn't work with the DOM renderer. The default is false.
*/
rescaleOverlappingGlyphs?: boolean;
/** /**
* Whether to select the word under the cursor on right click, this is * Whether to select the word under the cursor on right click, this is
* standard behavior in a lot of macOS applications. * standard behavior in a lot of macOS applications.
@@ -225,7 +242,7 @@ declare module '@xterm/xterm' {
/** /**
* The amount of scrollback in the terminal. Scrollback is the amount of * The amount of scrollback in the terminal. Scrollback is the amount of
* rows that are retained when lines are scrolled beyond the initial * rows that are retained when lines are scrolled beyond the initial
* viewport. * viewport. Defaults to 1000.
*/ */
scrollback?: number; scrollback?: number;