Skip to content

Commit

Permalink
Merge branch 'master' into patch-1
Browse files Browse the repository at this point in the history
  • Loading branch information
Tyriar committed Apr 21, 2024
2 parents b5ca41d + 770c173 commit 650ecb0
Show file tree
Hide file tree
Showing 44 changed files with 385 additions and 109 deletions.
8 changes: 5 additions & 3 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
{
"name": "xterm.js",
"image": "mcr.microsoft.com/devcontainers/typescript-node:0-18-buster",
"image": "mcr.microsoft.com/devcontainers/typescript-node:18-bookworm",
"features": {
"ghcr.io/devcontainers/features/node:1": {} // yarn
"ghcr.io/devcontainers/features/node:1": {
"version": 18
} // yarn
},
"forwardPorts": [
3000
],
"postCreateCommand": "yarn install",
"postCreateCommand": "yarn install && yarn setup",
"customizations": {
"vscode": {
"extensions": [
Expand Down
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
16
18
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
"runtimeExecutable": "npm",
"runtimeArgs": ["start"],
"stopOnEntry": true,
"runtimeVersion": "16",
"runtimeVersion": "18",
"serverReadyAction": {
"action": "openExternally",
"pattern": "App listening to (http://.*?:[0-9]+)"
Expand Down
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

```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`.
Expand All @@ -30,8 +30,8 @@ To start using xterm.js on your browser, add the `xterm.js` and `xterm.css` to t
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="node_modules/xterm/css/xterm.css" />
<script src="node_modules/xterm/lib/xterm.js"></script>
<link rel="stylesheet" href="node_modules/@xterm/xterm/css/xterm.css" />
<script src="node_modules/@xterm/xterm/lib/xterm.js"></script>
</head>
<body>
<div id="terminal"></div>
Expand Down Expand Up @@ -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:

```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.
Expand Down Expand Up @@ -222,6 +222,7 @@ Xterm.js is used in several world-class applications to provide great terminal e
- [**Cloudtutor.io**](https://cloudtutor.io): innovative online learning platform that offers users access to an interactive lab.
- [**Helix Editor Playground**](https://github.com/tomgroenwoldt/helix-editor-playground): Online playground for the terminal based helix editor.
- [**Coder**](https://github.com/coder/coder): Self-Hosted Remote Development Environments
- [**Wave Terminal**](https://waveterm.dev): An open-source, ai-native, terminal built for seamless workflows.
- [And much more...](https://github.com/xtermjs/xterm.js/network/dependents?package_id=UGFja2FnZS0xNjYzMjc4OQ%3D%3D)

Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it on our list. Note: Please add any new contributions to the end of the list only.
Expand Down
2 changes: 1 addition & 1 deletion addons/addon-attach/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@xterm/addon-attach",
"version": "0.9.0",
"version": "0.11.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
Expand Down
2 changes: 1 addition & 1 deletion addons/addon-canvas/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@xterm/addon-canvas",
"version": "0.5.0",
"version": "0.7.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
Expand Down
16 changes: 14 additions & 2 deletions addons/addon-canvas/src/BaseRenderLayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { CellColorResolver } from 'browser/renderer/shared/CellColorResolver';
import { acquireTextureAtlas } from 'browser/renderer/shared/CharAtlasCache';
import { TEXT_BASELINE } from 'browser/renderer/shared/Constants';
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 { IRasterizedGlyph, IRenderDimensions, ISelectionRenderModel, ITextureAtlas } from 'browser/renderer/shared/Types';
import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
Expand Down Expand Up @@ -365,6 +365,8 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer
*/
protected _drawChars(cell: ICellData, x: number, y: number): void {
const chars = cell.getChars();
const code = cell.getCode();
const width = cell.getWidth();
this._cellColorResolver.resolve(cell, x, this._bufferService.buffer.ydisp + y, this._deviceCellWidth);

if (!this._charAtlas) {
Expand Down Expand Up @@ -400,6 +402,16 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer
this._bitmapGenerator[glyph.texturePage]!.refresh();
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._bitmapGenerator[glyph.texturePage]?.bitmap || this._charAtlas!.pages[glyph.texturePage].canvas,
glyph.texturePosition.x,
Expand All @@ -408,7 +420,7 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer
glyph.size.y,
x * this._deviceCellWidth + this._deviceCharLeft - glyph.offset.x,
y * this._deviceCellHeight + this._deviceCharTop - glyph.offset.y,
glyph.size.x,
renderWidth,
glyph.size.y
);
this._ctx.restore();
Expand Down
2 changes: 1 addition & 1 deletion addons/addon-fit/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@xterm/addon-fit",
"version": "0.8.0",
"version": "0.10.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
Expand Down
2 changes: 1 addition & 1 deletion addons/addon-image/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@xterm/addon-image",
"version": "0.6.0",
"version": "0.8.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
Expand Down
2 changes: 1 addition & 1 deletion addons/addon-ligatures/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@xterm/addon-ligatures",
"version": "0.7.0",
"version": "0.9.0",
"description": "Add support for programming ligatures to xterm.js",
"author": {
"name": "The xterm.js authors",
Expand Down
2 changes: 1 addition & 1 deletion addons/addon-search/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@xterm/addon-search",
"version": "0.13.0",
"version": "0.15.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
Expand Down
21 changes: 8 additions & 13 deletions addons/addon-search/src/SearchAddon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import type { Terminal, IDisposable, ITerminalAddon, IDecoration } from '@xterm/xterm';
import type { SearchAddon as ISearchApi } from '@xterm/addon-search';
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 {
regex?: boolean;
Expand Down Expand Up @@ -78,8 +78,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA
*/
private _linesCache: LineCacheEntry[] | undefined;
private _linesCacheTimeoutId = 0;
private _cursorMoveListener: IDisposable | undefined;
private _resizeListener: IDisposable | undefined;
private _linesCacheDisposables = new MutableDisposable();

private readonly _onDidChangeResults = this.register(new EventEmitter<{ resultIndex: number, resultCount: number }>());
public readonly onDidChangeResults = this._onDidChangeResults.event;
Expand Down Expand Up @@ -427,8 +426,11 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA
const terminal = this._terminal!;
if (!this._linesCache) {
this._linesCache = new Array(terminal.buffer.active.length);
this._cursorMoveListener = terminal.onCursorMove(() => this._destroyLinesCache());
this._resizeListener = terminal.onResize(() => this._destroyLinesCache());
this._linesCacheDisposables.value = getDisposeArrayDisposable([
terminal.onLineFeed(() => this._destroyLinesCache()),
terminal.onCursorMove(() => this._destroyLinesCache()),
terminal.onResize(() => this._destroyLinesCache())
]);
}

window.clearTimeout(this._linesCacheTimeoutId);
Expand All @@ -437,14 +439,7 @@ export class SearchAddon extends Disposable implements ITerminalAddon , ISearchA

private _destroyLinesCache(): void {
this._linesCache = undefined;
if (this._cursorMoveListener) {
this._cursorMoveListener.dispose();
this._cursorMoveListener = undefined;
}
if (this._resizeListener) {
this._resizeListener.dispose();
this._resizeListener = undefined;
}
this._linesCacheDisposables.clear();
if (this._linesCacheTimeoutId) {
window.clearTimeout(this._linesCacheTimeoutId);
this._linesCacheTimeoutId = 0;
Expand Down
2 changes: 1 addition & 1 deletion addons/addon-serialize/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@xterm/addon-serialize",
"version": "0.11.0",
"version": "0.13.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
Expand Down
2 changes: 1 addition & 1 deletion addons/addon-unicode-graphemes/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@xterm/addon-unicode-graphemes",
"version": "0.1.0",
"version": "0.3.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
Expand Down
2 changes: 1 addition & 1 deletion addons/addon-unicode11/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@xterm/addon-unicode11",
"version": "0.6.0",
"version": "0.8.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
Expand Down
2 changes: 1 addition & 1 deletion addons/addon-web-links/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@xterm/addon-web-links",
"version": "0.9.0",
"version": "0.11.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
Expand Down
30 changes: 12 additions & 18 deletions addons/addon-web-links/src/WebLinkProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,18 @@ export class WebLinkProvider implements ILinkProvider {
}
}

function baseUrlString(url: URL): string {
if (url.password && url.username) {
return `${url.protocol}//${url.username}:${url.password}@${url.host}`;
function isUrl(urlString: string): boolean {
try {
const url = new URL(urlString);
const parsedBase = url.password && url.username
? `${url.protocol}//${url.username}:${url.password}@${url.host}`
: url.username
? `${url.protocol}//${url.username}@${url.host}`
: `${url.protocol}//${url.host}`;
return urlString.toLocaleLowerCase().startsWith(parsedBase.toLocaleLowerCase());
} catch (e) {
return false;
}

if (url.username) {
return `${url.protocol}//${url.username}@${url.host}`;
}

return `${url.protocol}//${url.host}`;
}

export class LinkComputer {
Expand All @@ -67,15 +69,7 @@ export class LinkComputer {
const text = match[0];

// check via URL if the matched text would form a proper url
// NOTE: This outsources the ugly url parsing to the browser.
// we check if the provided string resembles the URL-parsed one
// up to the end of the domain name (ignoring path and params)
try {
const url = new URL(text);
if (!text.startsWith(baseUrlString(url))) {
continue;
}
} catch (e) {
if (!isUrl(text)) {
continue;
}

Expand Down
2 changes: 1 addition & 1 deletion addons/addon-web-links/src/WebLinksAddon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { ILinkProviderOptions, WebLinkProvider } from './WebLinkProvider';
// - final interpunction like ,.!?
// - any sort of brackets <>()[]{} (not spec conform, but often used to enclose urls)
// - unsafe chars from rfc1738: {}|\^~[]`
const strictUrlRegex = /https?:[/]{2}[^\s"'!*(){}|\\\^<>`]*[^\s"':,.!?{}|\\\^~\[\]`()<>]/;
const strictUrlRegex = /(https?|HTTPS?):[/]{2}[^\s"'!*(){}|\\\^<>`]*[^\s"':,.!?{}|\\\^~\[\]`()<>]/;


function handleLink(event: MouseEvent, uri: string): void {
Expand Down
15 changes: 15 additions & 0 deletions addons/addon-web-links/test/WebLinksAddon.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,21 @@ describe('WebLinksAddon', () => {
await evalLinkStateData('http://test:password@example.com/some_path?param=1%202%3', { start: { x: 12, y: 1 }, end: { x: 27, y: 2 } });
});
});

// issue #4964
it('uppercase in protocol and host, default ports', async () => {
const data = ` HTTP://EXAMPLE.COM \\r\\n` +
` HTTPS://Example.com \\r\\n` +
` HTTP://Example.com:80 \\r\\n` +
` HTTP://Example.com:80/staysUpper \\r\\n` +
` HTTP://Ab:xY@abc.com:80/staysUpper \\r\\n`;
await writeSync(page, data);
await pollForLinkAtCell(3, 0, `HTTP://EXAMPLE.COM`);
await pollForLinkAtCell(3, 1, `HTTPS://Example.com`);
await pollForLinkAtCell(3, 2, `HTTP://Example.com:80`);
await pollForLinkAtCell(3, 3, `HTTP://Example.com:80/staysUpper`);
await pollForLinkAtCell(3, 4, `HTTP://Ab:xY@abc.com:80/staysUpper`);
});
});

async function testHostName(hostname: string): Promise<void> {
Expand Down
2 changes: 1 addition & 1 deletion addons/addon-webgl/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@xterm/addon-webgl",
"version": "0.16.0",
"version": "0.18.0",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
Expand Down
20 changes: 15 additions & 5 deletions addons/addon-webgl/src/GlyphRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
* @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 { IRasterizedGlyph, IRenderDimensions, ITextureAtlas } from 'browser/renderer/shared/Types';
import { NULL_CELL_CODE } from 'common/buffer/Constants';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { Terminal } from '@xterm/xterm';
import { IRenderModel, IWebGL2RenderingContext, IWebGLVertexArrayObject } from './Types';
import { createProgram, GLTexture, PROJECTION_MATRIX } from './WebglUtils';
import type { IOptionsService } from 'common/services/Services';

interface IVertices {
attributes: Float32Array;
Expand Down Expand Up @@ -111,7 +112,8 @@ export class GlyphRenderer extends Disposable {
constructor(
private readonly _terminal: Terminal,
private readonly _gl: IWebGL2RenderingContext,
private _dimensions: IRenderDimensions
private _dimensions: IRenderDimensions,
private readonly _optionsService: IOptionsService
) {
super();

Expand Down Expand Up @@ -212,15 +214,15 @@ export class GlyphRenderer extends Disposable {
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
// 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, 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;

// Exit early if this is a null character, allow space character to continue as it may have
Expand Down Expand Up @@ -275,6 +277,14 @@ export class GlyphRenderer extends Disposable {
array[$i + 8] = $glyph.sizeClipSpace.y;
}
// 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 {
Expand Down
Loading

0 comments on commit 650ecb0

Please sign in to comment.